diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 12e02c0e..00000000 --- a/.babelrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presets": [ - ["env", { - "targets": { - "chrome": 40, - "firefox": 35, - "edge": 14 - }, - "modules": false - }] - ] -} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..b50059bb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[{package.json,.travis.yml,nightwatch.json}] +indent_style = space +indent_size = 2 diff --git a/.eslintignore b/.eslintignore index 4277ae0f..529676da 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1 @@ -src/core/lib/** +src/core/vendor/** diff --git a/.eslintrc.json b/.eslintrc.json index 8632d2e6..d5e4e768 100755 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,10 +1,12 @@ { + "parser": "babel-eslint", "parserOptions": { - "ecmaVersion": 8, + "ecmaVersion": 9, "ecmaFeatures": { "impliedStrict": true }, - "sourceType": "module" + "sourceType": "module", + "allowImportExportEverywhere": true }, "env": { "browser": true, @@ -28,18 +30,13 @@ // modify rules from base configurations "no-unused-vars": ["error", { "args": "none", - "vars": "local", - // Allow vars that start with a capital letter to be unused. - // This is mainly for exported module names which are useful to indicate - // the name of the module and may be used to refer to itself in future. - "varsIgnorePattern": "^[A-Z]" + "vars": "all" }], "no-empty": ["error", { "allowEmptyCatch": true }], // disable rules from base configurations - "no-console": "off", "no-control-regex": "off", // stylistic conventions @@ -52,7 +49,11 @@ "no-trailing-spaces": "warn", "eol-last": "error", "func-call-spacing": "error", + "key-spacing": ["warn", { + "mode": "minimum" + }], "indent": ["error", 4, { + "ignoreComments": true, "ArrayExpression": "first", "SwitchCase": 1 }], @@ -85,14 +86,28 @@ "no-whitespace-before-property": "error", "operator-linebreak": ["error", "after"], "space-in-parens": "error", - "no-var": "error" + "no-var": "error", + "prefer-const": "error" }, + "overrides": [ + { + "files": "tests/**/*", + "rules": { + "no-unused-expressions": "off", + "no-console": "off" + } + } + ], "globals": { "$": false, "jQuery": false, - "moment": false, + "log": false, "COMPILE_TIME": false, - "COMPILE_MSG": false + "COMPILE_MSG": false, + "PKG_VERSION": false, + "ENVIRONMENT_IS_WORKER": false, + "ENVIRONMENT_IS_NODE": false, + "ENVIRONMENT_IS_WEB": false } } diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9e029351..8e7dee82 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -5,21 +5,10 @@ ### Summary - - + ### Example -### Possible solutions - - - -### Environment - - -* CyberChef compile time: -* User-Agent: -* [Link to reproduce]() diff --git a/.gitignore b/.gitignore index deafd4da..3ca816f6 100755 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,8 @@ docs/* !docs/*.conf.json !docs/*.ico .vscode +src/core/config/modules/* +src/core/config/OperationConfig.json +src/core/operations/index.mjs +tests/browser/output/* + diff --git a/.travis.yml b/.travis.yml index 5f039562..75f2054a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,21 @@ language: node_js node_js: - node +addons: + chrome: stable install: npm install before_script: - npm install -g grunt + - export NODE_OPTIONS=--max_old_space_size=2048 script: - grunt lint - grunt test - grunt docs - grunt node - grunt prod --msg="$COMPILE_MSG" + - xvfb-run --server-args="-screen 0 1200x800x24" grunt testui before_deploy: + - grunt exec:sitemap - grunt copy:ghPages deploy: - provider: pages @@ -22,7 +27,7 @@ deploy: repo: gchq/CyberChef branch: master - provider: releases - skip_cleaup: true + skip_cleanup: true api_key: secure: "HV1WSKv4l/0Y2bKKs1iBJocBcmLj08PCRUeEM/jTwA4jqJ8EiLHWiXtER/D5sEg2iibRVKd2OQjfrmS6bo4AiwdeVgAKmv0FtS2Jw+391N8Nd5AkEANHa5Om/IpHLTL2YRAjpJTsDpY72bMUTJIwjQA3TFJkgrpOw6KYfohOcgbxLpZ4XuNJRU3VL4Hsxdv5V9aOVmfFOmMOVPQlakXy7NgtW5POp1f2WJwgcZxylkR1CjwaqMyXmSoVl46pyH3tr5+dptsQoKSGdi6sIHGA60oDotFPcm+0ifa47wZw+vapuuDi4tdNxhrHGaDMG8xiE0WFDHwQUDlk2/+W7j9SEX0H3Em7us371JXRp56EDwEcDa34VpVkC6i8HGcHK55hnxVbMZXGf3qhOFD8wY7qMbjMRvIpucrMHBi86OfkDfv0vDj2LyvIl5APj/AX50BrE0tfH1MZbH26Jkx4NdlkcxQ14GumarmUqfmVvbX/fsoA6oUuAAE9ZgRRi3KHO4wci6KUcRfdm+XOeUkaBFsL86G3EEYIvrtBTuaypdz+Cx7nd1iPZyWMx5Y1gXnVzha4nBdV4+7l9JIsFggD8QVpw2uHXQiS1KXFjOeqA3DBD8tjMB7q26Fl2fD3jkOo4BTbQ2NrRIZUu/iL+fOmMPsyMt2qulB0yaSBCfkbEq8xrUA=" file: @@ -35,8 +40,14 @@ deploy: skip_cleanup: true email: "n1474335@gmail.com" api_key: - secure: "Z3FK6bm4RfQEIRXZ1lBNzQkVIoHpivThr9U+XBHmsBgIfdrK/XUnzs/slugo+NIz8nPiGmMx4gxyJonBCLHDGb1ysky2aEWTl26c0teaF4DeQEjWC1ZaGzv8MV1/GkUamnr1qouXjyUhyEAp33rd8ccN9Rq3QNYB/qLDcA9/FCme7JCW6sCd4zWO0LGEYMJEMc2FzAUkqhqsI05hegGhSDgKXRn5PmLARek4yHD+Hx7pstaTeQIy0WoGJjdzoB3iJIMmo/hWZGzZafktUOh223c5qzx4zMpDRNmMngBUw6R94nKd4KvplYRgB87Y3L/aiVU4CF+axwLmK8RPaC1wbJnlHf06zxHPdiFmsY/zKPpNel+nOnxzRrF5l2KMU4TU6gug3s9Jnzp9T5UMfhp0jW3YkxHGeuOPOeE1i0lTUWUGWrPHLQquAhLfkr2zxaU4ETk/y85hq9W4LAy0ENEDVXX2jP7FnI4Z1fdpmljpmVNJR+outPg6t+Coqgvil7v7XpMtDm8lKQanVYuxwmkb/ncOWFRWuM2j5zIEg3CHnFDcJ9bYrfKRg0b0tb/2BWD14pQnV76goVwzJQYVzdPc8TKIYJw2BZ1Nh9c0iruQVebe/6l1FX9fDCkz8VMmltni61/LxZrf8y0NT1YaU1raeNY2dH5UWvEa9p72FPMI6Eg=" + secure: "UnDQL3Kh+GK2toL0TK3FObO0ujVssU3Eg4BBuYdjwLB81GhiGE5/DTh7THdZPOpbLo6wQeOwfZDuMeKC1OU+0Uf4NsdYFu1aq6xMO20qBQ4qUfgsyiK4Qgywj9gk0p1+OFZdGAZ/j1CNRAaF71XQIY6iV84c+SO4WoizXYrNT0Jh4sr2DA4/97G2xmJtPi0qOzYrJ09R56ZUozmqeik5G0pMRIuJRbpjS/7bZXV+N7WV0ombZc9RkUaetbabEVOLQ+Xx5YAIVq+VuEeMe9VBSnxY/FfCLmy1wJsjGzpLCyBI9nbrG4nw8Wgc2m8NfK9rcpIvBTGner9r2j60NVDkZ8kLZPrqXhq6AZMwa+oz6K5UQCqRo2RRQzSGwXxg67HY5Tcq+oNmjd+DqpPg4LZ3eGlluyP5XfG+hpSr9Ya4d8q8SrUWLxkoLHI6ZKMtoKFbTCSSQPiluW5hsZxjz3yDkkjsJw64M/EM8UyJrgaXqDklQu+7rBGKLfsK6os7RDiqjBWpQ7gwpo8HvY0O8yqEAabPz+QGkanpjcCOZCXFbSkzWxYy37RMAPu88iINVZVlZE4l+WJenCpZY95ueyy0mG9cyMSzVRPyX6A+/n4H6VMFPFjpGDLTD588ACEjY1lmHfS/eXwXJcgqPPD2gW0XdRdUheU/ssqlfCfGWQMTDXs=" on: tags: true branch: master - +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/83c143a6822e218d5b34 + on_success: change + on_failure: always + on_start: never diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..2f3eca29 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,194 @@ +# Changelog +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). + + +### [8.24.0] - 2019-02-08 +- 'DNS over HTTPS' operation added [@h345983745] | [#489] + +### [8.23.1] - 2019-01-18 +- 'Convert co-ordinate format' operation added [@j433866] | [#476] + +### [8.23.0] - 2019-01-18 +- 'YARA Rules' operation added [@artemisbot] | [#468] + +### [8.22.0] - 2019-01-10 +- 'Subsection' operation added [@j433866] | [#467] + +### [8.21.0] - 2019-01-10 +- 'To Case Insensitive Regex' and 'From Case Insensitive Regex' operations added [@masq] | [#461] + +### [8.20.0] - 2019-01-09 +- 'Generate Lorem Ipsum' operation added [@klaxon1] | [#455] + +### [8.19.0] - 2018-12-30 +- UI test suite added to confirm that the app loads correctly in a reasonable time and that various operations from each module can be run [@n1474335] | [#458] + +### [8.18.0] - 2018-12-26 +- 'Split Colour Channels' operation added [@artemisbot] | [#449] + +### [8.17.0] - 2018-12-25 +- 'Generate QR Code' and 'Parse QR Code' operations added [@j433866] | [#448] + +### [8.16.0] - 2018-12-19 +- 'Play Media' operation added [@anthony-arnold] | [#446] + +### [8.15.0] - 2018-12-18 +- 'Text Encoding Brute Force' operation added [@Cynser] | [#439] + +### [8.14.0] - 2018-12-18 +- 'To Base62' and 'From Base62' operations added [@tcode2k16] | [#443] + +### [8.13.0] - 2018-12-15 +- 'A1Z26 Cipher Encode' and 'A1Z26 Cipher Decode' operations added [@jarmovanlenthe] | [#441] + +### [8.12.0] - 2018-11-21 +- 'Citrix CTX1 Encode' and 'Citrix CTX1 Decode' operations added [@bwhitn] | [#428] + +### [8.11.0] - 2018-11-13 +- 'CSV to JSON' and 'JSON to CSV' operations added [@n1474335] | [#277] + +### [8.10.0] - 2018-11-07 +- 'Remove Diacritics' operation added [@klaxon1] | [#387] + +### [8.9.0] - 2018-11-07 +- 'Defang URL' operation added [@arnydo] | [#394] + +### [8.8.0] - 2018-10-10 +- 'Parse TLV' operation added [@GCHQ77703] | [#351] + +### [8.7.0] - 2018-08-31 +- 'JWT Sign', 'JWT Verify' and 'JWT Decode' operations added [@GCHQ77703] | [#348] + +### [8.6.0] - 2018-08-29 +- 'To Geohash' and 'From Geohash' operations added [@GCHQ77703] | [#344] + +### [8.5.0] - 2018-08-23 +- 'To Braille' and 'From Braille' operations added [@n1474335] | [#255] + +### [8.4.0] - 2018-08-23 +- 'To Base85' and 'From Base85' operations added [@PenguinGeorge] | [#340] + +### [8.3.0] - 2018-08-21 +- 'To MessagePack' and 'From MessagePack' operations added [@artemisbot] | [#338] + +### [8.2.0] - 2018-08-21 +- Information links added to most operations, accessible in the description popover [@PenguinGeorge] | [#298] + +### [8.1.0] - 2018-08-19 +- 'Dechunk HTTP response' operation added [@sevzero] | [#311] + +## [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] +- A script has been added to aid in the creation of new operations by running `npm run newop` [@n1474335] | [#284] +- 'Magic' operation added - [automated detection of encoded data](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) [@n1474335] | [#239] +- UI updated to use [Bootstrap Material Design](https://fezvrasta.github.io/bootstrap-material-design/) [@n1474335] | [#248] +- `JSON`, `File` and `List` Dish types added [@n1474335] | [#284] +- `OperationError` type added for better handling of errors thrown by operations [@d98762625] | [#296] +- A `present()` method has been added, allowing operations to pass machine-friendly data to subsequent operations whilst presenting human-friendly data to the user [@n1474335] | [#284] +- Set operations added [@d98762625] | [#281] +- 'To Table' operation added [@JustAnotherMark] | [#294] +- 'Haversine distance' operation added [@Dachande663] | [#325] +- Started keeping a changelog [@n1474335] + +## [7.0.0] - 2017-12-28 +- Added support for loading, processing and downloading files up to 500MB [@n1474335] | [#224] + +## [6.0.0] - 2017-09-19 +- Threading support added. All recipe processing moved into a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to increase performance and to allow long-running operations to be cancelled [@n1474335] | [#173] +- Module system created so that operations relying on large libraries can be downloaded separately as required, reducing the initial loading time for the app [@n1474335] | [#173] + +## [5.0.0] - 2017-03-30 +- Webpack build process configured with Babel transpilation and ES6 imports and exports [@n1474335] | [#95] + +## [4.0.0] - 2016-11-28 +- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306) + + + +[8.24.0]: https://github.com/gchq/CyberChef/releases/tag/v8.24.0 +[8.23.1]: https://github.com/gchq/CyberChef/releases/tag/v8.23.1 +[8.23.0]: https://github.com/gchq/CyberChef/releases/tag/v8.23.0 +[8.22.0]: https://github.com/gchq/CyberChef/releases/tag/v8.22.0 +[8.21.0]: https://github.com/gchq/CyberChef/releases/tag/v8.21.0 +[8.20.0]: https://github.com/gchq/CyberChef/releases/tag/v8.20.0 +[8.19.0]: https://github.com/gchq/CyberChef/releases/tag/v8.19.0 +[8.18.0]: https://github.com/gchq/CyberChef/releases/tag/v8.18.0 +[8.17.0]: https://github.com/gchq/CyberChef/releases/tag/v8.17.0 +[8.16.0]: https://github.com/gchq/CyberChef/releases/tag/v8.16.0 +[8.15.0]: https://github.com/gchq/CyberChef/releases/tag/v8.15.0 +[8.14.0]: https://github.com/gchq/CyberChef/releases/tag/v8.14.0 +[8.13.0]: https://github.com/gchq/CyberChef/releases/tag/v8.13.0 +[8.12.0]: https://github.com/gchq/CyberChef/releases/tag/v8.12.0 +[8.11.0]: https://github.com/gchq/CyberChef/releases/tag/v8.11.0 +[8.10.0]: https://github.com/gchq/CyberChef/releases/tag/v8.10.0 +[8.9.0]: https://github.com/gchq/CyberChef/releases/tag/v8.9.0 +[8.8.0]: https://github.com/gchq/CyberChef/releases/tag/v8.8.0 +[8.7.0]: https://github.com/gchq/CyberChef/releases/tag/v8.7.0 +[8.6.0]: https://github.com/gchq/CyberChef/releases/tag/v8.6.0 +[8.5.0]: https://github.com/gchq/CyberChef/releases/tag/v8.5.0 +[8.4.0]: https://github.com/gchq/CyberChef/releases/tag/v8.4.0 +[8.3.0]: https://github.com/gchq/CyberChef/releases/tag/v8.3.0 +[8.2.0]: https://github.com/gchq/CyberChef/releases/tag/v8.2.0 +[8.1.0]: https://github.com/gchq/CyberChef/releases/tag/v8.1.0 +[8.0.0]: https://github.com/gchq/CyberChef/releases/tag/v8.0.0 +[7.0.0]: https://github.com/gchq/CyberChef/releases/tag/v7.0.0 +[6.0.0]: https://github.com/gchq/CyberChef/releases/tag/v6.0.0 +[5.0.0]: https://github.com/gchq/CyberChef/releases/tag/v5.0.0 +[4.0.0]: https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306 + +[@n1474335]: https://github.com/n1474335 +[@d98762625]: https://github.com/d98762625 +[@j433866]: https://github.com/j433866 +[@GCHQ77703]: https://github.com/GCHQ77703 +[@h345983745]: https://github.com/h345983745 +[@artemisbot]: https://github.com/artemisbot +[@picapi]: https://github.com/picapi +[@Dachande663]: https://github.com/Dachande663 +[@JustAnotherMark]: https://github.com/JustAnotherMark +[@sevzero]: https://github.com/sevzero +[@PenguinGeorge]: https://github.com/PenguinGeorge +[@arnydo]: https://github.com/arnydo +[@klaxon1]: https://github.com/klaxon1 +[@bwhitn]: https://github.com/bwhitn +[@jarmovanlenthe]: https://github.com/jarmovanlenthe +[@tcode2k16]: https://github.com/tcode2k16 +[@Cynser]: https://github.com/Cynser +[@anthony-arnold]: https://github.com/anthony-arnold +[@masq]: https://github.com/masq + +[#95]: https://github.com/gchq/CyberChef/pull/299 +[#173]: https://github.com/gchq/CyberChef/pull/173 +[#224]: https://github.com/gchq/CyberChef/pull/224 +[#239]: https://github.com/gchq/CyberChef/pull/239 +[#248]: https://github.com/gchq/CyberChef/pull/248 +[#255]: https://github.com/gchq/CyberChef/issues/255 +[#277]: https://github.com/gchq/CyberChef/issues/277 +[#281]: https://github.com/gchq/CyberChef/pull/281 +[#284]: https://github.com/gchq/CyberChef/pull/284 +[#294]: https://github.com/gchq/CyberChef/pull/294 +[#296]: https://github.com/gchq/CyberChef/pull/296 +[#298]: https://github.com/gchq/CyberChef/pull/298 +[#311]: https://github.com/gchq/CyberChef/pull/311 +[#325]: https://github.com/gchq/CyberChef/pull/325 +[#338]: https://github.com/gchq/CyberChef/pull/338 +[#340]: https://github.com/gchq/CyberChef/pull/340 +[#344]: https://github.com/gchq/CyberChef/pull/344 +[#348]: https://github.com/gchq/CyberChef/pull/348 +[#351]: https://github.com/gchq/CyberChef/pull/351 +[#387]: https://github.com/gchq/CyberChef/pull/387 +[#394]: https://github.com/gchq/CyberChef/pull/394 +[#428]: https://github.com/gchq/CyberChef/pull/428 +[#439]: https://github.com/gchq/CyberChef/pull/439 +[#441]: https://github.com/gchq/CyberChef/pull/441 +[#443]: https://github.com/gchq/CyberChef/pull/443 +[#446]: https://github.com/gchq/CyberChef/pull/446 +[#448]: https://github.com/gchq/CyberChef/pull/448 +[#449]: https://github.com/gchq/CyberChef/pull/449 +[#455]: https://github.com/gchq/CyberChef/pull/455 +[#458]: https://github.com/gchq/CyberChef/pull/458 +[#461]: https://github.com/gchq/CyberChef/pull/461 +[#467]: https://github.com/gchq/CyberChef/pull/467 +[#468]: https://github.com/gchq/CyberChef/pull/468 +[#476]: https://github.com/gchq/CyberChef/pull/476 +[#489]: https://github.com/gchq/CyberChef/pull/489 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..1fadd9c4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at oss@gchq.gov.uk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Gruntfile.js b/Gruntfile.js index c3b0e49f..11b77452 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,7 +1,20 @@ +"use strict"; + const webpack = require("webpack"); -const ExtractTextPlugin = require("extract-text-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); +const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; +const NodeExternals = require("webpack-node-externals"); const Inliner = require("web-resource-inliner"); +const glob = require("glob"); +const path = require("path"); + +/** + * Grunt configuration for building the app in various formats. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ module.exports = function (grunt) { grunt.file.defaultEncoding = "utf8"; @@ -10,15 +23,19 @@ module.exports = function (grunt) { // Tasks grunt.registerTask("dev", "A persistent task which creates a development build whenever source files are modified.", - ["clean:dev", "webpack:webDev"]); + ["clean:dev", "clean:config", "exec:generateConfig", "concurrent:dev"]); grunt.registerTask("node", "Compiles CyberChef into a single NodeJS module.", - ["clean:node", "webpack:node", "chmod:build"]); + ["clean:node", "clean:config", "exec:generateConfig", "webpack:node", "chmod:build"]); grunt.registerTask("test", - "A task which runs all the tests in test/tests.", - ["clean:test", "webpack:tests", "execute:test"]); + "A task which runs all the operation tests in the tests directory.", + ["exec:generateConfig", "exec:opTests"]); + + grunt.registerTask("testui", + "A task which runs all the UI tests in the tests directory. The prod task must already have been run.", + ["connect:prod", "exec:browserTests"]); grunt.registerTask("docs", "Compiles documentation in the /docs directory.", @@ -26,7 +43,7 @@ module.exports = function (grunt) { grunt.registerTask("prod", "Creates a production-ready build. Use the --msg flag to add a compile message.", - ["eslint", "clean:prod", "webpack:webProd", "inline", "chmod"]); + ["eslint", "clean:prod", "clean:config", "exec:generateConfig", "webpack:web", "inline", "chmod"]); grunt.registerTask("default", "Lints the code base", @@ -34,8 +51,10 @@ module.exports = function (grunt) { grunt.registerTask("inline", "Compiles a production build of CyberChef into a single, portable web page.", - runInliner); + ["exec:generateConfig", "webpack:webInline", "runInliner", "clean:inlineScripts"]); + + grunt.registerTask("runInliner", runInliner); grunt.registerTask("doc", "docs"); grunt.registerTask("tests", "test"); grunt.registerTask("lint", "eslint"); @@ -47,35 +66,33 @@ module.exports = function (grunt) { grunt.loadNpmTasks("grunt-jsdoc"); grunt.loadNpmTasks("grunt-contrib-clean"); grunt.loadNpmTasks("grunt-contrib-copy"); + grunt.loadNpmTasks("grunt-contrib-watch"); grunt.loadNpmTasks("grunt-chmod"); grunt.loadNpmTasks("grunt-exec"); - grunt.loadNpmTasks("grunt-execute"); grunt.loadNpmTasks("grunt-accessibility"); + grunt.loadNpmTasks("grunt-concurrent"); + grunt.loadNpmTasks("grunt-contrib-connect"); // Project configuration const compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC", - banner = "/**\n" + - "* CyberChef - The Cyber Swiss Army Knife\n" + - "*\n" + - "* @copyright Crown Copyright 2016\n" + - "* @license Apache-2.0\n" + - "*\n" + - "* Copyright 2016 Crown Copyright\n" + - "*\n" + - '* Licensed under the Apache License, Version 2.0 (the "License");\n' + - "* you may not use this file except in compliance with the License.\n" + - "* You may obtain a copy of the License at\n" + - "*\n" + - "* http://www.apache.org/licenses/LICENSE-2.0\n" + - "*\n" + - "* Unless required by applicable law or agreed to in writing, software\n" + - '* distributed under the License is distributed on an "AS IS" BASIS,\n' + - "* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + - "* See the License for the specific language governing permissions and\n" + - "* limitations under the License.\n" + - "*/\n", - pkg = grunt.file.readJSON("package.json"); + pkg = grunt.file.readJSON("package.json"), + webpackConfig = require("./webpack.config.js"), + BUILD_CONSTANTS = { + COMPILE_TIME: JSON.stringify(compileTime), + COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""), + PKG_VERSION: JSON.stringify(pkg.version), + ENVIRONMENT_IS_WORKER: function() { + return typeof importScripts === "function"; + }, + ENVIRONMENT_IS_NODE: function() { + return typeof process === "object" && typeof require === "function"; + }, + ENVIRONMENT_IS_WEB: function() { + return typeof window === "object"; + } + }, + moduleEntryPoints = listEntryModules(); /** * Compiles a production build of CyberChef into a single, portable web page. @@ -104,23 +121,39 @@ module.exports = function (grunt) { }); } + /** + * Generates an entry list for all the modules. + */ + function listEntryModules() { + const entryModules = {}; + + glob.sync("./src/core/config/modules/*.mjs").forEach(file => { + const basename = path.basename(file); + if (basename !== "Default.mjs" && basename !== "OpModules.mjs") + entryModules[basename.split(".mjs")[0]] = path.resolve(file); + }); + + return entryModules; + } + grunt.initConfig({ clean: { dev: ["build/dev/*"], prod: ["build/prod/*"], - test: ["build/test/*"], node: ["build/node/*"], - docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico"], + config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"], + docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"], + inlineScripts: ["build/prod/scripts.js"], }, eslint: { options: { configFile: "./.eslintrc.json" }, - configs: ["Gruntfile.js"], - core: ["src/core/**/*.js", "!src/core/lib/**/*"], - web: ["src/web/**/*.js"], - node: ["src/node/**/*.js"], - tests: ["test/**/*.js"], + configs: ["*.{js,mjs}"], + core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"], + web: ["src/web/**/*.{js,mjs}"], + node: ["src/node/**/*.{js,mjs}"], + tests: ["tests/**/*.{js,mjs}"], }, jsdoc: { options: { @@ -133,7 +166,8 @@ module.exports = function (grunt) { all: { src: [ "src/**/*.js", - "!src/core/lib/**/*", + "src/**/*.mjs", + "!src/core/vendor/**/*" ], } }, @@ -150,103 +184,49 @@ module.exports = function (grunt) { } }, webpack: { - options: { - plugins: [ - new webpack.ProvidePlugin({ - $: "jquery", - jQuery: "jquery", - moment: "moment-timezone" - }), - new webpack.BannerPlugin({ - banner: banner, - raw: true, - entryOnly: true - }), - new webpack.DefinePlugin({ - COMPILE_TIME: JSON.stringify(compileTime), - COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || "") - }), - new ExtractTextPlugin("styles.css"), - ], - resolve: { - alias: { - jquery: "jquery/src/jquery" - } - }, - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - loader: "babel-loader?compact=false" - }, - { - test: /\.css$/, - use: ExtractTextPlugin.extract({ - use: [ - { loader: "css-loader?minimize" }, - { loader: "postcss-loader" }, - ] - }) - }, - { - test: /\.less$/, - use: ExtractTextPlugin.extract({ - use: [ - { loader: "css-loader?minimize" }, - { loader: "postcss-loader" }, - { loader: "less-loader" } - ] - }) - }, - { - test: /\.(ico|eot|ttf|woff|woff2)$/, - loader: "url-loader", - options: { - limit: 10000 + options: webpackConfig, + web: () => { + return { + mode: "production", + target: "web", + entry: Object.assign({ + main: "./src/web/index.js", + sitemap: "./src/web/static/sitemap.js" + }, moduleEntryPoints), + output: { + path: __dirname + "/build/prod", + globalObject: "this" + }, + resolve: { + alias: { + "./config/modules/OpModules": "./config/modules/Default" + } + }, + plugins: [ + new webpack.DefinePlugin(BUILD_CONSTANTS), + new HtmlWebpackPlugin({ + filename: "index.html", + template: "./src/web/html/index.html", + chunks: ["main"], + compileTime: compileTime, + version: pkg.version, + minify: { + removeComments: true, + collapseWhitespace: true, + minifyJS: true, + minifyCSS: true } - }, - { // First party images are saved as files to be cached - test: /\.(png|jpg|gif|svg)$/, - exclude: /node_modules/, - loader: "file-loader", - options: { - name: "images/[name].[ext]" - } - }, - { // Third party images are inlined - test: /\.(png|jpg|gif|svg)$/, - exclude: /web\/static/, - loader: "url-loader", - options: { - limit: 10000 - } - }, + }), + new BundleAnalyzerPlugin({ + analyzerMode: "static", + reportFilename: "BundleAnalyzerReport.html", + openAnalyzer: false + }), ] - }, - stats: { - children: false, - warningsFilter: /source-map/ - } + }; }, - webDev: { - target: "web", - entry: "./src/web/index.js", - output: { - filename: "scripts.js", - path: __dirname + "/build/dev" - }, - plugins: [ - new HtmlWebpackPlugin({ - filename: "index.html", - template: "./src/web/html/index.html", - compileTime: compileTime, - version: pkg.version, - }) - ], - watch: true - }, - webProd: { + webInline: { + mode: "production", target: "web", entry: "./src/web/index.js", output: { @@ -254,32 +234,14 @@ module.exports = function (grunt) { path: __dirname + "/build/prod" }, plugins: [ - new webpack.optimize.UglifyJsPlugin({ - compress: { - "screw_ie8": true, - "dead_code": true, - "unused": true, - "warnings": false - }, - comments: false, - }), - new HtmlWebpackPlugin({ // Main version - filename: "index.html", - template: "./src/web/html/index.html", - compileTime: compileTime, - version: pkg.version, - minify: { - removeComments: true, - collapseWhitespace: true, - minifyJS: true, - minifyCSS: true - } - }), - new HtmlWebpackPlugin({ // Inline version + new webpack.DefinePlugin(Object.assign({}, BUILD_CONSTANTS, { + INLINE: "true" + })), + new HtmlWebpackPlugin({ filename: "cyberchef.htm", template: "./src/web/html/index.html", compileTime: compileTime, - version: pkg.version, + version: pkg.version + "s", inline: true, minify: { removeComments: true, @@ -290,37 +252,104 @@ module.exports = function (grunt) { }), ] }, - tests: { - target: "node", - entry: "./test/index.js", - output: { - filename: "index.js", - path: __dirname + "/build/test" - } - }, node: { + mode: "production", target: "node", - entry: "./src/node/index.js", + entry: "./src/node/index.mjs", + externals: [NodeExternals()], output: { filename: "CyberChef.js", path: __dirname + "/build/node", library: "CyberChef", libraryTarget: "commonjs2" + }, + plugins: [ + new webpack.DefinePlugin(BUILD_CONSTANTS) + ] + } + }, + "webpack-dev-server": { + options: { + webpack: webpackConfig, + host: "0.0.0.0", + disableHostCheck: true, + overlay: true, + inline: false, + clientLogLevel: "error", + stats: { + children: false, + chunks: false, + modules: false, + entrypoints: false, + warningsFilter: [ + /source-map/, + /dependency is an expression/, + /export 'default'/ + ], + } + }, + start: { + webpack: { + mode: "development", + target: "web", + entry: Object.assign({ + main: "./src/web/index.js" + }, moduleEntryPoints), + resolve: { + alias: { + "./config/modules/OpModules": "./config/modules/Default" + } + }, + output: { + globalObject: "this", + }, + plugins: [ + new webpack.DefinePlugin(BUILD_CONSTANTS), + new HtmlWebpackPlugin({ + filename: "index.html", + template: "./src/web/html/index.html", + chunks: ["main"], + compileTime: compileTime, + version: pkg.version, + }) + ] + } + } + }, + connect: { + prod: { + options: { + port: 8000, + base: "build/prod/" } } }, copy: { ghPages: { options: { - process: function (content) { + process: function (content, srcpath) { // Add Google Analytics code to index.html - content = content.replace("", - grunt.file.read("src/web/static/ga.html") + ""); - return grunt.template.process(content); - } + if (srcpath.indexOf("index.html") >= 0) { + content = content.replace("", + grunt.file.read("src/web/static/ga.html") + ""); + return grunt.template.process(content, srcpath); + } else { + return content; + } + }, + noProcess: ["**", "!**/*.html"] }, - src: "build/prod/index.html", - dest: "build/prod/index.html" + files: [ + { + src: "build/prod/index.html", + dest: "build/prod/index.html" + }, + { + expand: true, + src: "docs/**", + dest: "build/prod/" + }, + ] } }, chmod: { @@ -337,6 +366,18 @@ module.exports = function (grunt) { src: ["docs/**/*", "docs/"] } }, + watch: { + config: { + files: ["src/core/operations/**/*", "!src/core/operations/index.mjs"], + tasks: ["exec:generateConfig"] + } + }, + concurrent: { + dev: ["watch:config", "webpack-dev-server:start"], + options: { + logConcurrentOutput: true + } + }, exec: { repoSize: { command: [ @@ -348,9 +389,24 @@ module.exports = function (grunt) { cleanGit: { command: "git gc --prune=now --aggressive" }, - }, - execute: { - test: "build/test/index.js" + sitemap: { + command: "node build/prod/sitemap.js > build/prod/sitemap.xml" + }, + generateConfig: { + command: [ + "echo '\n--- Regenerating config files. ---'", + "echo [] > src/core/config/OperationConfig.json", + "node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateOpsIndex.mjs", + "node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateConfig.mjs", + "echo '--- Config scripts finished. ---\n'" + ].join(";") + }, + opTests: { + command: "node --experimental-modules --no-warnings --no-deprecation tests/operations/index.mjs" + }, + browserTests: { + command: "./node_modules/.bin/nightwatch --env prod,inline" + } }, }); }; diff --git a/README.md b/README.md index 3e65f682..e6d835a8 100755 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ # CyberChef [![Build Status](https://travis-ci.org/gchq/CyberChef.svg?branch=master)](https://travis-ci.org/gchq/CyberChef) -[![npm](https://badge.fury.io/js/cyberchef.svg)](https://www.npmjs.com/package/cyberchef) -![](https://reposs.herokuapp.com/?path=gchq/CyberChef&color=brightgreen) +[![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) + #### *The Cyber Swiss Army Knife* -CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include creating hexdumps, simple encoding like XOR or Base64, more complex encryption like AES, DES and Blowfish, data compression and decompression, calculating hashes and checksums, IPv6 and X.509 parsing, and much more. +CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include simple encoding like XOR or Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more. -The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years. Every effort has been made to structure the code in a readable and extendable format, however it should be noted that the analyst is not a professional developer and the code has not been peer-reviewed for compliance with a formal specification. +The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years. ## Live demo @@ -23,10 +27,10 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur There are four main areas in CyberChef: - 1. The **input** box in the top right, where you can paste, type or drag the data you want to operate on. - 2. The **output** box in the bottom right, where the outcome of the specified processing will be displayed. + 1. The **input** box in the top right, where you can paste, type or drag the text or file you want to operate on. + 2. The **output** box in the bottom right, where the outcome of your processing will be displayed. 3. The **operations** list on the far left, where you can find all the operations that CyberChef is capable of in categorised lists, or by searching. - 4. The **recipe** area in the middle, where you drag the operations that you want to use and specify arguments and options. + 4. The **recipe** area in the middle, where you can drag the operations that you want to use and specify arguments and options. You can use as many operations as you like in simple or complex ways. Some examples are as follows: @@ -34,48 +38,58 @@ You can use as many operations as you like in simple or complex ways. Some examp - [Convert a date and time to a different time zone][3] - [Parse a Teredo IPv6 address][4] - [Convert data from a hexdump, then decompress][5] - - [Display multiple timestamps as full dates][6] - - [Carry out different operations on data of different types][7] + - [Decrypt and disassemble shellcode][6] + - [Display multiple timestamps as full dates][7] + - [Carry out different operations on data of different types][8] + - [Use parts of the input as arguments to operations][9] + - [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10] + - [Automagically detect several layers of nested encoding][12] ## Features - Drag and drop - Operations can be dragged in and out of the recipe list, or reorganised. - - Files can be dragged over the input box to load them directly. + - Files up to 500MB can be dragged over the input box to load them directly into the browser. - Auto Bake - - Whenever you modify the input or the recipe, CyberChef will automatically “bake” for you and produce the output immediately. + - Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately. - This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance). - - If any bake takes longer than 200 milliseconds, auto bake will be switched off automatically to prevent further performance issues. + - Automated encoding detection + - CyberChef uses [a number of techniques](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) to attempt to automatically detect which encodings your data is under. If it finds a suitable operation which can make sense of your data, it displays the 'magic' icon in the Output field which you can click to decode your data. - Breakpoints - You can set breakpoints on any operation in your recipe to pause execution before running it. - You can also step through the recipe one operation at a time to see what the data looks like at each stage. - Save and load recipes - - If you come up with an awesome recipe that you know you’ll want to use again, just click save and add it to your local storage. It'll be waiting for you next time you visit CyberChef. - - You can also copy a URL which includes your recipe and input which can be shared with others. + - If you come up with an awesome recipe that you know you’ll want to use again, just click "Save recipe" and add it to your local storage. It'll be waiting for you next time you visit CyberChef. + - You can also copy the URL, which includes your recipe and input, to easily share it with others. - Search - If you know the name of the operation you want or a word associated with it, start typing it into the search field and any matching operations will immediately be shown. - Highlighting - - When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][8]). + - When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][11]). - Save to file and load from file - - You can save the output to a file at any time or load a file by dragging and dropping it into the input field (note that files larger than about 500kb may cause your browser to hang or even crash due to the way that browsers handle large amounts of textual data). + - You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 500MB are supported (depending on your browser), however some operations may take a very long time to run over this much data. - CyberChef is entirely client-side - - It should be noted that none of your input or recipe configuration is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer. - - Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your desktop. + - It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer. + - Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your local machine. ## Browser support -CyberChef is built to support Google Chrome 40+, Mozilla Firefox 35+ and Microsoft Edge 14+. +CyberChef is built to support + + - Google Chrome 40+ + - Mozilla Firefox 35+ + - Microsoft Edge 14+ ## Contributing -An installation walkthrough, how-to guides for adding new operations, descriptions of the repository structure, available data types and coding conventions can all be found in the project [wiki pages](https://github.com/gchq/CyberChef/wiki). +Contributing a new operation to CyberChef is super easy! There is a quickstart script which will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation. + +An installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the project [wiki pages](https://github.com/gchq/CyberChef/wiki). - - Sign the [GCHQ Contributor Licence Agreement](https://github.com/gchq/Gaffer/wiki/GCHQ-OSS-Contributor-License-Agreement-V1.0) - Push your changes to your fork. - - Submit a pull request. + - Submit a pull request. If you are doing this for the first time, you will be prompted to sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef) via the CLA assistant on the pull request. This will also ask whether you are happy for GCHQ to contact you about a token of thanks for your contribution, or about job opportunities at GCHQ. ## Licencing @@ -84,10 +98,14 @@ CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/lice [1]: https://gchq.github.io/CyberChef - [2]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22From%20Base64%22%2C%22args%22%3A%5B%22A-Za-z0-9%2B%2F%3D%22%2Ctrue%5D%7D%5D&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1 - [3]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22Translate%20DateTime%20Format%22%2C%22args%22%3A%5B%22Standard%20date%20and%20time%22%2C%22DD%2FMM%2FYYYY%20HH%3Amm%3Ass%22%2C%22UTC%22%2C%22dddd%20Do%20MMMM%20YYYY%20HH%3Amm%3Ass%20Z%20z%22%2C%22Australia%2FQueensland%22%5D%7D%5D&input=MTUvMDYvMjAxNSAyMDo0NTowMA - [4]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22Parse%20IPv6%20address%22%2C%22args%22%3A%5B%5D%7D%5D&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy - [5]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22From%20Hexdump%22%2C%22args%22%3A%5B%5D%7D%2C%7B%22op%22%3A%22Gunzip%22%2C%22args%22%3A%5B%5D%7D%5D&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu%2Fy7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb%2F3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw - [6]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22Fork%22%2C%22args%22%3A%5B%22%5C%5Cn%22%2C%22%5C%5Cn%22%5D%7D%2C%7B%22op%22%3A%22From%20UNIX%20Timestamp%22%2C%22args%22%3A%5B%22Seconds%20(s)%22%5D%7D%5D&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA - [7]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22Fork%22%2C%22args%22%3A%5B%22%5C%5Cn%22%2C%22%5C%5Cn%22%5D%7D%2C%7B%22op%22%3A%22Conditional%20Jump%22%2C%22args%22%3A%5B%221%22%2C%222%22%2C%2210%22%5D%7D%2C%7B%22op%22%3A%22To%20Hex%22%2C%22args%22%3A%5B%22Space%22%5D%7D%2C%7B%22op%22%3A%22Return%22%2C%22args%22%3A%5B%5D%7D%2C%7B%22op%22%3A%22To%20Base64%22%2C%22args%22%3A%5B%22A-Za-z0-9%2B%2F%3D%22%5D%7D%5D&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA - [8]: https://gchq.github.io/CyberChef/?recipe=%5B%7B%22op%22%3A%22XOR%22%2C%22args%22%3A%5B%7B%22option%22%3A%22Hex%22%2C%22string%22%3A%223a%22%7D%2Cfalse%2Cfalse%5D%7D%2C%7B%22op%22%3A%22To%20Hexdump%22%2C%22args%22%3A%5B%2216%22%2Cfalse%2Cfalse%5D%7D%5D&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4 + [2]: https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1 + [3]: https://gchq.github.io/CyberChef/#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA + [4]: https://gchq.github.io/CyberChef/#recipe=Parse_IPv6_address()&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy + [5]: https://gchq.github.io/CyberChef/#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw + [6]: https://gchq.github.io/CyberChef/#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ + [7]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA + [8]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA + [9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ + [10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg + [11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4 + [12]: https://gchq.github.io/CyberChef/#recipe=Magic(3,false,false)&input=V1VhZ3dzaWFlNm1QOGdOdENDTFVGcENwQ0IyNlJtQkRvREQ4UGFjZEFtekF6QlZqa0syUXN0RlhhS2hwQzZpVVM3UkhxWHJKdEZpc29SU2dvSjR3aGptMWFybTg2NHFhTnE0UmNmVW1MSHJjc0FhWmM1VFhDWWlmTmRnUzgzZ0RlZWpHWDQ2Z2FpTXl1QlY2RXNrSHQxc2NnSjg4eDJ0TlNvdFFEd2JHWTFtbUNvYjJBUkdGdkNLWU5xaU45aXBNcTFaVTFtZ2tkYk51R2NiNzZhUnRZV2hDR1VjOGc5M1VKdWRoYjhodHNoZVpud1RwZ3FoeDgzU1ZKU1pYTVhVakpUMnptcEM3dVhXdHVtcW9rYmRTaTg4WXRrV0RBYzFUb291aDJvSDRENGRkbU5LSldVRHBNd21uZ1VtSzE0eHdtb21jY1BRRTloTTE3MkFQblNxd3hkS1ExNzJSa2NBc3lzbm1qNWdHdFJtVk5OaDJzMzU5d3I2bVMyUVJQ diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 00000000..9c0329d3 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,24 @@ +module.exports = function(api) { + api.cache.forever(); + + return { + "presets": [ + ["@babel/preset-env", { + "targets": { + "chrome": 40, + "firefox": 35, + "edge": 14, + "node": "6.5" + }, + "modules": false, + "useBuiltIns": "entry" + }] + ], + "plugins": [ + "babel-plugin-syntax-dynamic-import", + ["babel-plugin-transform-builtin-extend", { + "globals": ["Error"] + }] + ] + }; +}; diff --git a/docs/favicon.ico b/docs/favicon.ico index d67a4081..fa2deb03 100755 Binary files a/docs/favicon.ico and b/docs/favicon.ico differ diff --git a/docs/jsdoc.conf.json b/docs/jsdoc.conf.json index 3c247edc..36e9611b 100755 --- a/docs/jsdoc.conf.json +++ b/docs/jsdoc.conf.json @@ -19,7 +19,7 @@ "outputSourcePath": true, "dateFormat": "ddd MMM Do YYYY", "sort": false, - "logoFile": "../build/prod/images/cyberchef-32x32.png", + "logoFile": "cyberchef-32x32.png", "cleverLinks": false, "monospaceLinks": false, "protocol": "html://", diff --git a/nightwatch.json b/nightwatch.json new file mode 100644 index 00000000..e9c1ebef --- /dev/null +++ b/nightwatch.json @@ -0,0 +1,34 @@ +{ + "src_folders": ["tests/browser"], + "output_folder": "tests/browser/output", + + "test_settings": { + + "default": { + "launch_url": "http://localhost:8080", + "webdriver": { + "start_process": true, + "server_path": "./node_modules/.bin/chromedriver", + "port": 9515, + "log_path": false + }, + "desiredCapabilities": { + "browserName": "chrome" + } + }, + + "dev": { + "launch_url": "http://localhost:8080" + }, + + "prod": { + "launch_url": "http://localhost:8000/index.html" + }, + + "inline": { + "launch_url": "http://localhost:8000/cyberchef.htm" + } + + } +} + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..55ad6303 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13999 @@ +{ + "name": "cyberchef", + "version": "8.24.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.2.tgz", + "integrity": "sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helpers": "^7.2.0", + "@babel/parser": "^7.2.2", + "@babel/template": "^7.2.2", + "@babel/traverse": "^7.2.2", + "@babel/types": "^7.2.2", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.10", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.2.tgz", + "integrity": "sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==", + "dev": true, + "requires": { + "@babel/types": "^7.2.2", + "jsesc": "^2.5.1", + "lodash": "^4.17.10", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-call-delegate": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz", + "integrity": "sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-define-map": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", + "integrity": "sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz", + "integrity": "sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.2.2.tgz", + "integrity": "sha512-YRD7I6Wsv+IHuTPkAmAS4HhY0dkPobgLftHp0cRGZSdrRvmZY8rFvae/GVu3bD00qscuvK3WPHB3YdNpBXUqrA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/template": "^7.2.2", + "@babel/types": "^7.2.2", + "lodash": "^4.17.10" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0.tgz", + "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz", + "integrity": "sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.2.3", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "@babel/helpers": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.2.0.tgz", + "integrity": "sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==", + "dev": true, + "requires": { + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.5", + "@babel/types": "^7.2.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.3.tgz", + "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-1L5mWLSvR76XYUQJXkd/EEQgjq8HHRP6lQuZTTg0VA4tTGPpGemmCdAfQIz1rzEuWAm+ecP8PyyEm30jC1eQCg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz", + "integrity": "sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.2.0" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz", + "integrity": "sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz", + "integrity": "sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.2.tgz", + "integrity": "sha512-gEZvgTy1VtcDOaQty1l10T3jQmJKlNVxLDCs+3rCVPr6nMkODLELxViq5X9l+rfxbie3XrfrMCYYY6eX3aOcOQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.1.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", + "dev": true + } + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.2.0.tgz", + "integrity": "sha512-coVO2Ayv7g0qdDbrNiadE4bU7lvCd9H539m2gMknyVjjMdwF/iCOM7R+E8PkntoqLkltO0rk+3axhpp/0v68VQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz", + "integrity": "sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", + "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz", + "integrity": "sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz", + "integrity": "sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", + "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz", + "integrity": "sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz", + "integrity": "sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz", + "integrity": "sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", + "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz", + "integrity": "sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA==", + "dev": true, + "requires": { + "@babel/helper-call-delegate": "^7.1.0", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz", + "integrity": "sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw==", + "dev": true, + "requires": { + "regenerator-transform": "^0.13.3" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz", + "integrity": "sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz", + "integrity": "sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + } + }, + "@babel/polyfill": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.2.5.tgz", + "integrity": "sha512-8Y/t3MWThtMLYr0YNC/Q76tqN1w30+b0uQMeFUYauG2UGTR19zyUtFrAzT23zNtBxPp+LbE5E/nwV/q/r3y6ug==", + "requires": { + "core-js": "^2.5.7", + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "@babel/preset-env": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.2.3.tgz", + "integrity": "sha512-AuHzW7a9rbv5WXmvGaPX7wADxFkZIqKlbBh1dmZUQp4iwiPpkE/Qnrji6SC4UQCQzvWY/cpHET29eUhXS9cLPw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.2.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.2.0", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.2.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.2.0", + "@babel/plugin-transform-classes": "^7.2.0", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.2.0", + "@babel/plugin-transform-dotall-regex": "^7.2.0", + "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.2.0", + "@babel/plugin-transform-function-name": "^7.2.0", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.2.0", + "@babel/plugin-transform-modules-systemjs": "^7.2.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-new-target": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-parameters": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.2.0", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.2.0", + "browserslist": "^4.3.4", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.3.0" + } + }, + "@babel/runtime-corejs2": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.3.1.tgz", + "integrity": "sha512-YpO13776h3e6Wy8dl2J8T9Qwlvopr+b4trCEhHE+yek6yIqV8sx6g3KozdHMbXeBpjosbPi+Ii5Z7X9oXFHUKA==", + "requires": { + "core-js": "^2.5.7", + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "@babel/template": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2" + } + }, + "@babel/traverse": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", + "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.2.3", + "@babel/types": "^7.2.2", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.10" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz", + "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, + "@jimp/bmp": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.6.0.tgz", + "integrity": "sha512-zZOcVT1zK/1QL5a7qirkzPPgDKB1ianER7pBdpR2J71vx/g8MnrPbL3h/jEVPxjdci2Hph/VWhc/oLBtTbqO8w==", + "requires": { + "@jimp/utils": "^0.6.0", + "bmp-js": "^0.1.0", + "core-js": "^2.5.7" + } + }, + "@jimp/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.6.0.tgz", + "integrity": "sha512-ngAkyCLtX7buc2QyFy0ql/j4R2wGYQVsVhW2G3Y0GVAAklRIFIUYpyNKrqs228xA8f2O6XStbDStFlYkt7uNeg==", + "requires": { + "@jimp/utils": "^0.6.0", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "core-js": "^2.5.7", + "exif-parser": "^0.1.12", + "file-type": "^9.0.0", + "load-bmfont": "^1.3.1", + "mkdirp": "0.5.1", + "phin": "^2.9.1", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.4.1" + }, + "dependencies": { + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + } + } + }, + "@jimp/custom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.6.0.tgz", + "integrity": "sha512-+YZIWhf03Rfbi+VPbHomKInu3tcntF/aij/JrIJd1QZq13f8m3mRNxakXupiL18KH0C8BPNDk8RiwFX+HaOw3A==", + "requires": { + "@jimp/core": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/gif": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.6.0.tgz", + "integrity": "sha512-aWQ02P0ymTN1eh0BVsY+84wMdb/QeiVpCNQZl9y50cRnpuMM8TTmF/ZdCEBDiTRFcwXzHsqBXcLwEcYp3X2lTw==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7", + "omggif": "^1.0.9" + } + }, + "@jimp/jpeg": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.6.0.tgz", + "integrity": "sha512-quYb+lM4h57jQvr2q9dEIkc0laTljws4dunIdFhJRfa5UlNL5mHInk8h5MxyALo0mZdT07TAcxiDHw5QXZ28JQ==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7", + "jpeg-js": "^0.3.4" + } + }, + "@jimp/plugin-blit": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.6.0.tgz", + "integrity": "sha512-LjiCa+8OT2fgmvBpZt0ogurg/eu5kB8ZFWDRwHPcf8i+058sZC20dar/qrjVd5Knssq4ynjb5oAHsGuJq16Rqw==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-blur": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.6.0.tgz", + "integrity": "sha512-/vjGcEiHda6OLTCYqXPFkfSTbL+RatZoGcp1vewcWqChUccn9QVINTlxB7nEI/3Nb/i7KdhOPNEQh1k6q6QXsw==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-color": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.6.0.tgz", + "integrity": "sha512-mvDeAwN8ZpDkOaABMJ0w9zUzo9OOtu1qvvPkSirXDTMiXt1nsbfz8BoeoD7nU2MFhQj5MiGjH65UDnsH5ZzYuw==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7", + "tinycolor2": "^1.4.1" + } + }, + "@jimp/plugin-contain": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.6.0.tgz", + "integrity": "sha512-gPHnoQkDztMbvnTVo01BaMoM/hhDJdeJ7FRToD4p4Qvdor4V0I6NXtjOeUPXfD94miTgh/UTyJDqeG4GZzi4sA==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-cover": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.6.0.tgz", + "integrity": "sha512-iv9lA2v3qv+x3eaTThtyzFg+hO8/pSnM8NBymC5OlpSJnR54aWi7BVFXLJAF27T4EZyXko432PVul2IdY3BEPw==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-crop": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.6.0.tgz", + "integrity": "sha512-YftdmFZ2YnZDYyBulkStCt2MZbKKfbjytkE+6i3Djk2b/Rfryg5xjgzVnAumCRQJhVPukexrnc2V7KKbEgx7mQ==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-displace": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.6.0.tgz", + "integrity": "sha512-kkva5Fy3r7J7QmiqYQ5c9NeUKKkN7+KSfCGsZ6tkRHK4REMIXhQO/OnJN8XG6RReV29O6QykdyeTXDiHUDiROw==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-dither": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.6.0.tgz", + "integrity": "sha512-ILSG7bl3SOqmcIa9C4nBvs0h0E0ObnMbeKWUZiNuz6i0OAlbxryiIfU4j0UVQD5XqT9ksC5mviVNrvOMw4SZLw==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-flip": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.6.0.tgz", + "integrity": "sha512-MXGGwABjERvfqVadEzJuVAmbsEQfjxXD0O/mMBegU1Qh7/JmnKAVplQCnojsMPxUdao/FKZjQqOnB/j4LLJtOQ==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-gaussian": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.6.0.tgz", + "integrity": "sha512-RUsBCyj6Ukxgn/TU8v6c6WRbSFqKM0iknLVqDkKIuiOyJB7ougv66fqomh/i/h3ihIkEnf50BuO0c3ovrczfvw==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-invert": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.6.0.tgz", + "integrity": "sha512-zTCqK8el6eqcNKAxw0y57gHBFgxygI5iM8dQDPyqsvVWO71i8XII7ubnJhEvPPN7vhIKlOSnS9XXglezvJoX4Q==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-mask": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.6.0.tgz", + "integrity": "sha512-zkZVqAA7lxWhkn5EbPjBQ6tPluYIGfLMSX4kD1gksj+MVJJnVAd459AVuEXCvkUvv4wG5AlH8m6ve5NZj9vvxw==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-normalize": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.6.0.tgz", + "integrity": "sha512-7bNGT+S0rw9gvmxpkNsA19JSqBZYFrAn9QhEmoN4HIimdKtJaoLJh/GnxrPuOBLuv1IPJntoTOOWvOmfrQ6/ww==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-print": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.6.0.tgz", + "integrity": "sha512-kXNHYo7bGQiMZkUqhCvm6OomjJtZnLGs7cgXp9qsCfPcDBLLW+X3oxnoLaePQMlpQt6hX/lzFnNaWKv/KB1jlA==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7", + "load-bmfont": "^1.4.0" + } + }, + "@jimp/plugin-resize": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.6.0.tgz", + "integrity": "sha512-m0AA/mPkJG++RuftBFDUMRenqgIN/uSh88Kqs33VURYaabApni4ML3QslE1TCJtl2Lnu1eosxYlbzODjHx49eg==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-rotate": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.6.0.tgz", + "integrity": "sha512-1QGlIisyxs2HNLuynq/ETc4h7E6At3yR+IYAhG9U4KONG4RqlIy0giyDhnfEZaiqOE+O7f+0Z7zN6GoSHmQjzg==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-scale": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.6.0.tgz", + "integrity": "sha512-le/ttYwYioNPRoMlMaoJMCTv+m8d1v0peo/3J8E6Rf9ok7Bw3agkvjL9ILnsmr8jXj1YLrBSPKRs5nJ6ziM/qA==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "@jimp/plugins": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.6.0.tgz", + "integrity": "sha512-9+znfBJM1B31kvw+IcQFnAuDntQhwca/SONFnKOSZ8BNiQdiuTNbXHFxOo3tvdv1ngtB+LkkiTgK+QoF358b8g==", + "requires": { + "@jimp/plugin-blit": "^0.6.0", + "@jimp/plugin-blur": "^0.6.0", + "@jimp/plugin-color": "^0.6.0", + "@jimp/plugin-contain": "^0.6.0", + "@jimp/plugin-cover": "^0.6.0", + "@jimp/plugin-crop": "^0.6.0", + "@jimp/plugin-displace": "^0.6.0", + "@jimp/plugin-dither": "^0.6.0", + "@jimp/plugin-flip": "^0.6.0", + "@jimp/plugin-gaussian": "^0.6.0", + "@jimp/plugin-invert": "^0.6.0", + "@jimp/plugin-mask": "^0.6.0", + "@jimp/plugin-normalize": "^0.6.0", + "@jimp/plugin-print": "^0.6.0", + "@jimp/plugin-resize": "^0.6.0", + "@jimp/plugin-rotate": "^0.6.0", + "@jimp/plugin-scale": "^0.6.0", + "core-js": "^2.5.7", + "timm": "^1.6.1" + } + }, + "@jimp/png": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.6.0.tgz", + "integrity": "sha512-DBtMyQyrJxuKI7/1dVqLek+rCMM8U6BSOTHgo05wU7lhJKTB6fn2tbYfsnHQKzd9ld1M2qKuC+O1GTVdB2yl6w==", + "requires": { + "@jimp/utils": "^0.6.0", + "core-js": "^2.5.7", + "pngjs": "^3.3.3" + } + }, + "@jimp/tiff": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.6.0.tgz", + "integrity": "sha512-PV95CquEsolFziq0zZrAEJIzZSKwMK89TvkOXTPDi/xesgdXGC2rtG1IZFpC9L4UX5hi/M5GaeJa49xULX6Nqw==", + "requires": { + "core-js": "^2.5.7", + "utif": "^2.0.1" + } + }, + "@jimp/types": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.6.0.tgz", + "integrity": "sha512-j4tm82huEWpLrwave/2NYnMTY6us/6K9Js6Vd/CHoM/ki8M71tMXEVzc8tly92wtnEzQ9+FEk0Ue6pYo68m/5A==", + "requires": { + "@jimp/bmp": "^0.6.0", + "@jimp/gif": "^0.6.0", + "@jimp/jpeg": "^0.6.0", + "@jimp/png": "^0.6.0", + "@jimp/tiff": "^0.6.0", + "core-js": "^2.5.7", + "timm": "^1.6.1" + } + }, + "@jimp/utils": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.6.0.tgz", + "integrity": "sha512-z5iYEfqc45vlYweROneNkjv32en6jS7lPL/eMLIvaEcQAHaoza20Dw8fUoJ0Ht9S92kR74xeTunAZq+gK2w67Q==", + "requires": { + "core-js": "^2.5.7" + } + }, + "@webassemblyjs/ast": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz", + "integrity": "sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz", + "integrity": "sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz", + "integrity": "sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz", + "integrity": "sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz", + "integrity": "sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.7.11" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz", + "integrity": "sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz", + "integrity": "sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg==", + "dev": true + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz", + "integrity": "sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz", + "integrity": "sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz", + "integrity": "sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.11.tgz", + "integrity": "sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.1" + } + }, + "@webassemblyjs/utf8": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.11.tgz", + "integrity": "sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz", + "integrity": "sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/helper-wasm-section": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-opt": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "@webassemblyjs/wast-printer": "1.7.11" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz", + "integrity": "sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz", + "integrity": "sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz", + "integrity": "sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz", + "integrity": "sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/floating-point-hex-parser": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-code-frame": "1.7.11", + "@webassemblyjs/helper-fsm": "1.7.11", + "@xtuc/long": "4.2.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz", + "integrity": "sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11", + "@xtuc/long": "4.2.1" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.1.tgz", + "integrity": "sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==", + "dev": true + }, + "JSONSelect": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz", + "integrity": "sha1-oI7cxn6z/L6Z7WMIVTRKDPKCu40=" + }, + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "access-sniff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/access-sniff/-/access-sniff-3.2.0.tgz", + "integrity": "sha512-HLvH8e5g312urx6ZRo+nxSHjhVHEcuUxbpjFaFQ1LZOtN19L0CSb5ppwxtxy0QZ05zYAcWmXH6lVurdb+mGuUw==", + "dev": true, + "requires": { + "axios": "^0.18.0", + "bluebird": "^3.5.1", + "chalk": "^2.3.1", + "commander": "^2.14.1", + "glob": "^7.1.2", + "html_codesniffer": "^2.1.1", + "jsdom": "^11.6.2", + "mkdirp": "^0.5.1", + "phantomjs-prebuilt": "^2.1.16", + "rc": "^1.2.5", + "underscore": "^1.8.3", + "unixify": "^1.0.0", + "validator": "^9.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", + "dev": true + } + } + }, + "acorn": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", + "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", + "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", + "dev": true, + "requires": { + "acorn": "^5.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + } + } + }, + "acorn-globals": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", + "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + } + }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true + }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + } + } + }, + "ajv": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", + "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz", + "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=", + "dev": true + }, + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-equal": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "arrive": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/arrive/-/arrive-2.4.1.tgz", + "integrity": "sha1-VkyH8gvAm4DeeBEk2UMWlQBLgCA=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "ast-types": { + "version": "0.11.7", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.7.tgz", + "integrity": "sha512-2mP3TwtkY/aTv5X3ZsMpNAbOnyoC/aMJwJSoaELPkHId0nSQgFcnU4dRW3isxiz7+zBexk0ym3WNVjMiQBnJSw==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.4.3.tgz", + "integrity": "sha512-/XSnzDepRkAU//xLcXA/lUWxpsBuw0WiriAHOqnxkuCtzLhaz+fL4it4gp20BQ8n5SyLzK/FOc7A0+u/rti2FQ==", + "dev": true, + "requires": { + "browserslist": "^4.3.6", + "caniuse-lite": "^1.0.30000921", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.6", + "postcss-value-parser": "^3.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "axios": { + "version": "0.18.0", + "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "dev": true, + "requires": { + "follow-redirects": "^1.3.0", + "is-buffer": "^1.1.5" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-eslint": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz", + "integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, + "babel-loader": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.4.tgz", + "integrity": "sha512-fhBhNkUToJcW9nV46v8w87AJOwAJDz84c1CL57n3Stj73FANM/b9TbCUK4YhdOwEyZ+OxhYpdeZDNzSI29Firw==", + "dev": true, + "requires": { + "find-cache-dir": "^1.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1", + "util.promisify": "^1.0.0" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", + "dev": true + }, + "babel-plugin-transform-builtin-extend": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-builtin-extend/-/babel-plugin-transform-builtin-extend-1.1.2.tgz", + "integrity": "sha1-Xpb+z1i4+h7XTvytiEdbKvPJEW4=", + "requires": { + "babel-runtime": "^6.2.0", + "babel-template": "^6.3.0" + } + }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + } + } + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "bfj": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.1.tgz", + "integrity": "sha512-+GUNvzHR4nRyGybQc2WpNJL4MJazMuvf92ueIyA0bIkPRwhhQu3IfZQ2PSoVPpCBJfmoSdOxu5rnotfFLlvYRQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "check-types": "^7.3.0", + "hoopy": "^0.1.2", + "tryer": "^1.0.0" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "bignumber.js": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-8.0.2.tgz", + "integrity": "sha512-EiuvFrnbv0jFixEQ9f58jo7X0qI2lNGIr/MxntmVzQc5JUweDSh8y8hbTCAomFtqwUPIOWcLXP0VEOSZTG7FFw==" + }, + "binary-extensions": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", + "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", + "dev": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==", + "dev": true + }, + "bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM=" + }, + "bn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bn/-/bn-1.0.1.tgz", + "integrity": "sha1-oVOCXmsessLbdyYUmwR6B84KO7M=" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "dev": true, + "requires": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + } + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "bootstrap": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.2.1.tgz", + "integrity": "sha512-tt/7vIv3Gm2mnd/WeDx36nfGGHleil0Wg8IeB7eMrVkY0fZ5iTaBisSh8oNANc2IBsCc6vCgCNTIM/IEN0+50Q==", + "dev": true + }, + "bootstrap-colorpicker": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/bootstrap-colorpicker/-/bootstrap-colorpicker-2.5.3.tgz", + "integrity": "sha512-xdllX8LSMvKULs3b8JrgRXTvyvjkSMHHHVuHjjN5FNMqr6kRe5NPiMHFmeAFjlgDF73MspikudLuEwR28LbzLw==", + "requires": { + "jquery": ">=1.10" + } + }, + "bootstrap-material-design": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bootstrap-material-design/-/bootstrap-material-design-4.1.1.tgz", + "integrity": "sha1-h0M9sL9k1qCvsPX6qoYGE0ydJtI=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "optional": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" + } + }, + "bson": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.0.1.tgz", + "integrity": "sha512-Q+E5edAc2DnSb77xcBJga0iJDyZlhkKRhWxKdPJcT3UK6nC6BtmMJGpkt+99bGht3HIhXHu7mxi5FLBgQAj5MA==", + "requires": { + "buffer": "^5.1.0", + "long": "^4.0.0" + }, + "dependencies": { + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + } + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true + }, + "bzip-deflate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bzip-deflate/-/bzip-deflate-1.0.0.tgz", + "integrity": "sha1-sC2wB+83vrzCk4Skssb08PTHlsk=" + }, + "cacache": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "dev": true + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30000925", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000925.tgz", + "integrity": "sha512-zcYupoUxtW46rOikuDF7vfL9N1Qe9ZuUBTz3n3q8fFsoJIs/h9UN6Vg/0QpjsmvImXw9mVc3g+ZBfqvUz/iALA==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "catharsis": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz", + "integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=", + "dev": true, + "requires": { + "underscore-contrib": "~0.3.0" + } + }, + "chai-nightwatch": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.2.1.tgz", + "integrity": "sha512-2lprSMi72sHq2ZGyPTYUDQNsd2O4z81SicascbI4bkU54Xzk5Ofunn2CbrExADGC7jBH2D8r66X/aSEl+/agXQ==", + "dev": true, + "requires": { + "assertion-error": "1.0.0", + "deep-eql": "0.1.3" + }, + "dependencies": { + "assertion-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "dev": true + } + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "check-types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-7.4.0.tgz", + "integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==", + "dev": true + }, + "chi-squared": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chi-squared/-/chi-squared-1.1.0.tgz", + "integrity": "sha1-iShlz/qOCnIPkhv8nGNcGawqNG0=", + "requires": { + "gamma": "^1.0.0" + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz", + "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "chromedriver": { + "version": "2.45.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.45.0.tgz", + "integrity": "sha512-Qwmcr+2mU3INeR6mVsQ8gO00vZpL8ZeTJLclX44C0dcs88jrSDgckPqbG+qkVX+m2L/aOPnF0lYgPdOiOiLt5w==", + "dev": true, + "requires": { + "del": "^3.0.0", + "extract-zip": "^1.6.7", + "mkdirp": "^0.5.1", + "request": "^2.88.0", + "tcp-port-used": "^1.0.1" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cjson": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.2.1.tgz", + "integrity": "sha1-c82KrWXZ4VBfmvF0TTt5wVJ2gqU=" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, + "cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "dev": true, + "requires": { + "exit": "0.1.2", + "glob": "^7.1.1" + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "coffeescript": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.10.0.tgz", + "integrity": "sha1-56qDAZF+9iGzXYo580jc3R234z4=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "compressible": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz", + "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", + "dev": true, + "requires": { + "mime-db": ">= 1.36.0 < 2" + } + }, + "compression": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", + "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.14", + "debug": "2.6.9", + "on-headers": "~1.0.1", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "dependencies": { + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, + "connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", + "dev": true + }, + "connect-livereload": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", + "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-4.0.0.tgz", + "integrity": "sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0", + "require-from-string": "^2.0.1" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-api": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/crypto-api/-/crypto-api-0.8.3.tgz", + "integrity": "sha512-ZhUQvYTn5DpW2aS8F/OezedZPniCNcJhpP4Njrsuyt+9Y9400ht5Wue7w3D/dZWgekF+W7fz4bYUKf6u/waiGQ==" + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-js": { + "version": "3.1.9-1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", + "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" + }, + "css-loader": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.1.0.tgz", + "integrity": "sha512-MoOu+CStsGrSt5K2OeZ89q3Snf+IkxRfAIt9aAKg4piioTrhtP1iEFPu+OVn3Ohz24FO6L+rw9UJxBILiSBw5Q==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "loader-utils": "^1.2.1", + "lodash": "^4.17.11", + "postcss": "^7.0.6", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^2.0.3", + "postcss-modules-scope": "^2.0.0", + "postcss-modules-values": "^2.0.0", + "postcss-value-parser": "^3.3.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-selector-tokenizer": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "css-what": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz", + "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==", + "dev": true + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "cssom": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", + "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", + "dev": true + }, + "cssstyle": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", + "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, + "ctph.js": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ctph.js/-/ctph.js-0.0.5.tgz", + "integrity": "sha1-F+xd3R2+aPFRvj1EbPGNRhuV8uc=" + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", + "dev": true + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-uri-to-buffer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", + "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==", + "dev": true + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "datauri": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/datauri/-/datauri-1.1.0.tgz", + "integrity": "sha512-0q+cTTKx7q8eDteZRIQLTFJuiIsVing17UbWTPssY4JLSMaYsk/VKpNulBDo9NSgQWcvlPrkEHW8kUO67T/7mQ==", + "dev": true, + "requires": { + "image-size": "^0.6.2", + "mimer": "^0.3.2", + "semver": "^5.5.0" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "http://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "deep-for-each": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/deep-for-each/-/deep-for-each-2.0.3.tgz", + "integrity": "sha512-Y9mu+rplGcNZ7veer+5rqcdI9w3aPb7/WyE/nYnsuPevaE2z5YuC2u7/Gz/hIKsa0zo8sE8gKoBimSNsO/sr+A==", + "dev": true, + "requires": { + "lodash.isplainobject": "^4.0.6" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "default-gateway": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz", + "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==", + "dev": true, + "requires": { + "execa": "^0.10.0", + "ip-regex": "^2.1.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "degenerator": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", + "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=", + "dev": true, + "requires": { + "ast-types": "0.x.x", + "escodegen": "1.x.x", + "esprima": "3.x.x" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + } + } + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "~1.1.1", + "entities": "~1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + } + } + }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "domelementtype": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.2.1.tgz", + "integrity": "sha512-SQVCLFS2E7G5CRCMdn6K9bIhRj1bS6QBWZfF0TUPh4V/BbqrQ619IdSS3/izn0FZ+9l+uODzaZjb08fjOfablA==", + "dev": true + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "duplexify": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", + "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ebnf-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/ebnf-parser/-/ebnf-parser-0.1.10.tgz", + "integrity": "sha1-zR9rpHfFY4xAyX7ZtXLbW6tdgzE=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + } + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "ejs": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", + "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.96.tgz", + "integrity": "sha512-ZUXBUyGLeoJxp4Nt6G/GjBRLnyz8IKQGexZ2ndWaoegThgMGFO1tdDYID5gBV32/1S83osjJHyfzvanE/8HY4Q==", + "dev": true + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "entities": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "dev": true, + "requires": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" + }, + "es6-polyfills": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es6-polyfills/-/es6-polyfills-2.0.0.tgz", + "integrity": "sha1-fzWP04jYyIjQDPyaHuqJ+XFoOTE=", + "requires": { + "es6-object-assign": "^1.0.3", + "es6-promise-polyfill": "^1.2.0" + } + }, + "es6-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", + "dev": true + }, + "es6-promise-polyfill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es6-promise-polyfill/-/es6-promise-polyfill-1.2.0.tgz", + "integrity": "sha1-84kl8jyz4+jObNqP93T867sJDN4=" + }, + "es6-promisify": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.0.1.tgz", + "integrity": "sha512-J3ZkwbEnnO+fGAKrjVpeUAnZshAdfZvbhQpqfIH9kSAspReRC4nJnu8ewm55b4y9ElyeuhCTzJD0XiH8Tsbhlw==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", + "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + } + } + }, + "escope": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escope/-/escope-1.0.3.tgz", + "integrity": "sha1-dZ3OhJbEJI/sLQyq9BCLzz8af10=", + "requires": { + "estraverse": "^2.0.0" + }, + "dependencies": { + "estraverse": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-2.0.0.tgz", + "integrity": "sha1-WuRpYyQ2ACBmdMyySgnhZnT83KE=" + } + } + }, + "eslint": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.12.1.tgz", + "integrity": "sha512-54NV+JkTpTu0d8+UYSA8mMKAG4XAsaOrozA9rCW7tgneg1mevcL7wIotPC+fZ0SkWwdhNqoXoxnQCTBp7UvTsg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.1.0", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.10.0.tgz", + "integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "esmangle": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esmangle/-/esmangle-1.0.1.tgz", + "integrity": "sha1-2bs3uPjq+/Tm1O1reqKVarvTxMI=", + "requires": { + "escodegen": "~1.3.2", + "escope": "~1.0.1", + "esprima": "~1.1.1", + "esshorten": "~1.1.0", + "estraverse": "~1.5.0", + "esutils": "~ 1.0.0", + "optionator": "~0.3.0", + "source-map": "~0.1.33" + }, + "dependencies": { + "escodegen": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz", + "integrity": "sha1-8CQBb1qI4Eb9EgBQVek5gC5sXyM=", + "requires": { + "esprima": "~1.1.1", + "estraverse": "~1.5.0", + "esutils": "~1.0.0", + "source-map": "~0.1.33" + } + }, + "esprima": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", + "integrity": "sha1-W28VR/TRAuZw4UDFCb5ncdautUk=" + }, + "estraverse": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=" + }, + "esutils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=" + }, + "fast-levenshtein": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz", + "integrity": "sha1-AXjc3uAjuSkFGTrwlZ6KdjnP3Lk=" + }, + "levn": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz", + "integrity": "sha1-uo0znQykphDjo/FFucr0iAcVUFQ=", + "requires": { + "prelude-ls": "~1.1.0", + "type-check": "~0.3.1" + } + }, + "optionator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.3.0.tgz", + "integrity": "sha1-lxWotfXnWGz/BsgkngOc1zZNP1Q=", + "requires": { + "deep-is": "~0.1.2", + "fast-levenshtein": "~1.0.0", + "levn": "~0.2.4", + "prelude-ls": "~1.1.0", + "type-check": "~0.3.1", + "wordwrap": "~0.0.2" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "requires": { + "amdefine": ">=0.0.4" + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + } + } + }, + "espree": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.0.tgz", + "integrity": "sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA==", + "dev": true, + "requires": { + "acorn": "^6.0.2", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "esshorten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/esshorten/-/esshorten-1.1.1.tgz", + "integrity": "sha1-F0+Wt8wmfkaHLYFOfbfCkL3/Yak=", + "requires": { + "escope": "~1.0.1", + "estraverse": "~4.1.1", + "esutils": "~2.0.2" + }, + "dependencies": { + "estraverse": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz", + "integrity": "sha1-9srKcokzqFDvkGYdDheYK6RxEaI=" + } + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter2": { + "version": "0.4.14", + "resolved": "http://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "eventemitter3": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=" + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "exports-loader": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/exports-loader/-/exports-loader-0.7.0.tgz", + "integrity": "sha512-RKwCrO4A6IiKm0pG3c9V46JxIHcDplwwGJn6+JJ1RcVnh/WSGJa0xkmk5cRVtgOPzCAtTMGj2F7nluh9L0vpSA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "source-map": "0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.0.tgz", + "integrity": "sha1-D+llA6yGpa213mP05BKuSHLNvoY=", + "dev": true + } + } + }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "file-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz", + "integrity": "sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "file-saver": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.0.tgz", + "integrity": "sha512-cYM1ic5DAkg25pHKgi5f10ziAM7RJU37gaH1XQlyNDrtUnzhC/dfoV9zf2OmF0RMKi42jG5B0JWBnPQqyj/G6g==" + }, + "file-sync-cmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=", + "dev": true + }, + "file-type": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", + "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true + }, + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "dev": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "findup-sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", + "dev": true, + "requires": { + "glob": "~5.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "follow-redirects": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz", + "integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==", + "dev": true, + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-extra": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "ftp": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", + "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", + "dev": true, + "requires": { + "readable-stream": "1.1.x", + "xregexp": "2.0.0" + }, + "dependencies": { + "xregexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", + "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=", + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gamma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gamma/-/gamma-1.0.0.tgz", + "integrity": "sha1-mDwck5/iPZMnAVhXEeHZpDDLdMs=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "requires": { + "globule": "^1.0.0" + } + }, + "geodesy": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/geodesy/-/geodesy-1.1.3.tgz", + "integrity": "sha512-H/0XSd1KjKZGZ2YGZcOYzRyY/foYAawwTEumNSo+YUwf+u5d4CfvBRg2i2Qimrx9yUEjWR8hLvMnhghuVFN0Zg==" + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-uri": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.2.tgz", + "integrity": "sha512-ZD325dMZOgerGqF/rF6vZXyFGTAay62svjQIT+X/oU2PtxYpFxvSkbsdi+oxIrsNxlZVd4y8wUDqkaExWTI/Cw==", + "dev": true, + "requires": { + "data-uri-to-buffer": "1", + "debug": "2", + "extend": "3", + "file-uri-to-path": "1", + "ftp": "~0.3.10", + "readable-stream": "2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getobject": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "^2.19.0", + "process": "~0.5.1" + }, + "dependencies": { + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + } + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "optional": true + }, + "grunt": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.3.tgz", + "integrity": "sha512-/JzmZNPfKorlCrrmxWqQO4JVodO+DVd5XX4DkocL/1WlLlKVLE9+SdEIempOAxDhWPysLle6afvn/hg7Ck2k9g==", + "dev": true, + "requires": { + "coffeescript": "~1.10.0", + "dateformat": "~1.0.12", + "eventemitter2": "~0.4.13", + "exit": "~0.1.1", + "findup-sync": "~0.3.0", + "glob": "~7.0.0", + "grunt-cli": "~1.2.0", + "grunt-known-options": "~1.1.0", + "grunt-legacy-log": "~2.0.0", + "grunt-legacy-util": "~1.1.1", + "iconv-lite": "~0.4.13", + "js-yaml": "~3.5.2", + "minimatch": "~3.0.2", + "mkdirp": "~0.5.1", + "nopt": "~3.0.6", + "path-is-absolute": "~1.0.0", + "rimraf": "~2.6.2" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "grunt-cli": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", + "dev": true, + "requires": { + "findup-sync": "~0.3.0", + "grunt-known-options": "~1.1.0", + "nopt": "~3.0.6", + "resolve": "~1.1.0" + } + }, + "js-yaml": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", + "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", + "dev": true, + "requires": { + "argparse": "^1.0.2", + "esprima": "^2.6.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "grunt-accessibility": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/grunt-accessibility/-/grunt-accessibility-6.0.0.tgz", + "integrity": "sha512-5Y7MMYzpzMICkspvmUOU+YC/VE5eiB5TV8k9u43ZFrzLIoYDulKce8KX0fyi2EXYEDKlUEyaVI/W4rLDqqy3/Q==", + "dev": true, + "requires": { + "access-sniff": "^3.2.0" + } + }, + "grunt-chmod": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-chmod/-/grunt-chmod-1.1.1.tgz", + "integrity": "sha1-0YZcWoTn7Zrv5Qn/v1KQ+XoleEA=", + "dev": true, + "requires": { + "shelljs": "^0.5.3" + }, + "dependencies": { + "shelljs": { + "version": "0.5.3", + "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", + "integrity": "sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM=", + "dev": true + } + } + }, + "grunt-concurrent": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-2.3.1.tgz", + "integrity": "sha1-Hj2zjM71o9oRleYdYx/n4yE0TSM=", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "async": "^1.2.1", + "indent-string": "^2.0.0", + "pad-stream": "^1.0.0" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "grunt-contrib-clean": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.0.tgz", + "integrity": "sha512-g5ZD3ORk6gMa5ugZosLDQl3dZO7cI3R14U75hTM+dVLVxdMNJCPVmwf9OUt4v4eWgpKKWWoVK9DZc1amJp4nQw==", + "dev": true, + "requires": { + "async": "^2.6.1", + "rimraf": "^2.6.2" + } + }, + "grunt-contrib-connect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-connect/-/grunt-contrib-connect-2.0.0.tgz", + "integrity": "sha512-JVjM9UDP84WbT2S7swkyuwPuxFtT+zry/RUBuP3IT8LZPEQjtzzMwiM+qimswNKQ9plh5WhcFWaaqz2ruB9/DA==", + "dev": true, + "requires": { + "async": "^2.6.1", + "connect": "^3.6.6", + "connect-livereload": "^0.6.0", + "morgan": "^1.9.0", + "node-http2": "^4.0.1", + "opn": "^5.3.0", + "portscanner": "^2.2.0", + "serve-index": "^1.9.1", + "serve-static": "^1.13.2" + } + }, + "grunt-contrib-copy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", + "dev": true, + "requires": { + "chalk": "^1.1.1", + "file-sync-cmp": "^0.1.0" + } + }, + "grunt-contrib-jshint": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz", + "integrity": "sha1-Np2QmyWTxA6L55lAshNAhQx5Oaw=", + "dev": true, + "requires": { + "chalk": "^1.1.1", + "hooker": "^0.2.3", + "jshint": "~2.9.4" + } + }, + "grunt-contrib-watch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", + "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", + "dev": true, + "requires": { + "async": "^2.6.0", + "gaze": "^1.1.0", + "lodash": "^4.17.10", + "tiny-lr": "^1.1.1" + } + }, + "grunt-eslint": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-21.0.0.tgz", + "integrity": "sha512-HJocD9P35lpCvy6pPPCTgzBavzckrT1nt7lpqV55Vy8E6LQJv4RortXoH1jJTYhO5DYY7RPATv7Uc4383PUYqQ==", + "dev": true, + "requires": { + "chalk": "^2.1.0", + "eslint": "^5.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "grunt-exec": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-exec/-/grunt-exec-3.0.0.tgz", + "integrity": "sha512-cgAlreXf3muSYS5LzW0Cc4xHK03BjFOYk0MqCQ/MZ3k1Xz2GU7D+IAJg4UKicxpO+XdONJdx/NJ6kpy2wI+uHg==", + "dev": true + }, + "grunt-jsdoc": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/grunt-jsdoc/-/grunt-jsdoc-2.3.0.tgz", + "integrity": "sha512-gC66TCRXeQMj3HIyqVSBJm8zdUz43e5vaG/PLO/627A1edbJnzxhJV7nF0KqLwMM0RDNu1istC6fvfnYqFKi3w==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.5", + "jsdoc": "~3.5.5", + "marked": "^0.5.0" + } + }, + "grunt-known-options": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz", + "integrity": "sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==", + "dev": true + }, + "grunt-legacy-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz", + "integrity": "sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw==", + "dev": true, + "requires": { + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.5" + }, + "dependencies": { + "colors": { + "version": "1.1.2", + "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz", + "integrity": "sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA==", + "dev": true, + "requires": { + "chalk": "~2.4.1", + "lodash": "~4.17.10" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "grunt-legacy-util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz", + "integrity": "sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A==", + "dev": true, + "requires": { + "async": "~1.5.2", + "exit": "~0.1.1", + "getobject": "~0.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.10", + "underscore.string": "~3.3.4", + "which": "~1.3.0" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "grunt-webpack": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/grunt-webpack/-/grunt-webpack-3.1.3.tgz", + "integrity": "sha512-SaZ8K8lG4iTxs7ClZxOWCf3kxqS2y+Eel8SbaEGgBKwhAp6e45beIu+vhBZRLX3vonKML2kjemKsQ21REaqNFQ==", + "dev": true, + "requires": { + "deep-for-each": "^2.0.2", + "lodash": "^4.7.0" + } + }, + "gzip-size": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.0.0.tgz", + "integrity": "sha512-5iI7omclyqrnWw4XbXAmGhPsABkSIDQonv2K0h61lybgofWa6iZyvrI3r2zsJH4P8Nb64fFVzlvfhs0g7BBxAA==", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "pify": "^3.0.0" + } + }, + "handle-thing": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hasha": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", + "dev": true, + "requires": { + "is-stream": "^1.0.1", + "pinkie-promise": "^2.0.0" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "highlight.js": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.13.1.tgz", + "integrity": "sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==" + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true + }, + "hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "dev": true + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + } + }, + "html-webpack-plugin": { + "version": "3.2.0", + "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", + "dev": true, + "requires": { + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", + "util.promisify": "1.0.0" + }, + "dependencies": { + "json5": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + }, + "dependencies": { + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + } + } + } + } + }, + "html_codesniffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/html_codesniffer/-/html_codesniffer-2.2.0.tgz", + "integrity": "sha512-xG6E3g2V/huu/WwRcrX3AFRmAUFkU7PWlgF/QFLtuRitI+NxvJcYTFthb24rB7/mhKE3khSIxB11A8IQTJ2N9w==", + "dev": true, + "requires": { + "grunt": "^1.0.0", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-jshint": "^1.0.0", + "grunt-contrib-watch": "^1.0.0", + "load-grunt-tasks": "^3.5.2" + } + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "http-parser-js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", + "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "http-proxy-middleware": { + "version": "0.18.0", + "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", + "dev": true, + "requires": { + "http-proxy": "^1.16.2", + "is-glob": "^4.0.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "i": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz", + "integrity": "sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=", + "dev": true + }, + "iced-error": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/iced-error/-/iced-error-0.0.13.tgz", + "integrity": "sha512-yEEaG8QfyyRL0SsbNNDw3rVgTyqwHFMCuV6jDvD43f/2shmdaFXkqvFLGhDlsYNSolzYHwVLM/CrXt9GygYopA==" + }, + "iced-lock": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/iced-lock/-/iced-lock-1.1.0.tgz", + "integrity": "sha1-YRbvHKs6zW5rEIk7snumIv0/3nI=", + "requires": { + "iced-runtime": "^1.0.0" + } + }, + "iced-runtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/iced-runtime/-/iced-runtime-1.0.3.tgz", + "integrity": "sha1-LU9PuZmreqVDCxk8d6f85BGDGc4=" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "icss-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.0.0.tgz", + "integrity": "sha512-bA/xGiwWM17qjllIs9X/y0EjsB7e0AV08F3OL8UPsoNkNRibIuu8f1eKTnQ8QO1DteKKTxTUAn+IEWUToIwGOA==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "image-size": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", + "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } + } + }, + "imports-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-0.8.0.tgz", + "integrity": "sha512-kXWL7Scp8KQ4552ZcdVTeaQCZSLW+e6nJfp3cwUMB673T7Hr98Xjx5JK+ql7ADlJUvj1JS5O01RLbKoutN5QDQ==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "source-map": "^0.6.1" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "ink-docstrap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ink-docstrap/-/ink-docstrap-1.3.2.tgz", + "integrity": "sha512-STx5orGQU1gfrkoI/fMU7lX6CSP7LBGO10gXNgOZhwKhUqbtNjCkYSewJtNnLmWP1tAGN6oyEpG1HFPw5vpa5Q==", + "dev": true, + "requires": { + "moment": "^2.14.1", + "sanitize-html": "^1.13.0" + } + }, + "inquirer": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", + "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "internal-ip": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz", + "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==", + "dev": true, + "requires": { + "default-gateway": "^2.6.0", + "ipaddr.js": "^1.5.2" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-number-like": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", + "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", + "dev": true, + "requires": { + "lodash.isfinite": "^3.3.2" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "is2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.1.tgz", + "integrity": "sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "ip-regex": "^2.1.0", + "is-url": "^1.2.2" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jimp": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.6.0.tgz", + "integrity": "sha512-RYpN+AAlTEMf8Bnkhq2eeTNyr70rDK/2UUfUqzBJmwmZwdR6fxRJvgbCGWT1BDVRxaAqo+4CWm8ePBxOIsr4jg==", + "requires": { + "@babel/polyfill": "^7.0.0", + "@jimp/custom": "^0.6.0", + "@jimp/plugins": "^0.6.0", + "@jimp/types": "^0.6.0", + "core-js": "^2.5.7" + } + }, + "jison": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/jison/-/jison-0.4.13.tgz", + "integrity": "sha1-kEFwfWIkE2f1iDRTK58ZwsNvrHg=", + "requires": { + "JSONSelect": "0.4.0", + "cjson": "~0.2.1", + "ebnf-parser": "~0.1.9", + "escodegen": "0.0.21", + "esprima": "1.0.x", + "jison-lex": "0.2.x", + "lex-parser": "~0.1.3", + "nomnom": "1.5.2" + }, + "dependencies": { + "escodegen": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-0.0.21.tgz", + "integrity": "sha1-U9ZSz6EDA4gnlFilJmxf/HCcY8M=", + "requires": { + "esprima": "~1.0.2", + "estraverse": "~0.0.4", + "source-map": ">= 0.1.2" + } + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" + }, + "estraverse": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-0.0.4.tgz", + "integrity": "sha1-AaCTLf7ldGhKWYr1pnw7+bZCjbI=" + } + } + }, + "jison-lex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/jison-lex/-/jison-lex-0.2.1.tgz", + "integrity": "sha1-rEuBXozOUTLrErXfz+jXB7iETf4=", + "requires": { + "lex-parser": "0.1.x", + "nomnom": "1.5.2" + } + }, + "jpeg-js": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.4.tgz", + "integrity": "sha512-6IzjQxvnlT8UlklNmDXIJMWxijULjqGrzgqc0OG7YadZdvm7KPQ1j0ehmQQHckgEWOfgpptzcnWgESovxudpTA==" + }, + "jquery": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" + }, + "js-base64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.0.tgz", + "integrity": "sha512-wlEBIZ5LP8usDylWbDNhKPEFVFdI5hCHpnVoT/Ysvoi/PRhJENm/Rlh9TvjYB38HFfKZN7OzEbRjmjvLkFw11g==", + "dev": true + }, + "js-crc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/js-crc/-/js-crc-0.2.0.tgz", + "integrity": "sha1-9yxcdhgXa/91zIEqHO2949jraDk=" + }, + "js-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.4.tgz", + "integrity": "sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow==", + "dev": true + }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "js2xmlparser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz", + "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", + "dev": true, + "requires": { + "xmlcreate": "^1.0.1" + } + }, + "jsdoc": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz", + "integrity": "sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==", + "dev": true, + "requires": { + "babylon": "7.0.0-beta.19", + "bluebird": "~3.5.0", + "catharsis": "~0.8.9", + "escape-string-regexp": "~1.0.5", + "js2xmlparser": "~3.0.0", + "klaw": "~2.0.0", + "marked": "~0.3.6", + "mkdirp": "~0.5.1", + "requizzle": "~0.2.1", + "strip-json-comments": "~2.0.1", + "taffydb": "2.6.2", + "underscore": "~1.8.3" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.19", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz", + "integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==", + "dev": true + }, + "klaw": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz", + "integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "marked": { + "version": "0.3.19", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "dev": true + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + } + } + }, + "jsdoc-babel": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsdoc-babel/-/jsdoc-babel-0.5.0.tgz", + "integrity": "sha512-PYfTbc3LNTeR8TpZs2M94NLDWqARq0r9gx3SvuziJfmJS7/AeMKvtj0xjzOX0R/4MOVA7/FqQQK7d6U0iEoztQ==", + "dev": true, + "requires": { + "jsdoc-regex": "^1.0.1", + "lodash": "^4.17.10" + } + }, + "jsdoc-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jsdoc-regex/-/jsdoc-regex-1.0.1.tgz", + "integrity": "sha1-hCRCjVtWOtjFx/vsB5uaiwnI3Po=", + "dev": true + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "jshint": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.6.tgz", + "integrity": "sha512-KO9SIAKTlJQOM4lE64GQUtGBRpTOuvbrRrSZw3AhUxMNG266nX9hK2cKA4SBhXOj0irJGyNyGSLT62HGOVDEOA==", + "dev": true, + "requires": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.10", + "minimatch": "~3.0.2", + "phantom": "~4.0.1", + "phantomjs-prebuilt": "~2.1.7", + "shelljs": "0.3.x", + "strip-json-comments": "1.0.x", + "unicode-5.2.0": "^0.7.5" + }, + "dependencies": { + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + } + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.0.0.tgz", + "integrity": "sha1-Rc2dTE0NaCXZC9fkD4PxGCsT3Qc=", + "requires": { + "esprima": "1.2.2", + "jison": "0.4.13", + "static-eval": "2.0.0", + "underscore": "1.7.0" + }, + "dependencies": { + "esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha1-dqD9Zvz+FU/SkmZ9wmQBl1CxZXs=" + } + } + }, + "jsonwebtoken": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", + "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", + "requires": { + "jws": "^3.1.5", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jsqr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.1.1.tgz", + "integrity": "sha512-FVoMU2ncTyjaOqN/vwvDnZ7jaAVvFzM3LK3vG3jvQZFWJQlAwJ1XTCOgAEKo+4Rkd6ydMXTTvqGV/4w5VunmTw==" + }, + "jsrsasign": { + "version": "8.0.12", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz", + "integrity": "sha1-Iqu5ZW00owuVMENnIINeicLlwxY=" + }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "requires": { + "jwa": "^1.1.5", + "safe-buffer": "^5.0.1" + } + }, + "kbpgp": { + "version": "2.0.82", + "resolved": "https://registry.npmjs.org/kbpgp/-/kbpgp-2.0.82.tgz", + "integrity": "sha512-CBcV786ZMOP9FOnpg3ZJC3a++TJb47HPVVkCPpmgSuVS9Cnss+m9j1u/IY9HXzEjys7DTebaoMAzqB05FWPZfg==", + "requires": { + "bn": "^1.0.0", + "bzip-deflate": "^1.0.0", + "deep-equal": ">=0.2.1", + "iced-error": ">=0.0.10", + "iced-lock": "^1.0.2", + "iced-runtime": "^1.0.3", + "keybase-ecurve": "^1.0.0", + "keybase-nacl": "^1.0.0", + "minimist": "^1.2.0", + "pgp-utils": ">=0.0.34", + "purepack": ">=1.0.4", + "triplesec": ">=3.0.27", + "tweetnacl": "^0.13.1" + } + }, + "kew": { + "version": "0.7.0", + "resolved": "http://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", + "dev": true + }, + "keybase-ecurve": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/keybase-ecurve/-/keybase-ecurve-1.0.0.tgz", + "integrity": "sha1-xrxyrdpGA/0xhP7n6ZaU7Y/WmtI=", + "requires": { + "bn": "^1.0.0" + } + }, + "keybase-nacl": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/keybase-nacl/-/keybase-nacl-1.0.10.tgz", + "integrity": "sha1-OGWDHpSBUWSI33y9mJRn6VDYeos=", + "requires": { + "iced-runtime": "^1.0.2", + "tweetnacl": "^0.13.1", + "uint64be": "^1.0.1" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lex-parser": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/lex-parser/-/lex-parser-0.1.4.tgz", + "integrity": "sha1-ZMTwJfF/1Tv7RXY/rrFvAVp0dVA=" + }, + "libyara-wasm": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/libyara-wasm/-/libyara-wasm-0.0.11.tgz", + "integrity": "sha512-rglapPFo0IHPNksWYQXI8oqftXYj5mOGOf4BXtbSySVRX71pro4BehNjJ5qEpjYx+roGvNkcAD9zCsitA08sxw==" + }, + "livereload-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", + "dev": true + }, + "load-bmfont": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.0.tgz", + "integrity": "sha512-kT63aTAlNhZARowaNYcY29Fn/QYkc52M3l6V1ifRcPewg2lvUZDAj7R6dXjOL9D0sict76op3T5+odumDSF81g==", + "requires": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + } + } + }, + "load-grunt-tasks": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/load-grunt-tasks/-/load-grunt-tasks-3.5.2.tgz", + "integrity": "sha1-ByhWEYD9IP+KaSdQWFL8WKrqDIg=", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "multimatch": "^2.0.0", + "pkg-up": "^1.0.0", + "resolve-pkg": "^0.1.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "loader-runner": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.1.tgz", + "integrity": "sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw==", + "dev": true + }, + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" + }, + "dependencies": { + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lodash._arraycopy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", + "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=", + "dev": true + }, + "lodash._arrayeach": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", + "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=", + "dev": true + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._baseclone": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", + "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", + "dev": true, + "requires": { + "lodash._arraycopy": "^3.0.0", + "lodash._arrayeach": "^3.0.0", + "lodash._baseassign": "^3.0.0", + "lodash._basefor": "^3.0.0", + "lodash.isarray": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basefor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", + "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=", + "dev": true + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, + "lodash.clone": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-3.0.3.tgz", + "integrity": "sha1-hGiMc9MrWpDKJWFpY/GJJSqZcEM=", + "dev": true, + "requires": { + "lodash._baseclone": "^3.0.0", + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0" + } + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.defaultsdeep": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz", + "integrity": "sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E=", + "dev": true + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", + "dev": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isfinite": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", + "integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=", + "dev": true + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.merge": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", + "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==", + "dev": true + }, + "lodash.mergewith": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", + "dev": true + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "dev": true + }, + "lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", + "dev": true + }, + "loglevel": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=" + }, + "loglevel-message-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/loglevel-message-prefix/-/loglevel-message-prefix-3.0.0.tgz", + "integrity": "sha1-ER/bltlPlh2PyLiqv7ZrBqw+dq0=", + "requires": { + "es6-polyfills": "^2.0.0", + "loglevel": "^1.4.0" + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "marked": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.5.1.tgz", + "integrity": "sha512-iUkBZegCZou4AdwbKTwSW/lNDcz5OuRSl3qdcl31Ia0B2QPG0Jn+tKblh/9/eP9/6+4h27vpoh8wel/vQOV0vw==", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", + "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^1.1.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "meow": { + "version": "3.7.0", + "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "dev": true + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "mimer": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/mimer/-/mimer-0.3.2.tgz", + "integrity": "sha512-N6NcgDQAevhP/02DQ/epK6daLy4NKrIHyTlJcO6qBiYn98q+Y4a/knNsAATCe1xLS2F0nEmJp+QYli2s8vKwyQ==", + "dev": true + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "mini-css-extract-plugin": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz", + "integrity": "sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "mkpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-1.0.0.tgz", + "integrity": "sha1-67Opd+evHGg65v2hK1Raa6bFhT0=", + "dev": true + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "optional": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "commander": { + "version": "2.15.1", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true, + "optional": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true, + "optional": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "moment": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz", + "integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA==" + }, + "moment-timezone": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz", + "integrity": "sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w==", + "requires": { + "moment": ">= 2.9.0" + } + }, + "more-entropy": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/more-entropy/-/more-entropy-0.0.7.tgz", + "integrity": "sha1-Z7/G96hvJvvDeqyD/UbYjGHRCbU=", + "requires": { + "iced-runtime": ">=0.0.1" + } + }, + "morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "dev": true, + "requires": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "multimatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", + "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", + "dev": true, + "requires": { + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" + } + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", + "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "ncp": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", + "integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=", + "dev": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "dev": true + }, + "netmask": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", + "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=", + "dev": true + }, + "ngeohash": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/ngeohash/-/ngeohash-0.6.3.tgz", + "integrity": "sha512-kltF0cOxgx1AbmVzKxYZaoB0aj7mOxZeHaerEtQV0YaqnkXNq26WWqMmJ6lTqShYxVRWZ/mwvvTrNeOwdslWiw==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "nightwatch": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-1.0.18.tgz", + "integrity": "sha512-BKosRh/QqpCCMxjnfP+gb8KMQV0y//TNdYDjB0RrU1pXgx2Xjyp46bK8tQWRFfqaxWDj5EKYFIPgvxFBXodIOA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "chai-nightwatch": "0.2.1", + "ejs": "^2.5.9", + "lodash.clone": "^3.0.3", + "lodash.defaultsdeep": "^4.6.0", + "lodash.merge": "^4.6.1", + "minimatch": "3.0.3", + "mkpath": "1.0.0", + "mocha": "^5.1.1", + "optimist": "^0.6.1", + "proxy-agent": "^3.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + } + } + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "node-forge": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", + "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "node-http2": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/node-http2/-/node-http2-4.0.1.tgz", + "integrity": "sha1-Fk/1O13SLITwrxQrh3xerraAmVk=", + "dev": true, + "requires": { + "assert": "1.4.1", + "events": "1.1.1", + "https-browserify": "0.0.1", + "setimmediate": "^1.0.5", + "stream-browserify": "2.0.1", + "timers-browserify": "2.0.2", + "url": "^0.11.0", + "websocket-stream": "^5.0.1" + }, + "dependencies": { + "https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.2", + "resolved": "http://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz", + "integrity": "sha1-q0iDz1l9zVCvIRNJoA+8pWrIa4Y=", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + } + } + }, + "node-libs-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", + "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.0", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.10.3", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "string_decoder": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "node-md6": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/node-md6/-/node-md6-0.1.0.tgz", + "integrity": "sha1-9WH0WyszY1K4KXbFHMoRR9U5N/U=" + }, + "node-releases": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz", + "integrity": "sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "node-sass": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", + "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", + "dev": true, + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash.assign": "^4.2.0", + "lodash.clonedeep": "^4.3.2", + "lodash.mergewith": "^4.6.0", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.10.0", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + } + } + }, + "nomnom": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz", + "integrity": "sha1-9DRUSKhTz71cDSYyDyR3qwUm/i8=", + "requires": { + "colors": "0.5.x", + "underscore": "1.1.x" + }, + "dependencies": { + "colors": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" + }, + "underscore": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz", + "integrity": "sha1-QLq4S60Z0jAJbo1u9ii/8FXYPbA=" + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "notepack.io": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-2.2.0.tgz", + "integrity": "sha512-9b5w3t5VSH6ZPosoYnyDONnUTF8o0UkBw7JLA6eBlYJWyGT1Q3vQa8Hmuj1/X6RYvHjjygBDgw6fJhe0JEojfw==" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nwmatcher": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", + "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==" + }, + "nwsapi": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.9.tgz", + "integrity": "sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "omggif": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.9.tgz", + "integrity": "sha1-3LcCTazVDFK00wPwSALJHAV8dl8=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "opener": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", + "dev": true + }, + "opn": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", + "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "otp": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/otp/-/otp-0.1.3.tgz", + "integrity": "sha1-wle/JdL5Anr3esUiabPBQmjSvWs=", + "requires": { + "thirty-two": "^0.0.2" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pac-proxy-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.0.tgz", + "integrity": "sha512-AOUX9jES/EkQX2zRz0AW7lSx9jD//hQS8wFXBvcnd/J2Py9KaMJMqV/LPqJssj1tgGufotb2mmopGPR15ODv1Q==", + "dev": true, + "requires": { + "agent-base": "^4.2.0", + "debug": "^3.1.0", + "get-uri": "^2.0.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "pac-resolver": "^3.0.0", + "raw-body": "^2.2.0", + "socks-proxy-agent": "^4.0.1" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + } + } + }, + "pac-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", + "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", + "dev": true, + "requires": { + "co": "^4.6.0", + "degenerator": "^1.0.4", + "ip": "^1.1.5", + "netmask": "^1.0.6", + "thunkify": "^2.1.2" + } + }, + "pad-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-1.2.0.tgz", + "integrity": "sha1-Yx3Mn3mBC3BZZeid7eps/w/B38k=", + "dev": true, + "requires": { + "meow": "^3.0.0", + "pumpify": "^1.3.3", + "repeating": "^2.0.0", + "split2": "^1.0.0", + "through2": "^2.0.0" + } + }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==" + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "parent-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", + "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-asn1": { + "version": "5.1.1", + "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" + } + }, + "parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU=" + }, + "parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY=" + }, + "parse-bmfont-xml": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", + "requires": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.4.5" + } + }, + "parse-headers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.1.tgz", + "integrity": "sha1-aug6eqJanZtwCswoaYzR8e1+lTY=", + "requires": { + "for-each": "^0.3.2", + "trim": "0.0.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pgp-utils": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/pgp-utils/-/pgp-utils-0.0.34.tgz", + "integrity": "sha1-2E9J98GTteC5QV9cxcKmle15DCM=", + "requires": { + "iced-error": ">=0.0.8", + "iced-runtime": ">=0.0.1" + } + }, + "phantom": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/phantom/-/phantom-4.0.12.tgz", + "integrity": "sha512-Tz82XhtPmwCk1FFPmecy7yRGZG2btpzY2KI9fcoPT7zT9det0CcMyfBFPp1S8DqzsnQnm8ZYEfdy528mwVtksA==", + "dev": true, + "optional": true, + "requires": { + "phantomjs-prebuilt": "^2.1.16", + "split": "^1.0.1", + "winston": "^2.4.0" + } + }, + "phantomjs-prebuilt": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", + "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3", + "extract-zip": "^1.6.5", + "fs-extra": "^1.0.0", + "hasha": "^2.2.0", + "kew": "^0.7.0", + "progress": "^1.1.8", + "request": "^2.81.0", + "request-progress": "^2.0.1", + "which": "^1.2.10" + } + }, + "phin": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=", + "requires": { + "pngjs": "^3.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "pkg-up": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz", + "integrity": "sha1-Pgj7RhUlxEIWJKM7n35tCvWwWiY=", + "dev": true, + "requires": { + "find-up": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "pkginfo": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", + "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=", + "dev": true + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "pngjs": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.3.tgz", + "integrity": "sha512-1n3Z4p3IOxArEs1VRXnZ/RXdfEniAUS9jb68g58FIXMNkPJeZd+Qh4Uq7/e0LVxAQGos1eIUrqrt4FpjdnEd+Q==" + }, + "popper.js": { + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", + "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==" + }, + "portfinder": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", + "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "portscanner": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", + "dev": true, + "requires": { + "async": "^2.6.0", + "is-number-like": "^1.0.3" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.5.tgz", + "integrity": "sha512-HBNpviAUFCKvEh7NZhw1e8MBPivRszIiUnhrJ+sBFVSYSqubrzwX3KG51mYgcRHX8j/cAgZJedONZcm5jTBdgQ==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-css-variables": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/postcss-css-variables/-/postcss-css-variables-0.11.0.tgz", + "integrity": "sha512-pjqWnJSy8zoentAhRIph/DiOX0EZmT/dpmVbpdSrCSdkdqstl2ViBlAfIIuHvHI+baTV8Gd+WzsVFjDZqVn4dg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.3", + "extend": "^3.0.1", + "postcss": "^6.0.8" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-import": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", + "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-load-config": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.0.0.tgz", + "integrity": "sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ==", + "dev": true, + "requires": { + "cosmiconfig": "^4.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.3.tgz", + "integrity": "sha512-jv4CQ8IQ0+TkaAIP7H4kgu/jQbrjte8xU61SYJAIOby+o3H0MGWX6eN1WXUKHccK6/EEjcAERjyIP8MXzAWAbQ==", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^7.0.6", + "postcss-value-parser": "^3.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-scope": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.0.1.tgz", + "integrity": "sha512-7+6k9c3/AuZ5c596LJx9n923A/j3nF3ormewYBF1RrIQvjvjXe1xE8V8A1KFyFwXbvnshT6FBZFX0k/F1igneg==", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^7.0.6" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-values": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz", + "integrity": "sha512-Ki7JZa7ff1N3EIMlPnGTZfUMe69FFwiQPnVSXC9mnn3jozCRBYIxiZd44yJOV2AmabOo4qFf8s0dC/+lweG7+w==", + "dev": true, + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^7.0.6" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=" + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "prompt": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz", + "integrity": "sha1-jlcSPDlquYiJf7Mn/Trtw+c15P4=", + "dev": true, + "requires": { + "colors": "^1.1.2", + "pkginfo": "0.x.x", + "read": "1.0.x", + "revalidator": "0.1.x", + "utile": "0.3.x", + "winston": "2.1.x" + }, + "dependencies": { + "async": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", + "dev": true + }, + "winston": { + "version": "2.1.1", + "resolved": "http://registry.npmjs.org/winston/-/winston-2.1.1.tgz", + "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", + "dev": true, + "requires": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "pkginfo": "0.3.x", + "stack-trace": "0.0.x" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "pkginfo": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=", + "dev": true + } + } + } + } + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "proxy-agent": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.0.3.tgz", + "integrity": "sha512-PXVVVuH9tiQuxQltFJVSnXWuDtNr+8aNBP6XVDDCDiUuDN8eRCm+ii4/mFWmXWEA0w8jjJSlePa4LXlM4jIzNA==", + "dev": true, + "requires": { + "agent-base": "^4.2.0", + "debug": "^3.1.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^4.1.2", + "pac-proxy-agent": "^3.0.0", + "proxy-from-env": "^1.0.0", + "socks-proxy-agent": "^4.0.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "purepack": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/purepack/-/purepack-1.0.4.tgz", + "integrity": "sha1-CGKC/ZOShfWGZLqam7oxzbFlzNI=" + }, + "qr-image": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/qr-image/-/qr-image-3.2.0.tgz", + "integrity": "sha1-n6gpW+rlDEoUnPn5CaHbRkqGcug=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz", + "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==", + "dev": true + }, + "randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "dev": true, + "requires": { + "bytes": "1", + "string_decoder": "0.10" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "requires": { + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz", + "integrity": "sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "regenerator-transform": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.3.tgz", + "integrity": "sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==", + "dev": true, + "requires": { + "private": "^0.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "regexpu-core": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.4.0.tgz", + "integrity": "sha512-eDDWElbwwI3K0Lo6CqbQbA6FwgtCz4kYTarrri1okfkRLZAqstU+B3voZBCjg8Fl6iq0gXrJG6MvRgLthfvgOA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^7.0.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.0.2" + } + }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", + "dev": true + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "renderkid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.2.tgz", + "integrity": "sha512-FsygIxevi1jSiPY9h7vZmBFUbAOcbYm9UwyiLNdVsLRs/5We9Ob5NMPbGYUTWiLq5L+ezlVdE0A8bbME5CWTpg==", + "dev": true, + "requires": { + "css-select": "^1.1.0", + "dom-converter": "~0.2", + "htmlparser2": "~3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + }, + "dependencies": { + "domhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", + "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", + "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "htmlparser2": { + "version": "3.3.0", + "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.1", + "domutils": "1.1", + "readable-stream": "1.0" + } + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "request-progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", + "dev": true, + "requires": { + "throttleit": "^1.0.0" + } + }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dev": true, + "requires": { + "lodash": "^4.13.1" + } + }, + "request-promise-native": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", + "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "dev": true, + "requires": { + "request-promise-core": "1.1.1", + "stealthy-require": "^1.1.0", + "tough-cookie": ">=2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "requizzle": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", + "integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=", + "dev": true, + "requires": { + "underscore": "~1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + } + } + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-pkg": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-0.1.0.tgz", + "integrity": "sha1-AsyZNBDik2livZcWahsHfalyVTE=", + "dev": true, + "requires": { + "resolve-from": "^2.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", + "dev": true + } + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "revalidator": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", + "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=", + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sanitize-html": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.19.1.tgz", + "integrity": "sha512-zNYr6FvBn4bZukr9x2uny6od/9YdjCLwF+FqxivqI0YOt/m9GIxfX+tWhm52tBAPUXiTTb4bJTGVagRz5b06bw==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "htmlparser2": "^3.9.0", + "lodash.clonedeep": "^4.5.0", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.mergewith": "^4.6.0", + "postcss": "^6.0.14", + "srcset": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "htmlparser2": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", + "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.0.6" + } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "readable-stream": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", + "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" + } + }, + "sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "dev": true, + "requires": { + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0", + "semver": "^5.5.0" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "scryptsy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.0.0.tgz", + "integrity": "sha1-Jiw28CMc+nZU4jY/o5TNLexm83g=" + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selfsigned": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", + "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "dev": true, + "requires": { + "node-forge": "0.7.5" + }, + "dependencies": { + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "dev": true + } + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz", + "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==", + "dev": true + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shelljs": { + "version": "0.3.0", + "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sitemap": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-2.1.0.tgz", + "integrity": "sha512-AkfA7RDVCITQo+j5CpXsMJlZ/8ENO2NtgMHYIh+YMvex2Hao/oe3MQgNa03p0aWY6srCfUA1Q02OgiWCAiuccA==", + "dev": true, + "requires": { + "lodash": "^4.17.10", + "url-join": "^4.0.0", + "xmlbuilder": "^10.0.0" + } + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + } + } + }, + "smart-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", + "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==", + "dev": true + }, + "snackbarjs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/snackbarjs/-/snackbarjs-1.1.0.tgz", + "integrity": "sha1-pont9ExxEEdzvPIhxGk3ZosLvNY=" + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "sockjs-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", + "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "dev": true, + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", + "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "socks": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.2.tgz", + "integrity": "sha512-g6wjBnnMOZpE0ym6e0uHSddz9p3a+WsBaaYQaBaSCJYvrC4IXykQR9MNGjLQf38e9iIIhp3b1/Zk8YZI3KGJ0Q==", + "dev": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.0.1" + } + }, + "socks-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", + "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", + "dev": true, + "requires": { + "agent-base": "~4.2.0", + "socks": "~2.2.0" + } + }, + "sortablejs": { + "version": "1.8.0-rc1", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.8.0-rc1.tgz", + "integrity": "sha512-umyNbQVDwRgc0SZvUB+FRUIUqACnu5vCCmK0zv/xWA3eDSOh+IZsg3GHdWvEOcUBwnykqyk760+YPgVa8HfxFg==" + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz", + "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz", + "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg==", + "dev": true + }, + "spdy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", + "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "optional": true, + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "split.js": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.5.10.tgz", + "integrity": "sha512-/J52X5c4ZypVwu4WAhD8E1T9uXQtNokvG6mIBHauzyA1aKH6bmETVSv3RPjBXEz6Gcc4mIThgmjGQL39LD16jQ==" + }, + "split2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.1.1.tgz", + "integrity": "sha1-Fi2bGIZfAqsvKtlYVSLbm1TEgfk=", + "dev": true, + "requires": { + "through2": "~2.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "srcset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", + "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", + "dev": true, + "requires": { + "array-uniq": "^1.0.2", + "number-is-nan": "^1.0.0" + } + }, + "ssdeep.js": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/ssdeep.js/-/ssdeep.js-0.0.2.tgz", + "integrity": "sha1-mItJTQ3JwxkAX9rJZj1jOO/tHyI=" + }, + "sshpk": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + } + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, + "static-eval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.0.tgz", + "integrity": "sha512-6flshd3F1Gwm+Ksxq463LtFd1liC77N/PX1FVVc3OzL3hAmo2fwHFbuArkcfi7s9rTNsLEhcRmXGFZhlgy40uw==", + "requires": { + "escodegen": "^1.8.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "style-loader": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", + "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, + "table": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.2.tgz", + "integrity": "sha512-f8mJmuu9beQEDkKHLzOv4VxVYlU68NpdzjbGPl69i4Hx0sTopJuNxuzJd17iV2h24dAfa93u794OnDA5jqXvfQ==", + "dev": true, + "requires": { + "ajv": "^6.6.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.0.0", + "string-width": "^2.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + } + } + }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, + "tapable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz", + "integrity": "sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==", + "dev": true + }, + "tar": { + "version": "2.2.1", + "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "tcp-port-used": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.1.tgz", + "integrity": "sha512-rwi5xJeU6utXoEIiMvVBMc9eJ2/ofzB+7nLOdnZuFTmNCLqRiQh2sMG9MqCxHU/69VC/Fwp5dV9306Qd54ll1Q==", + "dev": true, + "requires": { + "debug": "4.1.0", + "is2": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "terser": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.14.0.tgz", + "integrity": "sha512-KQC1QNKbC/K1ZUjLIWsezW7wkTJuB4v9ptQQUNOzAPVHuVf2LrwEcB0I9t2HTEYUwAFVGiiS6wc+P4ClLDc5FQ==", + "dev": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1", + "source-map-support": "~0.5.6" + } + }, + "terser-webpack-plugin": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.1.tgz", + "integrity": "sha512-GGSt+gbT0oKcMDmPx4SRSfJPE1XaN3kQRWG4ghxKQw9cn5G9x6aCKSsgYdvyM0na9NJ4Drv0RG6jbBByZ5CMjw==", + "dev": true, + "requires": { + "cacache": "^11.0.2", + "find-cache-dir": "^2.0.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "terser": "^3.8.1", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", + "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "thirty-two": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-0.0.2.tgz", + "integrity": "sha1-QlPinYywWPBIAmfFaYwOSSflS2o=" + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "thunkify": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", + "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=", + "dev": true + }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "timm": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.6.1.tgz", + "integrity": "sha512-hqDTYi/bWuDxL2i6T3v6nrvkAQ/1Bc060GSkVEQZp02zTSTB4CHSKsOkliequCftQaNRcjRqUZmpGWs5FfhrNg==" + }, + "tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "requires": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "tinycolor2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "triplesec": { + "version": "3.0.27", + "resolved": "https://registry.npmjs.org/triplesec/-/triplesec-3.0.27.tgz", + "integrity": "sha512-FDhkxa3JYnPOerOd+8k+SBmm7cb7KkyX+xXwNFV3XV6dsQgHuRvjtbnzWfPJ2kimeR8ErjZfPd/6r7RH6epHDw==", + "requires": { + "iced-error": ">=0.0.9", + "iced-lock": "^1.0.1", + "iced-runtime": "^1.0.2", + "more-entropy": ">=0.0.7", + "progress": "~1.1.2", + "uglify-js": "^3.1.9" + } + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "requires": { + "glob": "^7.1.2" + } + }, + "tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", + "dev": true + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz", + "integrity": "sha1-1ii1bzvMPVrnS6nUwacE3vWrS1Y=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "ua-parser-js": { + "version": "0.7.19", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", + "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + } + }, + "uint64be": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uint64be/-/uint64be-1.0.1.tgz", + "integrity": "sha1-H3FUIC8qG4rzU4cd2mUb80zpPpU=" + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" + }, + "underscore-contrib": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/underscore-contrib/-/underscore-contrib-0.3.0.tgz", + "integrity": "sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=", + "dev": true, + "requires": { + "underscore": "1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + } + } + }, + "underscore.string": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", + "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", + "dev": true, + "requires": { + "sprintf-js": "^1.0.3", + "util-deprecate": "^1.0.2" + } + }, + "unicode-5.2.0": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/unicode-5.2.0/-/unicode-5.2.0-0.7.5.tgz", + "integrity": "sha512-KVGLW1Bri30x00yv4HNM8kBxoqFXr0Sbo55735nvrlsx4PYBZol3UtoWgO492fSwmsetzPEZzy73rbU8OGXJcA==", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz", + "integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz", + "integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==", + "dev": true + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", + "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unixify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", + "integrity": "sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA=", + "dev": true, + "requires": { + "normalize-path": "^2.1.1" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-join": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", + "integrity": "sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=", + "dev": true + }, + "url-loader": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", + "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "mime": "^2.0.3", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "url-parse": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", + "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", + "dev": true, + "requires": { + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" + }, + "utif": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", + "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", + "requires": { + "pako": "^1.0.5" + } + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "utile": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", + "integrity": "sha1-E1LDQOuCDk2N26A5pPv6oy7U7zo=", + "dev": true, + "requires": { + "async": "~0.9.0", + "deep-equal": "~0.2.1", + "i": "0.3.x", + "mkdirp": "0.x.x", + "ncp": "1.0.x", + "rimraf": "2.x.x" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "deep-equal": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", + "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=", + "dev": true + } + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "valid-data-url": { + "version": "0.1.6", + "resolved": "http://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.6.tgz", + "integrity": "sha512-FXg2qXMzfAhZc0y2HzELNfUeiOjPr+52hU1DNBWiJJ2luXD+dD1R9NA48Ug5aj0ibbxroeGDc/RJv6ThiGgkDw==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validator": { + "version": "9.4.1", + "resolved": "http://registry.npmjs.org/validator/-/validator-9.4.1.tgz", + "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vkbeautify": { + "version": "0.99.3", + "resolved": "https://registry.npmjs.org/vkbeautify/-/vkbeautify-0.99.3.tgz", + "integrity": "sha512-2ozZEFfmVvQcHWoHLNuiKlUfDKlhh4KGsy54U0UrlLMR1SO+XKAIDqBxtBwHgNrekurlJwE8A9K6L49T78ZQ9Q==" + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "web-resource-inliner": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-4.2.1.tgz", + "integrity": "sha512-fOWnBQHVX8zHvEbECDTxtYL0FXIIZZ5H3LWoez8mGopYJK7inEru1kVMDzM1lVdeJBNEqUnNP5FBGxvzuMcwwQ==", + "dev": true, + "requires": { + "async": "^2.1.2", + "chalk": "^1.1.3", + "datauri": "^1.0.4", + "htmlparser2": "^3.9.2", + "lodash.unescape": "^4.0.1", + "request": "^2.78.0", + "valid-data-url": "^0.1.4", + "xtend": "^4.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "1.3.0", + "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "htmlparser2": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", + "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.0.6" + } + }, + "readable-stream": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", + "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "webpack": { + "version": "4.28.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.28.3.tgz", + "integrity": "sha512-vLZN9k5I7Nr/XB1IDG9GbZB4yQd1sPuvufMFgJkx0b31fi2LD97KQIjwjxE7xytdruAYfu5S0FLBLjdxmwGJCg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/wasm-edit": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "acorn": "^5.6.2", + "acorn-dynamic-import": "^3.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.1.0", + "terser-webpack-plugin": "^1.1.0", + "watchpack": "^1.5.0", + "webpack-sources": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + } + } + }, + "webpack-bundle-analyzer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.3.tgz", + "integrity": "sha512-naLWiRfmtH4UJgtUktRTLw6FdoZJ2RvCR9ePbwM9aRMsS/KjFerkPZG9epEvXRAw5d5oPdrs9+3p+afNjxW8Xw==", + "dev": true, + "requires": { + "acorn": "^5.7.3", + "bfj": "^6.1.1", + "chalk": "^2.4.1", + "commander": "^2.18.0", + "ejs": "^2.6.1", + "express": "^4.16.3", + "filesize": "^3.6.1", + "gzip-size": "^5.0.0", + "lodash": "^4.17.10", + "mkdirp": "^0.5.1", + "opener": "^1.5.1", + "ws": "^6.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "ws": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz", + "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz", + "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", + "dev": true, + "requires": { + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "range-parser": "^1.0.3", + "webpack-log": "^2.0.0" + } + }, + "webpack-dev-server": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz", + "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.18.0", + "import-local": "^2.0.0", + "internal-ip": "^3.0.1", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "schema-utils": "^1.0.0", + "selfsigned": "^1.9.1", + "semver": "^5.6.0", + "serve-index": "^1.7.2", + "sockjs": "0.3.19", + "sockjs-client": "1.3.0", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "3.4.0", + "webpack-log": "^2.0.0", + "yargs": "12.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "requires": { + "xregexp": "4.0.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, + "yargs": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-node-externals": { + "version": "1.7.2", + "resolved": "http://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", + "integrity": "sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg==", + "dev": true + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "websocket-driver": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "websocket-stream": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.1.2.tgz", + "integrity": "sha512-lchLOk435iDWs0jNuL+hiU14i3ERSrMA0IKSiJh7z6X/i4XNsutBZrtqu2CPOZuA4G/zabiqVAos0vW+S7GEVw==", + "dev": true, + "requires": { + "duplexify": "^3.5.1", + "inherits": "^2.0.1", + "readable-stream": "^2.3.3", + "safe-buffer": "^5.1.1", + "ws": "^3.2.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } + } + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz", + "integrity": "sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "winston": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q==", + "dev": true, + "optional": true, + "requires": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "dependencies": { + "async": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", + "dev": true, + "optional": true + }, + "colors": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true, + "optional": true + } + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, + "worker-farm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", + "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "worker-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz", + "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==", + "dev": true, + "requires": { + "loader-utils": "^1.0.0", + "schema-utils": "^0.4.0" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xhr": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", + "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", + "requires": { + "global": "~4.3.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha1-qQKekp09vN7RafPG4oI42VpdWig=" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + }, + "dependencies": { + "xmlbuilder": { + "version": "9.0.7", + "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + } + } + }, + "xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "dev": true + }, + "xmlcreate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", + "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=", + "dev": true + }, + "xmldom": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" + }, + "xpath": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", + "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" + }, + "xregexp": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.2.4.tgz", + "integrity": "sha512-sO0bYdYeJAJBcJA8g7MJJX7UrOZIfJPd8U2SC7B2Dd/J24U0aQNoGp33shCaBSWeb0rD5rh6VBUIXOkGal1TZA==", + "requires": { + "@babel/runtime-corejs2": "^7.2.0" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + } + } + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "~1.0.1" + } + }, + "zlibjs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", + "integrity": "sha1-UBl+2yihxCymWcyLTmqd3W1ERVQ=" + } + } +} diff --git a/package.json b/package.json index 09a08036..065adba4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "5.7.2", + "version": "8.24.2", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", @@ -30,69 +30,121 @@ "main": "build/node/CyberChef.js", "bugs": "https://github.com/gchq/CyberChef/issues", "devDependencies": { - "babel-core": "^6.24.0", - "babel-loader": "^6.4.0", - "babel-polyfill": "^6.23.0", - "babel-preset-env": "^1.2.2", - "css-loader": "^0.27.3", - "exports-loader": "^0.6.4", - "extract-text-webpack-plugin": "^2.1.0", - "file-loader": "^0.10.1", - "grunt": ">=0.4.5", - "grunt-accessibility": "~5.0.0", + "@babel/core": "^7.2.2", + "@babel/preset-env": "^7.2.3", + "autoprefixer": "^9.4.3", + "babel-eslint": "^10.0.1", + "babel-loader": "^8.0.4", + "babel-plugin-syntax-dynamic-import": "^6.18.0", + "bootstrap": "^4.2.1", + "chromedriver": "^2.45.0", + "colors": "^1.3.3", + "css-loader": "^2.1.0", + "eslint": "^5.12.1", + "exports-loader": "^0.7.0", + "file-loader": "^3.0.1", + "grunt": "^1.0.3", + "grunt-accessibility": "~6.0.0", "grunt-chmod": "~1.1.1", - "grunt-contrib-clean": "~1.0.0", + "grunt-concurrent": "^2.3.1", + "grunt-contrib-clean": "~2.0.0", + "grunt-contrib-connect": "^2.0.0", "grunt-contrib-copy": "~1.0.0", - "grunt-eslint": "^19.0.0", - "grunt-exec": "~1.0.1", - "grunt-execute": "^0.2.2", - "grunt-jsdoc": "^2.1.0", - "grunt-webpack": "^2.0.1", - "html-webpack-plugin": "^2.28.0", - "imports-loader": "^0.7.1", - "ink-docstrap": "^1.1.4", - "jsdoc-babel": "^0.3.0", - "less": "^2.7.2", - "less-loader": "^4.0.3", - "postcss-css-variables": "^0.7.0", - "postcss-import": "^10.0.0", - "postcss-loader": "^2.0.5", - "style-loader": "^0.15.0", - "url-loader": "^0.5.8", - "web-resource-inliner": "^4.1.0", - "webpack": "^2.2.1" + "grunt-contrib-watch": "^1.1.0", + "grunt-eslint": "^21.0.0", + "grunt-exec": "~3.0.0", + "grunt-jsdoc": "^2.3.0", + "grunt-webpack": "^3.1.3", + "html-webpack-plugin": "^3.2.0", + "imports-loader": "^0.8.0", + "ink-docstrap": "^1.3.2", + "jsdoc-babel": "^0.5.0", + "mini-css-extract-plugin": "^0.5.0", + "nightwatch": "^1.0.18", + "node-sass": "^4.11.0", + "postcss-css-variables": "^0.11.0", + "postcss-import": "^12.0.1", + "postcss-loader": "^3.0.0", + "prompt": "^1.0.0", + "sass-loader": "^7.1.0", + "sitemap": "^2.1.0", + "style-loader": "^0.23.1", + "url-loader": "^1.1.2", + "web-resource-inliner": "^4.2.1", + "webpack": "^4.28.3", + "webpack-bundle-analyzer": "^3.0.3", + "webpack-dev-server": "^3.1.14", + "webpack-node-externals": "^1.7.2", + "worker-loader": "^2.0.0" }, "dependencies": { - "bootstrap": "^3.3.7", - "bootstrap-colorpicker": "^2.5.1", - "bootstrap-switch": "^3.3.4", - "crypto-api": "^0.6.2", + "arrive": "^2.4.1", + "babel-plugin-transform-builtin-extend": "1.1.2", + "babel-polyfill": "^6.26.0", + "bcryptjs": "^2.4.3", + "bignumber.js": "^8.0.2", + "bootstrap-colorpicker": "^2.5.3", + "bootstrap-material-design": "^4.1.1", + "bson": "^4.0.1", + "chi-squared": "^1.1.0", + "crypto-api": "^0.8.3", "crypto-js": "^3.1.9-1", "d3": "^4.9.1", "d3-hexbin": "^0.2.2", - "diff": "^3.2.0", - "escodegen": "^1.8.1", + "ctph.js": "0.0.5", + "diff": "^3.5.0", + "es6-promisify": "^6.0.1", + "escodegen": "^1.11.0", "esmangle": "^1.0.1", - "esprima": "^3.1.3", - "exif-parser": "^0.1.9", - "google-code-prettify": "^1.0.5", - "jquery": "^3.1.1", - "jsbn": "^1.1.0", - "jsrsasign": "7.1.3", - "lodash": "^4.17.4", - "moment": "^2.17.1", - "moment-timezone": "^0.5.11", - "sladex-blowfish": "^0.8.1", - "sortablejs": "^1.5.1", - "split.js": "^1.2.0", - "vkbeautify": "^0.99.1", + "esprima": "^4.0.1", + "exif-parser": "^0.1.12", + "file-saver": "^2.0.0", + "geodesy": "^1.1.3", + "highlight.js": "^9.13.1", + "jimp": "^0.6.0", + "jquery": "^3.3.1", + "js-crc": "^0.2.0", + "js-sha3": "^0.8.0", + "jsesc": "^2.5.2", + "jsonpath": "^1.0.0", + "jsonwebtoken": "^8.4.0", + "jsqr": "^1.1.1", + "jsrsasign": "8.0.12", + "kbpgp": "^2.0.82", + "libyara-wasm": "0.0.11", + "lodash": "^4.17.11", + "loglevel": "^1.6.1", + "loglevel-message-prefix": "^3.0.0", + "moment": "^2.23.0", + "moment-timezone": "^0.5.23", + "ngeohash": "^0.6.3", + "node-forge": "^0.7.6", + "node-md6": "^0.1.0", + "notepack.io": "^2.2.0", + "nwmatcher": "^1.4.4", + "otp": "^0.1.3", + "popper.js": "^1.14.6", + "qr-image": "^3.2.0", + "scryptsy": "^2.0.0", + "snackbarjs": "^1.1.0", + "sortablejs": "^1.8.0-rc1", + "split.js": "^1.5.10", + "ssdeep.js": "0.0.2", + "ua-parser-js": "^0.7.19", + "utf8": "^3.0.0", + "vkbeautify": "^0.99.3", "xmldom": "^0.1.27", - "xpath": "0.0.24", - "zlibjs": "^0.2.0" + "xpath": "0.0.27", + "xregexp": "^4.2.4", + "zlibjs": "^0.3.1" }, "scripts": { + "start": "grunt dev", "build": "grunt prod", "test": "grunt test", - "docs": "grunt docs" + "testui": "grunt testui", + "docs": "grunt docs", + "lint": "grunt lint", + "newop": "node --experimental-modules src/core/config/scripts/newOperation.mjs" } } diff --git a/src/core/Chef.js b/src/core/Chef.js deleted file mode 100755 index 9fa23d22..00000000 --- a/src/core/Chef.js +++ /dev/null @@ -1,126 +0,0 @@ -import Dish from "./Dish.js"; -import Recipe from "./Recipe.js"; - - -/** - * The main controller for CyberChef. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - */ -const Chef = function() { - this.dish = new Dish(); -}; - - -/** - * Runs the recipe over the input. - * - * @param {string} inputText - The input data as a string - * @param {Object[]} recipeConfig - The recipe configuration object - * @param {Object} options - The options object storing various user choices - * @param {boolean} options.attempHighlight - Whether or not to attempt highlighting - * @param {number} progress - The position in the recipe to start from - * @param {number} [step] - Whether to only execute one operation in the recipe - * - * @returns {Object} response - * @returns {string} response.result - The output of the recipe - * @returns {string} response.type - The data type of the result - * @returns {number} response.progress - The position that we have got to in the recipe - * @returns {number} response.options - The app options object (which may have been changed) - * @returns {number} response.duration - The number of ms it took to execute the recipe - * @returns {number} response.error - The error object thrown by a failed operation (false if no error) -*/ -Chef.prototype.bake = async function(inputText, recipeConfig, options, progress, step) { - let startTime = new Date().getTime(), - recipe = new Recipe(recipeConfig), - containsFc = recipe.containsFlowControl(), - error = false; - - // Reset attemptHighlight flag - if (options.hasOwnProperty("attemptHighlight")) { - options.attemptHighlight = true; - } - - if (containsFc) options.attemptHighlight = false; - - // Clean up progress - if (progress >= recipeConfig.length) { - progress = 0; - } - - if (step) { - // Unset breakpoint on this step - recipe.setBreakpoint(progress, false); - // Set breakpoint on next step - recipe.setBreakpoint(progress + 1, true); - } - - // If stepping with flow control, we have to start from the beginning - // but still want to skip all previous breakpoints - if (progress > 0 && containsFc) { - recipe.removeBreaksUpTo(progress); - progress = 0; - } - - // If starting from scratch, load data - if (progress === 0) { - this.dish.set(inputText, Dish.STRING); - } - - try { - progress = await recipe.execute(this.dish, progress); - } catch (err) { - // Return the error in the result so that everything else gets correctly updated - // rather than throwing it here and losing state info. - error = err; - progress = err.progress; - } - - return { - result: this.dish.type === Dish.HTML ? - this.dish.get(Dish.HTML) : - this.dish.get(Dish.STRING), - type: Dish.enumLookup(this.dish.type), - progress: progress, - options: options, - duration: new Date().getTime() - startTime, - error: error - }; -}; - - -/** - * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs, - * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a - * minute, we run a silent bake which will force the browser to load and cache all the relevant - * JavaScript code needed to do a real bake. - * - * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a - * long time and the browser has swapped out all its memory. - * - * The output will not be modified (hence "silent" bake). - * - * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load - * the recipe, ingredients and dish. - * - * @param {Object[]} recipeConfig - The recipe configuration object - * @returns {number} The time it took to run the silent bake in milliseconds. -*/ -Chef.prototype.silentBake = function(recipeConfig) { - let startTime = new Date().getTime(), - recipe = new Recipe(recipeConfig), - dish = new Dish("", Dish.STRING); - - try { - recipe.execute(dish); - } catch (err) { - // Suppress all errors - } - return new Date().getTime() - startTime; -}; - -export default Chef; diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs new file mode 100755 index 00000000..ed111b65 --- /dev/null +++ b/src/core/Chef.mjs @@ -0,0 +1,198 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Dish from "./Dish"; +import Recipe from "./Recipe"; +import log from "loglevel"; + +/** + * The main controller for CyberChef. + */ +class Chef { + + /** + * Chef constructor + */ + constructor() { + this.dish = new Dish(); + } + + + /** + * Runs the recipe over the input. + * + * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer + * @param {Object[]} recipeConfig - The recipe configuration object + * @param {Object} options - The options object storing various user choices + * @param {boolean} options.attempHighlight - Whether or not to attempt highlighting + * @param {number} progress - The position in the recipe to start from + * @param {number} [step] - Whether to only execute one operation in the recipe + * + * @returns {Object} response + * @returns {string} response.result - The output of the recipe + * @returns {string} response.type - The data type of the result + * @returns {number} response.progress - The position that we have got to in the recipe + * @returns {number} response.duration - The number of ms it took to execute the recipe + * @returns {number} response.error - The error object thrown by a failed operation (false if no error) + */ + async bake(input, recipeConfig, options, progress, step) { + log.debug("Chef baking"); + const startTime = new Date().getTime(), + recipe = new Recipe(recipeConfig), + containsFc = recipe.containsFlowControl(), + notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8; + let error = false; + + if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); + + // Clean up progress + if (progress >= recipeConfig.length) { + progress = 0; + } + + if (step) { + // Unset breakpoint on this step + recipe.setBreakpoint(progress, false); + // Set breakpoint on next step + recipe.setBreakpoint(progress + 1, true); + } + + // If the previously run operation presented a different value to its + // normal output, we need to recalculate it. + if (recipe.lastOpPresented(progress)) { + progress = 0; + } + + // If stepping with flow control, we have to start from the beginning + // but still want to skip all previous breakpoints + if (progress > 0 && containsFc) { + recipe.removeBreaksUpTo(progress); + progress = 0; + } + + // If starting from scratch, load data + if (progress === 0) { + const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING; + this.dish.set(input, type); + } + + try { + progress = await recipe.execute(this.dish, progress); + } catch (err) { + log.error(err); + error = { + displayStr: err.displayStr, + }; + progress = err.progress; + } + + // Depending on the size of the output, we may send it back as a string or an ArrayBuffer. + // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file. + // The threshold is specified in KiB. + const threshold = (options.ioDisplayThreshold || 1024) * 1024; + const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING; + + // Create a raw version of the dish, unpresented + const rawDish = this.dish.clone(); + + // Present the raw result + await recipe.present(this.dish); + + return { + dish: rawDish, + result: this.dish.type === Dish.HTML ? + await this.dish.get(Dish.HTML, notUTF8) : + await this.dish.get(returnType, notUTF8), + type: Dish.enumLookup(this.dish.type), + progress: progress, + duration: new Date().getTime() - startTime, + error: error + }; + } + + + /** + * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs, + * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a + * minute, we run a silent bake which will force the browser to load and cache all the relevant + * JavaScript code needed to do a real bake. + * + * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a + * long time and the browser has swapped out all its memory. + * + * The output will not be modified (hence "silent" bake). + * + * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load + * the recipe, ingredients and dish. + * + * @param {Object[]} recipeConfig - The recipe configuration object + * @returns {number} The time it took to run the silent bake in milliseconds. + */ + silentBake(recipeConfig) { + log.debug("Running silent bake"); + + const startTime = new Date().getTime(), + recipe = new Recipe(recipeConfig), + dish = new Dish(); + + try { + recipe.execute(dish); + } catch (err) { + // Suppress all errors + } + return new Date().getTime() - startTime; + } + + + /** + * Calculates highlight offsets if possible. + * + * @param {Object[]} recipeConfig + * @param {string} direction + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + * @returns {Object} + */ + async calculateHighlights(recipeConfig, direction, pos) { + const recipe = new Recipe(recipeConfig); + const highlights = await recipe.generateHighlightList(); + + if (!highlights) return false; + + for (let i = 0; i < highlights.length; i++) { + // Remove multiple highlights before processing again + pos = [pos[0]]; + + const func = direction === "forward" ? highlights[i].f : highlights[i].b; + + if (typeof func == "function") { + pos = func(pos, highlights[i].args); + } + } + + return { + pos: pos, + direction: direction + }; + } + + + /** + * Translates the dish to a specified type and returns it. + * + * @param {Dish} dish + * @param {string} type + * @returns {Dish} + */ + async getDishAs(dish, type) { + const newDish = new Dish(dish); + return await newDish.get(type); + } + +} + +export default Chef; diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js new file mode 100644 index 00000000..a243f32c --- /dev/null +++ b/src/core/ChefWorker.js @@ -0,0 +1,235 @@ +/** + * Web Worker to handle communications between the front-end and the core. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import "babel-polyfill"; +import Chef from "./Chef"; +import OperationConfig from "./config/OperationConfig.json"; +import OpModules from "./config/modules/OpModules"; + +// Add ">" to the start of all log messages in the Chef Worker +import loglevelMessagePrefix from "loglevel-message-prefix"; + +loglevelMessagePrefix(log, { + prefixes: [], + staticPrefixes: [">"], + prefixFormat: "%p" +}); + + +// Set up Chef instance +self.chef = new Chef(); + +self.OpModules = OpModules; +self.OperationConfig = OperationConfig; + +// Tell the app that the worker has loaded and is ready to operate +self.postMessage({ + action: "workerLoaded", + data: {} +}); + +/** + * Respond to message from parent thread. + * + * Messages should have the following format: + * { + * action: "bake" | "silentBake", + * data: { + * input: {string}, + * recipeConfig: {[Object]}, + * options: {Object}, + * progress: {number}, + * step: {boolean} + * } | undefined + * } + */ +self.addEventListener("message", function(e) { + // Handle message + const r = e.data; + log.debug("ChefWorker receiving command '" + r.action + "'"); + + switch (r.action) { + case "bake": + bake(r.data); + break; + case "silentBake": + silentBake(r.data); + break; + case "getDishAs": + getDishAs(r.data); + break; + case "docURL": + // Used to set the URL of the current document so that scripts can be + // imported into an inline worker. + self.docURL = r.data; + break; + case "highlight": + calculateHighlights( + r.data.recipeConfig, + r.data.direction, + r.data.pos + ); + break; + case "setLogLevel": + log.setLevel(r.data, false); + break; + default: + break; + } +}); + + +/** + * Baking handler + * + * @param {Object} data + */ +async function bake(data) { + // Ensure the relevant modules are loaded + self.loadRequiredModules(data.recipeConfig); + + try { + const response = await self.chef.bake( + data.input, // The user's input + data.recipeConfig, // The configuration of the recipe + data.options, // Options set by the user + data.progress, // The current position in the recipe + data.step // Whether or not to take one step or execute the whole recipe + ); + + self.postMessage({ + action: "bakeComplete", + data: Object.assign(response, { + id: data.id + }) + }); + } catch (err) { + self.postMessage({ + action: "bakeError", + data: Object.assign(err, { + id: data.id + }) + }); + } +} + + +/** + * Silent baking handler + */ +function silentBake(data) { + const duration = self.chef.silentBake(data.recipeConfig); + + self.postMessage({ + action: "silentBakeComplete", + data: duration + }); +} + + +/** + * Translates the dish to a given type. + */ +async function getDishAs(data) { + const value = await self.chef.getDishAs(data.dish, data.type); + + self.postMessage({ + action: "dishReturned", + data: { + value: value, + id: data.id + } + }); +} + + +/** + * Calculates highlight offsets if possible. + * + * @param {Object[]} recipeConfig + * @param {string} direction + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + */ +async function calculateHighlights(recipeConfig, direction, pos) { + pos = await self.chef.calculateHighlights(recipeConfig, direction, pos); + + self.postMessage({ + action: "highlightsCalculated", + data: pos + }); +} + + +/** + * Checks that all required modules are loaded and loads them if not. + * + * @param {Object} recipeConfig + */ +self.loadRequiredModules = function(recipeConfig) { + recipeConfig.forEach(op => { + const module = self.OperationConfig[op.op].module; + + if (!OpModules.hasOwnProperty(module)) { + log.info(`Loading ${module} module`); + self.sendStatusMessage(`Loading ${module} module`); + self.importScripts(`${self.docURL}/${module}.js`); + self.sendStatusMessage(""); + } + }); +}; + + +/** + * Send status update to the app. + * + * @param {string} msg + */ +self.sendStatusMessage = function(msg) { + self.postMessage({ + action: "statusMessage", + data: msg + }); +}; + + +/** + * Send an option value update to the app. + * + * @param {string} option + * @param {*} value + */ +self.setOption = function(option, value) { + self.postMessage({ + action: "optionUpdate", + data: { + option: option, + value: value + } + }); +}; + + +/** + * Send register values back to the app. + * + * @param {number} opIndex + * @param {number} numPrevRegisters + * @param {string[]} registers + */ +self.setRegisters = function(opIndex, numPrevRegisters, registers) { + self.postMessage({ + action: "setRegisters", + data: { + opIndex: opIndex, + numPrevRegisters: numPrevRegisters, + registers: registers + } + }); +}; diff --git a/src/core/Dish.js b/src/core/Dish.js deleted file mode 100755 index 914188c1..00000000 --- a/src/core/Dish.js +++ /dev/null @@ -1,206 +0,0 @@ -import Utils from "./Utils.js"; - -/** - * The data being operated on by each operation. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {byteArray|string|number} value - The value of the input data. - * @param {number} type - The data type of value, see Dish enums. - */ -const Dish = function(value, type) { - this.value = value || typeof value == "string" ? value : null; - this.type = type || Dish.BYTE_ARRAY; -}; - - -/** - * Dish data type enum for byte arrays. - * @readonly - * @enum - */ -Dish.BYTE_ARRAY = 0; -/** - * Dish data type enum for strings. - * @readonly - * @enum - */ -Dish.STRING = 1; -/** - * Dish data type enum for numbers. - * @readonly - * @enum - */ -Dish.NUMBER = 2; -/** - * Dish data type enum for HTML. - * @readonly - * @enum - */ -Dish.HTML = 3; - - -/** - * Returns the data type enum for the given type string. - * - * @static - * @param {string} typeStr - The name of the data type. - * @returns {number} The data type enum value. - */ -Dish.typeEnum = function(typeStr) { - switch (typeStr) { - case "byteArray": - case "Byte array": - return Dish.BYTE_ARRAY; - case "string": - case "String": - return Dish.STRING; - case "number": - case "Number": - return Dish.NUMBER; - case "html": - case "HTML": - return Dish.HTML; - default: - throw "Invalid data type string. No matching enum."; - } -}; - - -/** - * Returns the data type string for the given type enum. - * - * @static - * @param {string} typeEnum - The enum value of the data type. - * @returns {number} The data type as a string. - */ -Dish.enumLookup = function(typeEnum) { - switch (typeEnum) { - case Dish.BYTE_ARRAY: - return "byteArray"; - case Dish.STRING: - return "string"; - case Dish.NUMBER: - return "number"; - case Dish.HTML: - return "html"; - default: - throw "Invalid data type enum. No matching type."; - } -}; - - -/** - * Sets the data value and type and then validates them. - * - * @param {byteArray|string|number} value - The value of the input data. - * @param {number} type - The data type of value, see Dish enums. - */ -Dish.prototype.set = function(value, type) { - this.value = value; - this.type = type; - - if (!this.valid()) { - const sample = Utils.truncate(JSON.stringify(this.value), 13); - throw "Data is not a valid " + Dish.enumLookup(type) + ": " + sample; - } -}; - - -/** - * Returns the value of the data in the type format specified. - * - * @param {number} type - The data type of value, see Dish enums. - * @returns {byteArray|string|number} The value of the output data. - */ -Dish.prototype.get = function(type) { - if (this.type !== type) { - this.translate(type); - } - return this.value; -}; - - -/** - * Translates the data to the given type format. - * - * @param {number} toType - The data type of value, see Dish enums. - */ -Dish.prototype.translate = function(toType) { - // Convert data to intermediate byteArray type - switch (this.type) { - case Dish.STRING: - this.value = this.value ? Utils.strToByteArray(this.value) : []; - this.type = Dish.BYTE_ARRAY; - break; - case Dish.NUMBER: - this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : []; - this.type = Dish.BYTE_ARRAY; - break; - case Dish.HTML: - this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : []; - this.type = Dish.BYTE_ARRAY; - break; - default: - break; - } - - // Convert from byteArray to toType - switch (toType) { - case Dish.STRING: - case Dish.HTML: - this.value = this.value ? Utils.byteArrayToUtf8(this.value) : ""; - this.type = Dish.STRING; - break; - case Dish.NUMBER: - this.value = this.value ? parseFloat(Utils.byteArrayToUtf8(this.value)) : 0; - this.type = Dish.NUMBER; - break; - default: - break; - } -}; - - -/** - * Validates that the value is the type that has been specified. - * May have to disable parts of BYTE_ARRAY validation if it effects performance. - * - * @returns {boolean} Whether the data is valid or not. -*/ -Dish.prototype.valid = function() { - switch (this.type) { - case Dish.BYTE_ARRAY: - if (!(this.value instanceof Array)) { - return false; - } - - // Check that every value is a number between 0 - 255 - for (let i = 0; i < this.value.length; i++) { - if (typeof this.value[i] != "number" || - this.value[i] < 0 || - this.value[i] > 255) { - return false; - } - } - return true; - case Dish.STRING: - case Dish.HTML: - if (typeof this.value == "string") { - return true; - } - return false; - case Dish.NUMBER: - if (typeof this.value == "number") { - return true; - } - return false; - default: - return false; - } -}; - -export default Dish; diff --git a/src/core/Dish.mjs b/src/core/Dish.mjs new file mode 100755 index 00000000..0635eb75 --- /dev/null +++ b/src/core/Dish.mjs @@ -0,0 +1,434 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "./Utils"; +import DishError from "./errors/DishError"; +import BigNumber from "bignumber.js"; +import log from "loglevel"; + +/** + * The data being operated on by each operation. + */ +class Dish { + + /** + * Dish constructor + * + * @param {Dish} [dish=null] - A dish to clone + */ + constructor(dish=null) { + this.value = []; + this.type = Dish.BYTE_ARRAY; + + if (dish && + dish.hasOwnProperty("value") && + dish.hasOwnProperty("type")) { + this.set(dish.value, dish.type); + } + } + + + /** + * Returns the data type enum for the given type string. + * + * @param {string} typeStr - The name of the data type. + * @returns {number} The data type enum value. + */ + static typeEnum(typeStr) { + switch (typeStr.toLowerCase()) { + case "bytearray": + case "byte array": + return Dish.BYTE_ARRAY; + case "string": + return Dish.STRING; + case "number": + return Dish.NUMBER; + case "html": + return Dish.HTML; + case "arraybuffer": + case "array buffer": + return Dish.ARRAY_BUFFER; + case "bignumber": + case "big number": + return Dish.BIG_NUMBER; + case "json": + return Dish.JSON; + case "file": + return Dish.FILE; + case "list": + return Dish.LIST_FILE; + default: + throw new DishError("Invalid data type string. No matching enum."); + } + } + + + /** + * Returns the data type string for the given type enum. + * + * @param {number} typeEnum - The enum value of the data type. + * @returns {string} The data type as a string. + */ + static enumLookup(typeEnum) { + switch (typeEnum) { + case Dish.BYTE_ARRAY: + return "byteArray"; + case Dish.STRING: + return "string"; + case Dish.NUMBER: + return "number"; + case Dish.HTML: + return "html"; + case Dish.ARRAY_BUFFER: + return "ArrayBuffer"; + case Dish.BIG_NUMBER: + return "BigNumber"; + case Dish.JSON: + return "JSON"; + case Dish.FILE: + return "File"; + case Dish.LIST_FILE: + return "List"; + default: + throw new DishError("Invalid data type enum. No matching type."); + } + } + + + /** + * Sets the data value and type and then validates them. + * + * @param {*} value + * - The value of the input data. + * @param {number} type + * - The data type of value, see Dish enums. + */ + set(value, type) { + if (typeof type === "string") { + type = Dish.typeEnum(type); + } + + log.debug("Dish type: " + Dish.enumLookup(type)); + this.value = value; + this.type = type; + + if (!this.valid()) { + const sample = Utils.truncate(JSON.stringify(this.value), 13); + throw new DishError(`Data is not a valid ${Dish.enumLookup(type)}: ${sample}`); + } + } + + + /** + * Returns the value of the data in the type format specified. + * + * @param {number} type - The data type of value, see Dish enums. + * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. + * @returns {*} - The value of the output data. + */ + async get(type, notUTF8=false) { + if (typeof type === "string") { + type = Dish.typeEnum(type); + } + if (this.type !== type) { + await this._translate(type, notUTF8); + } + return this.value; + } + + + /** + * Translates the data to the given type format. + * + * @param {number} toType - The data type of value, see Dish enums. + * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. + */ + async _translate(toType, notUTF8=false) { + log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); + const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8; + + // Convert data to intermediate byteArray type + try { + switch (this.type) { + case Dish.STRING: + this.value = this.value ? Utils.strToByteArray(this.value) : []; + break; + case Dish.NUMBER: + this.value = typeof this.value === "number" ? Utils.strToByteArray(this.value.toString()) : []; + break; + case Dish.HTML: + this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : []; + break; + case Dish.ARRAY_BUFFER: + // Array.from() would be nicer here, but it's slightly slower + this.value = Array.prototype.slice.call(new Uint8Array(this.value)); + break; + case Dish.BIG_NUMBER: + this.value = BigNumber.isBigNumber(this.value) ? Utils.strToByteArray(this.value.toFixed()) : []; + break; + case Dish.JSON: + this.value = this.value ? Utils.strToByteArray(JSON.stringify(this.value, null, 4)) : []; + break; + case Dish.FILE: + this.value = await Utils.readFile(this.value); + this.value = Array.prototype.slice.call(this.value); + break; + case Dish.LIST_FILE: + this.value = await Promise.all(this.value.map(async f => Utils.readFile(f))); + this.value = this.value.map(b => Array.prototype.slice.call(b)); + this.value = [].concat.apply([], this.value); + break; + default: + break; + } + } catch (err) { + throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to byteArray: ${err}`); + } + + this.type = Dish.BYTE_ARRAY; + + // Convert from byteArray to toType + try { + switch (toType) { + case Dish.STRING: + case Dish.HTML: + this.value = this.value ? byteArrayToStr(this.value) : ""; + this.type = Dish.STRING; + break; + case Dish.NUMBER: + this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0; + this.type = Dish.NUMBER; + break; + case Dish.ARRAY_BUFFER: + this.value = new Uint8Array(this.value).buffer; + this.type = Dish.ARRAY_BUFFER; + break; + case Dish.BIG_NUMBER: + try { + this.value = new BigNumber(byteArrayToStr(this.value)); + } catch (err) { + this.value = new BigNumber(NaN); + } + this.type = Dish.BIG_NUMBER; + break; + case Dish.JSON: + this.value = JSON.parse(byteArrayToStr(this.value)); + this.type = Dish.JSON; + break; + case Dish.FILE: + this.value = new File(this.value, "unknown"); + break; + case Dish.LIST_FILE: + this.value = [new File(this.value, "unknown")]; + this.type = Dish.LIST_FILE; + break; + default: + break; + } + } catch (err) { + throw new DishError(`Error translating from byteArray to ${Dish.enumLookup(toType)}: ${err}`); + } + } + + + /** + * Validates that the value is the type that has been specified. + * May have to disable parts of BYTE_ARRAY validation if it effects performance. + * + * @returns {boolean} Whether the data is valid or not. + */ + valid() { + switch (this.type) { + case Dish.BYTE_ARRAY: + if (!(this.value instanceof Array)) { + return false; + } + + // Check that every value is a number between 0 - 255 + for (let i = 0; i < this.value.length; i++) { + if (typeof this.value[i] !== "number" || + this.value[i] < 0 || + this.value[i] > 255) { + return false; + } + } + return true; + case Dish.STRING: + case Dish.HTML: + return typeof this.value === "string"; + case Dish.NUMBER: + return typeof this.value === "number"; + case Dish.ARRAY_BUFFER: + return this.value instanceof ArrayBuffer; + case Dish.BIG_NUMBER: + return BigNumber.isBigNumber(this.value); + case Dish.JSON: + // All values can be serialised in some manner, so we return true in all cases + return true; + case Dish.FILE: + return this.value instanceof File; + case Dish.LIST_FILE: + return this.value instanceof Array && + this.value.reduce((acc, curr) => acc && curr instanceof File, true); + default: + return false; + } + } + + + /** + * Determines how much space the Dish takes up. + * Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish, + * we measure how many bytes are taken up when the number is written as a string. + * + * @returns {number} + */ + get size() { + switch (this.type) { + case Dish.BYTE_ARRAY: + case Dish.STRING: + case Dish.HTML: + return this.value.length; + case Dish.NUMBER: + case Dish.BIG_NUMBER: + return this.value.toString().length; + case Dish.ARRAY_BUFFER: + return this.value.byteLength; + case Dish.JSON: + return JSON.stringify(this.value).length; + case Dish.FILE: + return this.value.size; + case Dish.LIST_FILE: + return this.value.reduce((acc, curr) => acc + curr.size, 0); + default: + return -1; + } + } + + + /** + * Returns a deep clone of the current Dish. + * + * @returns {Dish} + */ + clone() { + const newDish = new Dish(); + + switch (this.type) { + case Dish.STRING: + case Dish.HTML: + case Dish.NUMBER: + case Dish.BIG_NUMBER: + // These data types are immutable so it is acceptable to copy them by reference + newDish.set( + this.value, + this.type + ); + break; + case Dish.BYTE_ARRAY: + case Dish.JSON: + // These data types are mutable so they need to be copied by value + newDish.set( + JSON.parse(JSON.stringify(this.value)), + this.type + ); + break; + case Dish.ARRAY_BUFFER: + // Slicing an ArrayBuffer returns a new ArrayBuffer with a copy its contents + newDish.set( + this.value.slice(0), + this.type + ); + break; + case Dish.FILE: + // A new file can be created by copying over all the values from the original + newDish.set( + new File([this.value], this.value.name, { + "type": this.value.type, + "lastModified": this.value.lastModified + }), + this.type + ); + break; + case Dish.LIST_FILE: + newDish.set( + this.value.map(f => + new File([f], f.name, { + "type": f.type, + "lastModified": f.lastModified + }) + ), + this.type + ); + break; + default: + throw new DishError("Cannot clone Dish, unknown type"); + } + + return newDish; + } + +} + + +/** + * Dish data type enum for byte arrays. + * @readonly + * @enum + */ +Dish.BYTE_ARRAY = 0; +/** + * Dish data type enum for strings. + * @readonly + * @enum + */ +Dish.STRING = 1; +/** + * Dish data type enum for numbers. + * @readonly + * @enum + */ +Dish.NUMBER = 2; +/** + * Dish data type enum for HTML. + * @readonly + * @enum + */ +Dish.HTML = 3; +/** + * Dish data type enum for ArrayBuffers. + * @readonly + * @enum + */ +Dish.ARRAY_BUFFER = 4; +/** + * Dish data type enum for BigNumbers. + * @readonly + * @enum + */ +Dish.BIG_NUMBER = 5; +/** + * Dish data type enum for JSON. + * @readonly + * @enum + */ +Dish.JSON = 6; +/** + * Dish data type enum for lists of files. + * @readonly + * @enum + */ +Dish.FILE = 7; +/** +* Dish data type enum for lists of files. +* @readonly +* @enum +*/ +Dish.LIST_FILE = 8; + + +export default Dish; diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js deleted file mode 100755 index 8585a160..00000000 --- a/src/core/FlowControl.js +++ /dev/null @@ -1,213 +0,0 @@ -import Recipe from "./Recipe.js"; -import Dish from "./Dish.js"; - - -/** - * Flow Control operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const FlowControl = { - - /** - * @constant - * @default - */ - FORK_DELIM: "\\n", - /** - * @constant - * @default - */ - MERGE_DELIM: "\\n", - /** - * @constant - * @default - */ - FORK_IGNORE_ERRORS: false, - - /** - * Fork operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @returns {Object} The updated state of the recipe. - */ - runFork: async function(state) { - let opList = state.opList, - inputType = opList[state.progress].inputType, - outputType = opList[state.progress].outputType, - input = state.dish.get(inputType), - ings = opList[state.progress].getIngValues(), - splitDelim = ings[0], - mergeDelim = ings[1], - ignoreErrors = ings[2], - subOpList = [], - inputs = [], - i; - - if (input) - inputs = input.split(splitDelim); - - // Create subOpList for each tranche to operate on - // (all remaining operations unless we encounter a Merge) - for (i = state.progress + 1; i < opList.length; i++) { - if (opList[i].name === "Merge" && !opList[i].isDisabled()) { - break; - } else { - subOpList.push(opList[i]); - } - } - - let recipe = new Recipe(), - output = "", - progress = 0; - - recipe.addOperations(subOpList); - - // Run recipe over each tranche - for (i = 0; i < inputs.length; i++) { - const dish = new Dish(inputs[i], inputType); - try { - progress = await recipe.execute(dish, 0); - } catch (err) { - if (!ignoreErrors) { - throw err; - } - progress = err.progress + 1; - } - output += dish.get(outputType) + mergeDelim; - } - - state.dish.set(output, outputType); - state.progress += progress; - return state; - }, - - - /** - * Merge operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @returns {Object} The updated state of the recipe. - */ - runMerge: function(state) { - // No need to actually do anything here. The fork operation will - // merge when it sees this operation. - return state; - }, - - - /** - * @constant - * @default - */ - JUMP_NUM: 0, - /** - * @constant - * @default - */ - MAX_JUMPS: 10, - - /** - * Jump operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @param {number} state.numJumps - The number of jumps taken so far. - * @returns {Object} The updated state of the recipe. - */ - runJump: function(state) { - let ings = state.opList[state.progress].getIngValues(), - jumpNum = ings[0], - maxJumps = ings[1]; - - if (jumpNum < 0) { - jumpNum--; - } - - if (state.numJumps >= maxJumps) { - return state; - } - - state.progress += jumpNum; - state.numJumps++; - return state; - }, - - - /** - * Conditional Jump operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @param {number} state.numJumps - The number of jumps taken so far. - * @returns {Object} The updated state of the recipe. - */ - runCondJump: function(state) { - let ings = state.opList[state.progress].getIngValues(), - dish = state.dish, - regexStr = ings[0], - jumpNum = ings[1], - maxJumps = ings[2]; - - if (jumpNum < 0) { - jumpNum--; - } - - if (state.numJumps >= maxJumps) { - return state; - } - - if (regexStr !== "" && dish.get(Dish.STRING).search(regexStr) > -1) { - state.progress += jumpNum; - state.numJumps++; - } - - return state; - }, - - - /** - * Return operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @returns {Object} The updated state of the recipe. - */ - runReturn: function(state) { - state.progress = state.opList.length; - return state; - }, - - - /** - * Comment operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @returns {Object} The updated state of the recipe. - */ - runComment: function(state) { - return state; - }, - -}; - -export default FlowControl; diff --git a/src/core/Ingredient.js b/src/core/Ingredient.js deleted file mode 100755 index 543f732b..00000000 --- a/src/core/Ingredient.js +++ /dev/null @@ -1,92 +0,0 @@ -import Utils from "./Utils.js"; - - -/** - * The arguments to operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {Object} ingredientConfig - */ -const Ingredient = function(ingredientConfig) { - this.name = ""; - this.type = ""; - this.value = null; - - if (ingredientConfig) { - this._parseConfig(ingredientConfig); - } -}; - - -/** - * Reads and parses the given config. - * - * @private - * @param {Object} ingredientConfig - */ -Ingredient.prototype._parseConfig = function(ingredientConfig) { - this.name = ingredientConfig.name; - this.type = ingredientConfig.type; -}; - - -/** - * Returns the value of the Ingredient as it should be displayed in a recipe config. - * - * @returns {*} - */ -Ingredient.prototype.getConfig = function() { - return this.value; -}; - - -/** - * Sets the value of the Ingredient. - * - * @param {*} value - */ -Ingredient.prototype.setValue = function(value) { - this.value = Ingredient.prepare(value, this.type); -}; - - -/** - * Most values will be strings when they are entered. This function converts them to the correct - * type. - * - * @static - * @param {*} data - * @param {string} type - The name of the data type. -*/ -Ingredient.prepare = function(data, type) { - let number; - - switch (type) { - case "binaryString": - case "binaryShortString": - case "editableOption": - return Utils.parseEscapedChars(data); - case "byteArray": - if (typeof data == "string") { - data = data.replace(/\s+/g, ""); - return Utils.hexToByteArray(data); - } else { - return data; - } - case "number": - number = parseFloat(data); - if (isNaN(number)) { - const sample = Utils.truncate(data.toString(), 10); - throw "Invalid ingredient value. Not a number: " + sample; - } - return number; - default: - return data; - } -}; - -export default Ingredient; diff --git a/src/core/Ingredient.mjs b/src/core/Ingredient.mjs new file mode 100755 index 00000000..96cdd400 --- /dev/null +++ b/src/core/Ingredient.mjs @@ -0,0 +1,123 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "./Utils"; +import {fromHex} from "./lib/Hex"; + +/** + * The arguments to operations. + */ +class Ingredient { + + /** + * Ingredient constructor + * + * @param {Object} ingredientConfig + */ + constructor(ingredientConfig) { + this.name = ""; + this.type = ""; + this._value = null; + this.disabled = false; + this.hint = ""; + this.rows = 0; + this.toggleValues = []; + this.target = null; + this.defaultIndex = 0; + + if (ingredientConfig) { + this._parseConfig(ingredientConfig); + } + } + + + /** + * Reads and parses the given config. + * + * @private + * @param {Object} ingredientConfig + */ + _parseConfig(ingredientConfig) { + this.name = ingredientConfig.name; + this.type = ingredientConfig.type; + this.defaultValue = ingredientConfig.value; + this.disabled = !!ingredientConfig.disabled; + this.hint = ingredientConfig.hint || false; + this.rows = ingredientConfig.rows || false; + this.toggleValues = ingredientConfig.toggleValues; + this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null; + this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0; + } + + + /** + * Returns the value of the Ingredient as it should be displayed in a recipe config. + * + * @returns {*} + */ + get config() { + return this._value; + } + + + /** + * Sets the value of the Ingredient. + * + * @param {*} value + */ + set value(value) { + this._value = Ingredient.prepare(value, this.type); + } + + + /** + * Gets the value of the Ingredient. + * + * @returns {*} + */ + get value() { + return this._value; + } + + + /** + * Most values will be strings when they are entered. This function converts them to the correct + * type. + * + * @param {*} data + * @param {string} type - The name of the data type. + */ + static prepare(data, type) { + let number; + + switch (type) { + case "binaryString": + case "binaryShortString": + case "editableOption": + case "editableOptionShort": + return Utils.parseEscapedChars(data); + case "byteArray": + if (typeof data == "string") { + data = data.replace(/\s+/g, ""); + return fromHex(data); + } else { + return data; + } + case "number": + number = parseFloat(data); + if (isNaN(number)) { + const sample = Utils.truncate(data.toString(), 10); + throw "Invalid ingredient value. Not a number: " + sample; + } + return number; + default: + return data; + } + } + +} + +export default Ingredient; diff --git a/src/core/Operation.js b/src/core/Operation.js deleted file mode 100755 index d2493718..00000000 --- a/src/core/Operation.js +++ /dev/null @@ -1,163 +0,0 @@ -import Dish from "./Dish.js"; -import Ingredient from "./Ingredient.js"; - - -/** - * The Operation specified by the user to be run. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {string} operationName - * @param {Object} operationConfig - */ -const Operation = function(operationName, operationConfig) { - this.name = operationName; - this.description = ""; - this.inputType = -1; - this.outputType = -1; - this.run = null; - this.highlight = null; - this.highlightReverse = null; - this.breakpoint = false; - this.disabled = false; - this.ingList = []; - - if (operationConfig) { - this._parseConfig(operationConfig); - } -}; - - -/** - * Reads and parses the given config. - * - * @private - * @param {Object} operationConfig - */ -Operation.prototype._parseConfig = function(operationConfig) { - this.description = operationConfig.description; - this.inputType = Dish.typeEnum(operationConfig.inputType); - this.outputType = Dish.typeEnum(operationConfig.outputType); - this.run = operationConfig.run; - this.highlight = operationConfig.highlight; - this.highlightReverse = operationConfig.highlightReverse; - this.flowControl = operationConfig.flowControl; - - for (let a = 0; a < operationConfig.args.length; a++) { - const ingredientConfig = operationConfig.args[a]; - const ingredient = new Ingredient(ingredientConfig); - this.addIngredient(ingredient); - } -}; - - -/** - * Returns the value of the Operation as it should be displayed in a recipe config. - * - * @returns {Object} - */ -Operation.prototype.getConfig = function() { - const ingredientConfig = []; - - for (let o = 0; o < this.ingList.length; o++) { - ingredientConfig.push(this.ingList[o].getConfig()); - } - - const operationConfig = { - "op": this.name, - "args": ingredientConfig - }; - - return operationConfig; -}; - - -/** - * Adds a new Ingredient to this Operation. - * - * @param {Ingredient} ingredient - */ -Operation.prototype.addIngredient = function(ingredient) { - this.ingList.push(ingredient); -}; - - -/** - * Set the Ingredient values for this Operation. - * - * @param {Object[]} ingValues - */ -Operation.prototype.setIngValues = function(ingValues) { - for (let i = 0; i < ingValues.length; i++) { - this.ingList[i].setValue(ingValues[i]); - } -}; - - -/** - * Get the Ingredient values for this Operation. - * - * @returns {Object[]} - */ -Operation.prototype.getIngValues = function() { - const ingValues = []; - for (let i = 0; i < this.ingList.length; i++) { - ingValues.push(this.ingList[i].value); - } - return ingValues; -}; - - -/** - * Set whether this Operation has a breakpoint. - * - * @param {boolean} value - */ -Operation.prototype.setBreakpoint = function(value) { - this.breakpoint = !!value; -}; - - -/** - * Returns true if this Operation has a breakpoint set. - * - * @returns {boolean} - */ -Operation.prototype.isBreakpoint = function() { - return this.breakpoint; -}; - - -/** - * Set whether this Operation is disabled. - * - * @param {boolean} value - */ -Operation.prototype.setDisabled = function(value) { - this.disabled = !!value; -}; - - -/** - * Returns true if this Operation is disabled. - * - * @returns {boolean} - */ -Operation.prototype.isDisabled = function() { - return this.disabled; -}; - - -/** - * Returns true if this Operation is a flow control. - * - * @returns {boolean} - */ -Operation.prototype.isFlowControl = function() { - return this.flowControl; -}; - -export default Operation; diff --git a/src/core/Operation.mjs b/src/core/Operation.mjs new file mode 100755 index 00000000..c0907fe8 --- /dev/null +++ b/src/core/Operation.mjs @@ -0,0 +1,318 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Dish from "./Dish"; +import Ingredient from "./Ingredient"; + +/** + * The Operation specified by the user to be run. + */ +class Operation { + + /** + * Operation constructor + */ + constructor() { + // Private fields + this._inputType = -1; + this._outputType = -1; + this._presentType = -1; + this._breakpoint = false; + this._disabled = false; + this._flowControl = false; + this._manualBake = false; + this._ingList = []; + + // Public fields + this.name = ""; + this.module = ""; + this.description = ""; + this.infoURL = null; + } + + + /** + * Interface for operation runner + * + * @param {*} input + * @param {Object[]} args + * @returns {*} + */ + run(input, args) { + return input; + } + + + /** + * Interface for forward highlighter + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return false; + } + + + /** + * Interface for reverse highlighter + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return false; + } + + + /** + * Method to be called when displaying the result of an operation in a human-readable + * format. This allows operations to return usable data from their run() method and + * only format them when this method is called. + * + * The default action is to return the data unchanged, but child classes can override + * this behaviour. + * + * @param {*} data - The result of the run() function + * @param {Object[]} args - The operation's arguments + * @returns {*} - A human-readable version of the data + */ + present(data, args) { + return data; + } + + + /** + * Sets the input type as a Dish enum. + * + * @param {string} typeStr + */ + set inputType(typeStr) { + this._inputType = Dish.typeEnum(typeStr); + } + + + /** + * Gets the input type as a readable string. + * + * @returns {string} + */ + get inputType() { + return Dish.enumLookup(this._inputType); + } + + + /** + * Sets the output type as a Dish enum. + * + * @param {string} typeStr + */ + set outputType(typeStr) { + this._outputType = Dish.typeEnum(typeStr); + if (this._presentType < 0) this._presentType = this._outputType; + } + + + /** + * Gets the output type as a readable string. + * + * @returns {string} + */ + get outputType() { + return Dish.enumLookup(this._outputType); + } + + + /** + * Sets the presentation type as a Dish enum. + * + * @param {string} typeStr + */ + set presentType(typeStr) { + this._presentType = Dish.typeEnum(typeStr); + } + + + /** + * Gets the presentation type as a readable string. + * + * @returns {string} + */ + get presentType() { + return Dish.enumLookup(this._presentType); + } + + + /** + * Sets the args for the current operation. + * + * @param {Object[]} conf + */ + set args(conf) { + conf.forEach(arg => { + const ingredient = new Ingredient(arg); + this.addIngredient(ingredient); + }); + } + + + /** + * Gets the args for the current operation. + * + * @param {Object[]} conf + */ + get args() { + return this._ingList.map(ing => { + const conf = { + name: ing.name, + type: ing.type, + value: ing.defaultValue + }; + + if (ing.toggleValues) conf.toggleValues = ing.toggleValues; + if (ing.hint) conf.hint = ing.hint; + if (ing.rows) conf.rows = ing.rows; + if (ing.disabled) conf.disabled = ing.disabled; + if (ing.target) conf.target = ing.target; + if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex; + return conf; + }); + } + + + /** + * Returns the value of the Operation as it should be displayed in a recipe config. + * + * @returns {Object} + */ + get config() { + return { + "op": this.name, + "args": this._ingList.map(ing => ing.config) + }; + } + + + /** + * Adds a new Ingredient to this Operation. + * + * @param {Ingredient} ingredient + */ + addIngredient(ingredient) { + this._ingList.push(ingredient); + } + + + /** + * Set the Ingredient values for this Operation. + * + * @param {Object[]} ingValues + */ + set ingValues(ingValues) { + ingValues.forEach((val, i) => { + this._ingList[i].value = val; + }); + } + + + /** + * Get the Ingredient values for this Operation. + * + * @returns {Object[]} + */ + get ingValues() { + return this._ingList.map(ing => ing.value); + } + + + /** + * Set whether this Operation has a breakpoint. + * + * @param {boolean} value + */ + set breakpoint(value) { + this._breakpoint = !!value; + } + + + /** + * Returns true if this Operation has a breakpoint set. + * + * @returns {boolean} + */ + get breakpoint() { + return this._breakpoint; + } + + + /** + * Set whether this Operation is disabled. + * + * @param {boolean} value + */ + set disabled(value) { + this._disabled = !!value; + } + + + /** + * Returns true if this Operation is disabled. + * + * @returns {boolean} + */ + get disabled() { + return this._disabled; + } + + + /** + * Returns true if this Operation is a flow control. + * + * @returns {boolean} + */ + get flowControl() { + return this._flowControl; + } + + + /** + * Set whether this Operation is a flowcontrol op. + * + * @param {boolean} value + */ + set flowControl(value) { + this._flowControl = !!value; + } + + + /** + * Returns true if this Operation should not trigger AutoBake. + * + * @returns {boolean} + */ + get manualBake() { + return this._manualBake; + } + + + /** + * Set whether this Operation should trigger AutoBake. + * + * @param {boolean} value + */ + set manualBake(value) { + this._manualBake = !!value; + } + +} + +export default Operation; diff --git a/src/core/Recipe.js b/src/core/Recipe.js deleted file mode 100755 index 1b0e7f73..00000000 --- a/src/core/Recipe.js +++ /dev/null @@ -1,220 +0,0 @@ -import Operation from "./Operation.js"; -import OperationConfig from "./config/OperationConfig.js"; - - -/** - * The Recipe controls a list of Operations and the Dish they operate on. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {Object} recipeConfig - */ -const Recipe = function(recipeConfig) { - this.opList = []; - - if (recipeConfig) { - this._parseConfig(recipeConfig); - } -}; - - -/** - * Reads and parses the given config. - * - * @private - * @param {Object} recipeConfig - */ -Recipe.prototype._parseConfig = function(recipeConfig) { - for (let c = 0; c < recipeConfig.length; c++) { - const operationName = recipeConfig[c].op; - const operationConfig = OperationConfig[operationName]; - const operation = new Operation(operationName, operationConfig); - operation.setIngValues(recipeConfig[c].args); - operation.setBreakpoint(recipeConfig[c].breakpoint); - operation.setDisabled(recipeConfig[c].disabled); - this.addOperation(operation); - } -}; - - -/** - * Returns the value of the Recipe as it should be displayed in a recipe config. - * - * @returns {*} - */ -Recipe.prototype.getConfig = function() { - const recipeConfig = []; - - for (let o = 0; o < this.opList.length; o++) { - recipeConfig.push(this.opList[o].getConfig()); - } - - return recipeConfig; -}; - - -/** - * Adds a new Operation to this Recipe. - * - * @param {Operation} operation - */ -Recipe.prototype.addOperation = function(operation) { - this.opList.push(operation); -}; - - -/** - * Adds a list of Operations to this Recipe. - * - * @param {Operation[]} operations - */ -Recipe.prototype.addOperations = function(operations) { - this.opList = this.opList.concat(operations); -}; - - -/** - * Set a breakpoint on a specified Operation. - * - * @param {number} position - The index of the Operation - * @param {boolean} value - */ -Recipe.prototype.setBreakpoint = function(position, value) { - try { - this.opList[position].setBreakpoint(value); - } catch (err) { - // Ignore index error - } -}; - - -/** - * Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow - * Control Fork operation. - * - * @param {number} pos - */ -Recipe.prototype.removeBreaksUpTo = function(pos) { - for (let i = 0; i < pos; i++) { - this.opList[i].setBreakpoint(false); - } -}; - - -/** - * Returns true if there is an Flow Control Operation in this Recipe. - * - * @returns {boolean} - */ -Recipe.prototype.containsFlowControl = function() { - for (let i = 0; i < this.opList.length; i++) { - if (this.opList[i].isFlowControl()) return true; - } - return false; -}; - - -/** - * Returns the index of the last Operation index that will be executed, taking into account disabled - * Operations and breakpoints. - * - * @param {number} [startIndex=0] - The index to start searching from - * @returns (number} - */ -Recipe.prototype.lastOpIndex = function(startIndex) { - let i = startIndex + 1 || 0, - op; - - for (; i < this.opList.length; i++) { - op = this.opList[i]; - if (op.isDisabled()) return i-1; - if (op.isBreakpoint()) return i-1; - } - - return i-1; -}; - - -/** - * Executes each operation in the recipe over the given Dish. - * - * @param {Dish} dish - * @param {number} [startFrom=0] - The index of the Operation to start executing from - * @returns {number} - The final progress through the recipe - */ -Recipe.prototype.execute = async function(dish, startFrom) { - startFrom = startFrom || 0; - let op, input, output, numJumps = 0; - - for (let i = startFrom; i < this.opList.length; i++) { - op = this.opList[i]; - if (op.isDisabled()) { - continue; - } - if (op.isBreakpoint()) { - return i; - } - - try { - input = dish.get(op.inputType); - - if (op.isFlowControl()) { - // Package up the current state - let state = { - "progress" : i, - "dish" : dish, - "opList" : this.opList, - "numJumps" : numJumps - }; - - state = await op.run(state); - i = state.progress; - numJumps = state.numJumps; - } else { - output = await op.run(input, op.getIngValues()); - dish.set(output, op.outputType); - } - } catch (err) { - const e = typeof err == "string" ? { message: err } : err; - - e.progress = i; - if (e.fileName) { - e.displayStr = op.name + " - " + e.name + " in " + - e.fileName + " on line " + e.lineNumber + - ".

Message: " + (e.displayStr || e.message); - } else { - e.displayStr = op.name + " - " + (e.displayStr || e.message); - } - - throw e; - } - } - - return this.opList.length; -}; - - -/** - * Returns the recipe configuration in string format. - * - * @returns {string} - */ -Recipe.prototype.toString = function() { - return JSON.stringify(this.getConfig()); -}; - - -/** - * Creates a Recipe from a given configuration string. - * - * @param {string} recipeStr - */ -Recipe.prototype.fromString = function(recipeStr) { - const recipeConfig = JSON.parse(recipeStr); - this._parseConfig(recipeConfig); -}; - -export default Recipe; diff --git a/src/core/Recipe.mjs b/src/core/Recipe.mjs new file mode 100755 index 00000000..a11b4d02 --- /dev/null +++ b/src/core/Recipe.mjs @@ -0,0 +1,342 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import OperationConfig from "./config/OperationConfig.json"; +import OperationError from "./errors/OperationError"; +import Operation from "./Operation"; +import DishError from "./errors/DishError"; +import log from "loglevel"; + +// Cache container for modules +let modules = null; + +/** + * The Recipe controls a list of Operations and the Dish they operate on. + */ +class Recipe { + + /** + * Recipe constructor + * + * @param {Object} recipeConfig + */ + constructor(recipeConfig) { + this.opList = []; + + if (recipeConfig) { + this._parseConfig(recipeConfig); + } + } + + + /** + * Reads and parses the given config. + * + * @private + * @param {Object} recipeConfig + */ + _parseConfig(recipeConfig) { + recipeConfig.forEach(c => { + this.opList.push({ + name: c.op, + module: OperationConfig[c.op].module, + ingValues: c.args, + breakpoint: c.breakpoint, + disabled: c.disabled, + }); + }); + } + + + /** + * Populate elements of opList with operation instances. + * Dynamic import here removes top-level cyclic dependency issue. + * + * @private + */ + async _hydrateOpList() { + if (!modules) { + // Using Webpack Magic Comments to force the dynamic import to be included in the main chunk + // https://webpack.js.org/api/module-methods/ + modules = await import(/* webpackMode: "eager" */ "./config/modules/OpModules"); + modules = modules.default; + } + + this.opList = this.opList.map(o => { + if (o instanceof Operation) { + return o; + } else { + const op = new modules[o.module][o.name](); + op.ingValues = o.ingValues; + op.breakpoint = o.breakpoint; + op.disabled = o.disabled; + return op; + } + }); + } + + + /** + * Returns the value of the Recipe as it should be displayed in a recipe config. + * + * @returns {Object[]} + */ + get config() { + return this.opList.map(op => ({ + op: op.name, + args: op.ingValues, + })); + } + + + /** + * Adds a new Operation to this Recipe. + * + * @param {Operation} operation + */ + addOperation(operation) { + this.opList.push(operation); + } + + + /** + * Adds a list of Operations to this Recipe. + * + * @param {Operation[]} operations + */ + addOperations(operations) { + operations.forEach(o => { + if (o instanceof Operation) { + this.opList.push(o); + } else { + this.opList.push({ + name: o.name, + module: o.module, + ingValues: o.args, + breakpoint: o.breakpoint, + disabled: o.disabled, + }); + } + }); + } + + + /** + * Set a breakpoint on a specified Operation. + * + * @param {number} position - The index of the Operation + * @param {boolean} value + */ + setBreakpoint(position, value) { + try { + this.opList[position].breakpoint = value; + } catch (err) { + // Ignore index error + } + } + + + /** + * Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow + * Control Fork operation. + * + * @param {number} pos + */ + removeBreaksUpTo(pos) { + for (let i = 0; i < pos; i++) { + this.opList[i].breakpoint = false; + } + } + + + /** + * Returns true if there is a Flow Control Operation in this Recipe. + * + * @returns {boolean} + */ + containsFlowControl() { + return this.opList.reduce((acc, curr) => { + return acc || curr.flowControl; + }, false); + } + + + /** + * Executes each operation in the recipe over the given Dish. + * + * @param {Dish} dish + * @param {number} [startFrom=0] + * - The index of the Operation to start executing from + * @param {number} [forkState={}] + * - If this is a forked recipe, the state of the recipe up to this point + * @returns {number} + * - The final progress through the recipe + */ + async execute(dish, startFrom=0, forkState={}) { + let op, input, output, + numJumps = 0, + numRegisters = forkState.numRegisters || 0; + + if (startFrom === 0) this.lastRunOp = null; + + await this._hydrateOpList(); + + log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`); + + for (let i = startFrom; i < this.opList.length; i++) { + op = this.opList[i]; + log.debug(`[${i}] ${op.name} ${JSON.stringify(op.ingValues)}`); + if (op.disabled) { + log.debug("Operation is disabled, skipping"); + continue; + } + if (op.breakpoint) { + log.debug("Pausing at breakpoint"); + return i; + } + + try { + input = await dish.get(op.inputType); + log.debug("Executing operation"); + + if (op.flowControl) { + // Package up the current state + let state = { + "progress": i, + "dish": dish, + "opList": this.opList, + "numJumps": numJumps, + "numRegisters": numRegisters, + "forkOffset": forkState.forkOffset || 0 + }; + + state = await op.run(state); + i = state.progress; + numJumps = state.numJumps; + numRegisters = state.numRegisters; + } else { + output = await op.run(input, op.ingValues); + dish.set(output, op.outputType); + } + this.lastRunOp = op; + } catch (err) { + // Return expected errors as output + if (err instanceof OperationError || + (err.type && err.type === "OperationError")) { + // Cannot rely on `err instanceof OperationError` here as extending + // native types is not fully supported yet. + dish.set(err.message, "string"); + return i; + } else if (err instanceof DishError || + (err.type && err.type === "DishError")) { + dish.set(err.message, "string"); + return i; + } else { + const e = typeof err == "string" ? { message: err } : err; + + e.progress = i; + if (e.fileName) { + e.displayStr = `${op.name} - ${e.name} in ${e.fileName} on line ` + + `${e.lineNumber}.

Message: ${e.displayStr || e.message}`; + } else { + e.displayStr = `${op.name} - ${e.displayStr || e.message}`; + } + + throw e; + } + } + } + + log.debug("Recipe complete"); + return this.opList.length; + } + + + /** + * Present the results of the final operation. + * + * @param {Dish} dish + */ + async present(dish) { + if (!this.lastRunOp) return; + + const output = await this.lastRunOp.present( + await dish.get(this.lastRunOp.outputType), + this.lastRunOp.ingValues + ); + dish.set(output, this.lastRunOp.presentType); + } + + + /** + * Returns the recipe configuration in string format. + * + * @returns {string} + */ + toString() { + return JSON.stringify(this.config); + } + + + /** + * Creates a Recipe from a given configuration string. + * + * @param {string} recipeStr + */ + fromString(recipeStr) { + const recipeConfig = JSON.parse(recipeStr); + this._parseConfig(recipeConfig); + } + + + /** + * Generates a list of all the highlight functions assigned to operations in the recipe, if the + * entire recipe supports highlighting. + * + * @returns {Object[]} highlights + * @returns {function} highlights[].f + * @returns {function} highlights[].b + * @returns {Object[]} highlights[].args + */ + async generateHighlightList() { + await this._hydrateOpList(); + const highlights = []; + + for (let i = 0; i < this.opList.length; i++) { + const op = this.opList[i]; + if (op.disabled) continue; + + // If any breakpoints are set, do not attempt to highlight + if (op.breakpoint) return false; + + // If any of the operations do not support highlighting, fail immediately. + if (op.highlight === false || op.highlight === undefined) return false; + + highlights.push({ + f: op.highlight, + b: op.highlightReverse, + args: op.ingValues + }); + } + + return highlights; + } + + + /** + * Determines whether the previous operation has a different presentation type to its normal output. + * + * @param {number} progress + * @returns {boolean} + */ + lastOpPresented(progress) { + if (progress < 1) return false; + return this.opList[progress-1].presentType !== this.opList[progress-1].outputType; + } + +} + +export default Recipe; diff --git a/src/core/Utils.js b/src/core/Utils.js deleted file mode 100755 index bb05ec3d..00000000 --- a/src/core/Utils.js +++ /dev/null @@ -1,1206 +0,0 @@ -import CryptoJS from "crypto-js"; - - -/** - * Utility functions for use in operations, the core framework and the stage. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Utils = { - - /** - * Translates an ordinal into a character. - * - * @param {number} o - * @returns {char} - * - * @example - * // returns 'a' - * Utils.chr(97); - */ - chr: function(o) { - return String.fromCharCode(o); - }, - - - /** - * Translates a character into an ordinal. - * - * @param {char} c - * @returns {number} - * - * @example - * // returns 97 - * Utils.ord('a'); - */ - ord: function(c) { - return c.charCodeAt(0); - }, - - - /** - * Adds leading zeros to strings - * - * @param {string} str - String to add leading characters to. - * @param {number} max - Maximum width of the string. - * @param {char} [chr='0'] - The character to pad with. - * @returns {string} - * - * @example - * // returns "0a" - * Utils.padLeft("a", 2); - * - * // returns "000a" - * Utils.padLeft("a", 4); - * - * // returns "xxxa" - * Utils.padLeft("a", 4, "x"); - * - * // returns "bcabchello" - * Utils.padLeft("hello", 10, "abc"); - */ - padLeft: function(str, max, chr) { - chr = chr || "0"; - let startIndex = chr.length - (max - str.length); - startIndex = startIndex < 0 ? 0 : startIndex; - return str.length < max ? - Utils.padLeft(chr.slice(startIndex, chr.length) + str, max, chr) : str; - }, - - - /** - * Adds trailing spaces to strings. - * - * @param {string} str - String to add trailing characters to. - * @param {number} max - Maximum width of the string. - * @param {char} [chr='0'] - The character to pad with. - * @returns {string} - * - * @example - * // returns "a " - * Utils.padRight("a", 4); - * - * // returns "axxx" - * Utils.padRight("a", 4, "x"); - */ - padRight: function(str, max, chr) { - chr = chr || " "; - return str.length < max ? - Utils.padRight(str + chr.slice(0, max-str.length), max, chr) : str; - }, - - - /** - * Adds trailing bytes to a byteArray. - * - * @author tlwr [toby@toby.codes] - * - * @param {byteArray} arr - byteArray to add trailing bytes to. - * @param {number} numBytes - Maximum width of the array. - * @param {Integer} [padByte=0] - The byte to pad with. - * @returns {byteArray} - * - * @example - * // returns ["a", 0, 0, 0] - * Utils.padBytesRight("a", 4); - * - * // returns ["a", 1, 1, 1] - * Utils.padBytesRight("a", 4, 1); - * - * // returns ["t", "e", "s", "t", 0, 0, 0, 0] - * Utils.padBytesRight("test", 8); - * - * // returns ["t", "e", "s", "t", 1, 1, 1, 1] - * Utils.padBytesRight("test", 8, 1); - */ - padBytesRight: function(arr, numBytes, padByte) { - padByte = padByte || 0; - const paddedBytes = new Array(numBytes); - paddedBytes.fill(padByte); - - Array.prototype.map.call(arr, function(b, i) { - paddedBytes[i] = b; - }); - - return paddedBytes; - }, - - - /** - * @alias Utils.padLeft - */ - pad: function(str, max, chr) { - return Utils.padLeft(str, max, chr); - }, - - - /** - * Truncates a long string to max length and adds suffix. - * - * @param {string} str - String to truncate - * @param {number} max - Maximum length of the final string - * @param {string} [suffix='...'] - The string to add to the end of the final string - * @returns {string} - * - * @example - * // returns "A long..." - * Utils.truncate("A long string", 9); - * - * // returns "A long s-" - * Utils.truncate("A long string", 9, "-"); - */ - truncate: function(str, max, suffix) { - suffix = suffix || "..."; - if (str.length > max) { - str = str.slice(0, max - suffix.length) + suffix; - } - return str; - }, - - - /** - * Converts a character or number to its hex representation. - * - * @param {char|number} c - * @param {number} [length=2] - The width of the resulting hex number. - * @returns {string} - * - * @example - * // returns "6e" - * Utils.hex("n"); - * - * // returns "6e" - * Utils.hex(110); - */ - hex: function(c, length) { - c = typeof c == "string" ? Utils.ord(c) : c; - length = length || 2; - return Utils.pad(c.toString(16), length); - }, - - - /** - * Converts a character or number to its binary representation. - * - * @param {char|number} c - * @param {number} [length=8] - The width of the resulting binary number. - * @returns {string} - * - * @example - * // returns "01101110" - * Utils.bin("n"); - * - * // returns "01101110" - * Utils.bin(110); - */ - bin: function(c, length) { - c = typeof c == "string" ? Utils.ord(c) : c; - length = length || 8; - return Utils.pad(c.toString(2), length); - }, - - - /** - * Returns a string with all non-printable chars as dots, optionally preserving whitespace. - * - * @param {string} str - The input string to display. - * @param {boolean} [preserveWs=false] - Whether or not to print whitespace. - * @returns {string} - */ - printable: function(str, preserveWs) { - if (typeof window !== "undefined" && window.app && !window.app.options.treatAsUtf8) { - str = Utils.byteArrayToChars(Utils.strToByteArray(str)); - } - - const re = /[\0-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g; - const wsRe = /[\x09-\x10\x0D\u2028\u2029]/g; - - str = str.replace(re, "."); - if (!preserveWs) str = str.replace(wsRe, "."); - return str; - }, - - - /** - * Parse a string entered by a user and replace escaped chars with the bytes they represent. - * - * @param {string} str - * @returns {string} - * - * @example - * // returns "\x00" - * Utils.parseEscapedChars("\\x00"); - * - * // returns "\n" - * Utils.parseEscapedChars("\\n"); - */ - parseEscapedChars: function(str) { - return str.replace(/(\\)?\\([nrtbf]|x[\da-f]{2})/g, function(m, a, b) { - if (a === "\\") return "\\"+b; - switch (b[0]) { - case "n": - return "\n"; - case "r": - return "\r"; - case "t": - return "\t"; - case "b": - return "\b"; - case "f": - return "\f"; - case "x": - return Utils.chr(parseInt(b.substr(1), 16)); - } - }); - }, - - - /** - * Expand an alphabet range string into a list of the characters in that range. - * - * @param {string} alphStr - * @returns {char[]} - * - * @example - * // returns ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] - * Utils.expandAlphRange("0-9"); - * - * // returns ["a", "b", "c", "d", "0", "1", "2", "3", "+", "/"] - * Utils.expandAlphRange("a-d0-3+/"); - * - * // returns ["a", "b", "c", "d", "0", "-", "3"] - * Utils.expandAlphRange("a-d0\\-3") - */ - expandAlphRange: function(alphStr) { - const alphArr = []; - - for (let i = 0; i < alphStr.length; i++) { - if (i < alphStr.length - 2 && - alphStr[i+1] === "-" && - alphStr[i] !== "\\") { - let start = Utils.ord(alphStr[i]), - end = Utils.ord(alphStr[i+2]); - - for (let j = start; j <= end; j++) { - alphArr.push(Utils.chr(j)); - } - i += 2; - } else if (i < alphStr.length - 2 && - alphStr[i] === "\\" && - alphStr[i+1] === "-") { - alphArr.push("-"); - i++; - } else { - alphArr.push(alphStr[i]); - } - } - return alphArr; - }, - - - /** - * Translates a hex string into an array of bytes. - * - * @param {string} hexStr - * @returns {byteArray} - * - * @example - * // returns [0xfe, 0x09, 0xa7] - * Utils.hexToByteArray("fe09a7"); - */ - hexToByteArray: function(hexStr) { - // TODO: Handle errors i.e. input string is not hex - if (!hexStr) return []; - hexStr = hexStr.replace(/\s+/g, ""); - const byteArray = []; - for (let i = 0; i < hexStr.length; i += 2) { - byteArray.push(parseInt(hexStr.substr(i, 2), 16)); - } - return byteArray; - }, - - - /** - * Translates an array of bytes to a hex string. - * - * @param {byteArray} byteArray - * @returns {string} - * - * @example - * // returns "fe09a7" - * Utils.byteArrayToHex([0xfe, 0x09, 0xa7]); - */ - byteArrayToHex: function(byteArray) { - if (!byteArray) return ""; - let hexStr = ""; - for (let i = 0; i < byteArray.length; i++) { - hexStr += Utils.hex(byteArray[i]) + " "; - } - return hexStr.slice(0, hexStr.length-1); - }, - - - /** - * Converts a string to a byte array. - * Treats the string as UTF-8 if any values are over 255. - * - * @param {string} str - * @returns {byteArray} - * - * @example - * // returns [72,101,108,108,111] - * Utils.strToByteArray("Hello"); - * - * // returns [228,189,160,229,165,189] - * Utils.strToByteArray("你好"); - */ - strToByteArray: function(str) { - const byteArray = new Array(str.length); - let i = str.length, b; - while (i--) { - b = str.charCodeAt(i); - byteArray[i] = b; - // If any of the bytes are over 255, read as UTF-8 - if (b > 255) return Utils.strToUtf8ByteArray(str); - } - return byteArray; - }, - - - /** - * Converts a string to a UTF-8 byte array. - * - * @param {string} str - * @returns {byteArray} - * - * @example - * // returns [72,101,108,108,111] - * Utils.strToUtf8ByteArray("Hello"); - * - * // returns [228,189,160,229,165,189] - * Utils.strToUtf8ByteArray("你好"); - */ - strToUtf8ByteArray: function(str) { - let wordArray = CryptoJS.enc.Utf8.parse(str), - byteArray = Utils.wordArrayToByteArray(wordArray); - - if (typeof window !== "undefined" && str.length !== wordArray.sigBytes) { - window.app.options.attemptHighlight = false; - } - return byteArray; - }, - - - /** - * Converts a string to a charcode array - * - * @param {string} str - * @returns {byteArray} - * - * @example - * // returns [72,101,108,108,111] - * Utils.strToCharcode("Hello"); - * - * // returns [20320,22909] - * Utils.strToCharcode("你好"); - */ - strToCharcode: function(str) { - const byteArray = new Array(str.length); - let i = str.length; - while (i--) { - byteArray[i] = str.charCodeAt(i); - } - return byteArray; - }, - - - /** - * Attempts to convert a byte array to a UTF-8 string. - * - * @param {byteArray} byteArray - * @returns {string} - * - * @example - * // returns "Hello" - * Utils.byteArrayToUtf8([72,101,108,108,111]); - * - * // returns "你好" - * Utils.byteArrayToUtf8([228,189,160,229,165,189]); - */ - byteArrayToUtf8: function(byteArray) { - try { - // Try to output data as UTF-8 string - const words = []; - for (let i = 0; i < byteArray.length; i++) { - words[i >>> 2] |= byteArray[i] << (24 - (i % 4) * 8); - } - let wordArray = new CryptoJS.lib.WordArray.init(words, byteArray.length), - str = CryptoJS.enc.Utf8.stringify(wordArray); - - if (typeof window !== "undefined" && str.length !== wordArray.sigBytes) - window.app.options.attemptHighlight = false; - return str; - } catch (err) { - // If it fails, treat it as ANSI - return Utils.byteArrayToChars(byteArray); - } - }, - - - /** - * Converts a charcode array to a string. - * - * @param {byteArray} byteArray - * @returns {string} - * - * @example - * // returns "Hello" - * Utils.byteArrayToChars([72,101,108,108,111]); - * - * // returns "你好" - * Utils.byteArrayToChars([20320,22909]); - */ - byteArrayToChars: function(byteArray) { - if (!byteArray) return ""; - let str = ""; - for (let i = 0; i < byteArray.length;) { - str += String.fromCharCode(byteArray[i++]); - } - return str; - }, - - - /** - * Converts a CryptoJS.lib.WordArray to a byteArray. - * - * @param {CryptoJS.lib.WordArray} wordArray - * @returns {byteArray} - * - * @example - * // returns [84, 101, 115, 116] - * Utils.wordArrayToByteArray(CryptoJS.enc.Hex.parse("54657374")); - */ - wordArrayToByteArray: function(wordArray) { - if (wordArray.sigBytes <= 0) return []; - - let words = wordArray.words, - byteArray = []; - - for (let i = 0; i < wordArray.sigBytes; i++) { - byteArray.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff); - } - - return byteArray; - }, - - - /** - * Base64's the input byte array using the given alphabet, returning a string. - * - * @param {byteArray|string} data - * @param {string} [alphabet] - * @returns {string} - * - * @example - * // returns "SGVsbG8=" - * Utils.toBase64([72, 101, 108, 108, 111]); - * - * // returns "SGVsbG8=" - * Utils.toBase64("Hello"); - */ - toBase64: function(data, alphabet) { - if (!data) return ""; - if (typeof data == "string") { - data = Utils.strToByteArray(data); - } - - alphabet = alphabet ? - Utils.expandAlphRange(alphabet).join("") : - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - let output = "", - chr1, chr2, chr3, - enc1, enc2, enc3, enc4, - i = 0; - - while (i < data.length) { - chr1 = data[i++]; - chr2 = data[i++]; - chr3 = data[i++]; - - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - - output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + - alphabet.charAt(enc3) + alphabet.charAt(enc4); - } - - return output; - }, - - - /** - * UnBase64's the input string using the given alphabet, returning a byte array. - * - * @param {byteArray} data - * @param {string} [alphabet] - * @param {string} [returnType="string"] - Either "string" or "byteArray" - * @param {boolean} [removeNonAlphChars=true] - * @returns {byteArray} - * - * @example - * // returns "Hello" - * Utils.fromBase64("SGVsbG8="); - * - * // returns [72, 101, 108, 108, 111] - * Utils.fromBase64("SGVsbG8=", null, "byteArray"); - */ - fromBase64: function(data, alphabet, returnType, removeNonAlphChars) { - returnType = returnType || "string"; - - if (!data) { - return returnType === "string" ? "" : []; - } - - alphabet = alphabet ? - Utils.expandAlphRange(alphabet).join("") : - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - if (removeNonAlphChars === undefined) - removeNonAlphChars = true; - - let output = [], - chr1, chr2, chr3, - enc1, enc2, enc3, enc4, - i = 0; - - if (removeNonAlphChars) { - const re = new RegExp("[^" + alphabet.replace(/[\[\]\\\-^$]/g, "\\$&") + "]", "g"); - data = data.replace(re, ""); - } - - while (i < data.length) { - enc1 = alphabet.indexOf(data.charAt(i++)); - enc2 = alphabet.indexOf(data.charAt(i++) || "="); - enc3 = alphabet.indexOf(data.charAt(i++) || "="); - enc4 = alphabet.indexOf(data.charAt(i++) || "="); - - enc2 = enc2 === -1 ? 64 : enc2; - enc3 = enc3 === -1 ? 64 : enc3; - enc4 = enc4 === -1 ? 64 : enc4; - - chr1 = (enc1 << 2) | (enc2 >> 4); - chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); - chr3 = ((enc3 & 3) << 6) | enc4; - - output.push(chr1); - - if (enc3 !== 64) { - output.push(chr2); - } - if (enc4 !== 64) { - output.push(chr3); - } - } - - return returnType === "string" ? Utils.byteArrayToUtf8(output) : output; - }, - - - /** - * Convert a byte array into a hex string. - * - * @param {byteArray} data - * @param {string} [delim=" "] - * @param {number} [padding=2] - * @returns {string} - * - * @example - * // returns "0a 14 1e" - * Utils.toHex([10,20,30]); - * - * // returns "0a:14:1e" - * Utils.toHex([10,20,30], ":"); - */ - toHex: function(data, delim, padding) { - if (!data) return ""; - - delim = typeof delim == "string" ? delim : " "; - padding = padding || 2; - let output = ""; - - for (let i = 0; i < data.length; i++) { - output += Utils.pad(data[i].toString(16), padding) + delim; - } - - // Add \x or 0x to beginning - if (delim === "0x") output = "0x" + output; - if (delim === "\\x") output = "\\x" + output; - - if (delim.length) - return output.slice(0, -delim.length); - else - return output; - }, - - - /** - * Convert a byte array into a hex string as efficiently as possible with no options. - * - * @param {byteArray} data - * @returns {string} - * - * @example - * // returns "0a141e" - * Utils.toHex([10,20,30]); - */ - toHexFast: function(data) { - if (!data) return ""; - - const output = []; - - for (let i = 0; i < data.length; i++) { - output.push((data[i] >>> 4).toString(16)); - output.push((data[i] & 0x0f).toString(16)); - } - - return output.join(""); - }, - - - /** - * Convert a hex string into a byte array. - * - * @param {string} data - * @param {string} [delim] - * @param {number} [byteLen=2] - * @returns {byteArray} - * - * @example - * // returns [10,20,30] - * Utils.fromHex("0a 14 1e"); - * - * // returns [10,20,30] - * Utils.fromHex("0a:14:1e", "Colon"); - */ - fromHex: function(data, delim, byteLen) { - delim = delim || (data.indexOf(" ") >= 0 ? "Space" : "None"); - byteLen = byteLen || 2; - if (delim !== "None") { - const delimRegex = Utils.regexRep[delim]; - data = data.replace(delimRegex, ""); - } - - const output = []; - for (let i = 0; i < data.length; i += byteLen) { - output.push(parseInt(data.substr(i, byteLen), 16)); - } - return output; - }, - - - /** - * Parses CSV data and returns it as a two dimensional array or strings. - * - * @param {string} data - * @returns {string[][]} - * - * @example - * // returns [["head1", "head2"], ["data1", "data2"]] - * Utils.parseCSV("head1,head2\ndata1,data2"); - */ - parseCSV: function(data) { - - let b, - ignoreNext = false, - inString = false, - cell = "", - line = [], - lines = []; - - for (let i = 0; i < data.length; i++) { - b = data[i]; - if (ignoreNext) { - cell += b; - ignoreNext = false; - } else if (b === "\\") { - cell += b; - ignoreNext = true; - } else if (b === "\"" && !inString) { - inString = true; - } else if (b === "\"" && inString) { - inString = false; - } else if (b === "," && !inString) { - line.push(cell); - cell = ""; - } else if ((b === "\n" || b === "\r") && !inString) { - line.push(cell); - cell = ""; - lines.push(line); - line = []; - } else { - cell += b; - } - } - - if (line.length) { - line.push(cell); - lines.push(line); - } - - return lines; - }, - - - /** - * Removes all HTML (or XML) tags from the input string. - * - * @param {string} htmlStr - * @param {boolean} removeScriptAndStyle - Flag to specify whether to remove entire script or style blocks - * @returns {string} - * - * @example - * // returns "Test" - * Utils.stripHtmlTags("
Test
"); - */ - stripHtmlTags: function(htmlStr, removeScriptAndStyle) { - if (removeScriptAndStyle) { - htmlStr = htmlStr.replace(/<(script|style)[^>]*>.*<\/(script|style)>/gmi, ""); - } - return htmlStr.replace(/<[^>\n]+>/g, ""); - }, - - - /** - * Escapes HTML tags in a string to stop them being rendered. - * https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet - * - * @param {string} str - * @returns string - * - * @example - * // return "A <script> tag" - * Utils.escapeHtml("A ", - staticSection = "", - padding = ""; - - if (input.length < 1) { - return "Please enter a string."; - } - - // Highlight offset 0 - if (len0 % 4 === 2) { - staticSection = offset0.slice(0, -3); - offset0 = "" + - staticSection + "" + - "" + offset0.substr(offset0.length - 3, 1) + "" + - "" + offset0.substr(offset0.length - 2) + ""; - } else if (len0 % 4 === 3) { - staticSection = offset0.slice(0, -2); - offset0 = "" + - staticSection + "" + - "" + offset0.substr(offset0.length - 2, 1) + "" + - "" + offset0.substr(offset0.length - 1) + ""; - } else { - staticSection = offset0; - offset0 = "" + - staticSection + ""; - } - - if (!showVariable) { - offset0 = staticSection; - } - - - // Highlight offset 1 - padding = "" + offset1.substr(0, 1) + "" + - "" + offset1.substr(1, 1) + ""; - offset1 = offset1.substr(2); - if (len1 % 4 === 2) { - staticSection = offset1.slice(0, -3); - offset1 = padding + "" + - staticSection + "" + - "" + offset1.substr(offset1.length - 3, 1) + "" + - "" + offset1.substr(offset1.length - 2) + ""; - } else if (len1 % 4 === 3) { - staticSection = offset1.slice(0, -2); - offset1 = padding + "" + - staticSection + "" + - "" + offset1.substr(offset1.length - 2, 1) + "" + - "" + offset1.substr(offset1.length - 1) + ""; - } else { - staticSection = offset1; - offset1 = padding + "" + - staticSection + ""; - } - - if (!showVariable) { - offset1 = staticSection; - } - - // Highlight offset 2 - padding = "" + offset2.substr(0, 2) + "" + - "" + offset2.substr(2, 1) + ""; - offset2 = offset2.substr(3); - if (len2 % 4 === 2) { - staticSection = offset2.slice(0, -3); - offset2 = padding + "" + - staticSection + "" + - "" + offset2.substr(offset2.length - 3, 1) + "" + - "" + offset2.substr(offset2.length - 2) + ""; - } else if (len2 % 4 === 3) { - staticSection = offset2.slice(0, -2); - offset2 = padding + "" + - staticSection + "" + - "" + offset2.substr(offset2.length - 2, 1) + "" + - "" + offset2.substr(offset2.length - 1) + ""; - } else { - staticSection = offset2; - offset2 = padding + "" + - staticSection + ""; - } - - if (!showVariable) { - offset2 = staticSection; - } - - return (showVariable ? "Characters highlighted in green could change if the input is surrounded by more data." + - "\nCharacters highlighted in red are for padding purposes only." + - "\nUnhighlighted characters are static." + - "\nHover over the static sections to see what they decode to on their own.\n" + - "\nOffset 0: " + offset0 + - "\nOffset 1: " + offset1 + - "\nOffset 2: " + offset2 + - script : - offset0 + "\n" + offset1 + "\n" + offset2); - }, - - - /** - * Highlight to Base64 - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightTo: function(pos, args) { - pos[0].start = Math.floor(pos[0].start / 3 * 4); - pos[0].end = Math.ceil(pos[0].end / 3 * 4); - return pos; - }, - - /** - * Highlight from Base64 - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightFrom: function(pos, args) { - pos[0].start = Math.ceil(pos[0].start / 4 * 3); - pos[0].end = Math.floor(pos[0].end / 4 * 3); - return pos; - }, - -}; - -export default Base64; diff --git a/src/core/operations/Bcrypt.mjs b/src/core/operations/Bcrypt.mjs new file mode 100644 index 00000000..36a20607 --- /dev/null +++ b/src/core/operations/Bcrypt.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import bcrypt from "bcryptjs"; + +/** + * Bcrypt operation + */ +class Bcrypt extends Operation { + + /** + * Bcrypt constructor + */ + constructor() { + super(); + + this.name = "Bcrypt"; + this.module = "Crypto"; + this.description = "bcrypt is a password hashing function designed by Niels Provos and David Mazi\xe8res, based on the Blowfish cipher, and presented at USENIX in 1999. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count (rounds) can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.

Enter the password in the input to generate its hash."; + this.infoURL = "https://wikipedia.org/wiki/Bcrypt"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Rounds", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const rounds = args[0]; + const salt = await bcrypt.genSalt(rounds); + + return await bcrypt.hash(input, salt, null, p => { + // Progress callback + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`); + }); + + } + +} + +export default Bcrypt; diff --git a/src/core/operations/BcryptCompare.mjs b/src/core/operations/BcryptCompare.mjs new file mode 100644 index 00000000..5d8c393e --- /dev/null +++ b/src/core/operations/BcryptCompare.mjs @@ -0,0 +1,56 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import bcrypt from "bcryptjs"; + +/** + * Bcrypt compare operation + */ +class BcryptCompare extends Operation { + + /** + * BcryptCompare constructor + */ + constructor() { + super(); + + this.name = "Bcrypt compare"; + this.module = "Crypto"; + this.description = "Tests whether the input matches the given bcrypt hash. To test multiple possible passwords, use the 'Fork' operation."; + this.infoURL = "https://wikipedia.org/wiki/Bcrypt"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Hash", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const hash = args[0]; + + const match = await bcrypt.compare(input, hash, null, p => { + // Progress callback + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`); + }); + + return match ? "Match: " + input : "No match"; + + } + +} + +export default BcryptCompare; diff --git a/src/core/operations/BcryptParse.mjs b/src/core/operations/BcryptParse.mjs new file mode 100644 index 00000000..629eb1c2 --- /dev/null +++ b/src/core/operations/BcryptParse.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import bcrypt from "bcryptjs"; + +/** + * Bcrypt parse operation + */ +class BcryptParse extends Operation { + + /** + * BcryptParse constructor + */ + constructor() { + super(); + + this.name = "Bcrypt parse"; + this.module = "Crypto"; + this.description = "Parses a bcrypt hash to determine the number of rounds used, the salt, and the password hash."; + this.infoURL = "https://wikipedia.org/wiki/Bcrypt"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + try { + return `Rounds: ${bcrypt.getRounds(input)} +Salt: ${bcrypt.getSalt(input)} +Password hash: ${input.split(bcrypt.getSalt(input))[1]} +Full hash: ${input}`; + } catch (err) { + throw new OperationError("Error: " + err.toString()); + } + } + +} + +export default BcryptParse; diff --git a/src/core/operations/BifidCipherDecode.mjs b/src/core/operations/BifidCipherDecode.mjs new file mode 100644 index 00000000..b55d2e26 --- /dev/null +++ b/src/core/operations/BifidCipherDecode.mjs @@ -0,0 +1,125 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { genPolybiusSquare } from "../lib/Ciphers"; +import OperationError from "../errors/OperationError"; + +/** + * Bifid Cipher Decode operation + */ +class BifidCipherDecode extends Operation { + + /** + * BifidCipherDecode constructor + */ + constructor() { + super(); + + this.name = "Bifid Cipher Decode"; + this.module = "Ciphers"; + this.description = "The Bifid cipher is a cipher which uses a Polybius square in conjunction with transposition, which can be fairly difficult to decipher without knowing the alphabet keyword."; + this.infoURL = "https://wikipedia.org/wiki/Bifid_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Keyword", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid key + */ + run(input, args) { + const keywordStr = args[0].toUpperCase().replace("J", "I"), + keyword = keywordStr.split("").unique(), + alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ", + structure = []; + + let output = "", + count = 0, + trans = ""; + + if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0) + throw new OperationError("The key must consist only of letters in the English alphabet"); + + const polybius = genPolybiusSquare(keywordStr); + + input.replace("J", "I").split("").forEach((letter) => { + const alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0; + let polInd; + + if (alpInd) { + for (let i = 0; i < 5; i++) { + polInd = polybius[i].indexOf(letter.toLocaleUpperCase()); + if (polInd >= 0) { + trans += `${i}${polInd}`; + } + } + + if (alpha.split("").indexOf(letter) >= 0) { + structure.push(true); + } else if (alpInd) { + structure.push(false); + } + } else { + structure.push(letter); + } + }); + + structure.forEach(pos => { + if (typeof pos === "boolean") { + const coords = [trans[count], trans[count+trans.length/2]]; + + output += pos ? + polybius[coords[0]][coords[1]] : + polybius[coords[0]][coords[1]].toLocaleLowerCase(); + count++; + } else { + output += pos; + } + }); + + return output; + } + + /** + * Highlight Bifid Cipher Decode + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Bifid Cipher Decode in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default BifidCipherDecode; diff --git a/src/core/operations/BifidCipherEncode.mjs b/src/core/operations/BifidCipherEncode.mjs new file mode 100644 index 00000000..2db6279c --- /dev/null +++ b/src/core/operations/BifidCipherEncode.mjs @@ -0,0 +1,130 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import { genPolybiusSquare } from "../lib/Ciphers"; + +/** + * Bifid Cipher Encode operation + */ +class BifidCipherEncode extends Operation { + + /** + * BifidCipherEncode constructor + */ + constructor() { + super(); + + this.name = "Bifid Cipher Encode"; + this.module = "Ciphers"; + this.description = "The Bifid cipher is a cipher which uses a Polybius square in conjunction with transposition, which can be fairly difficult to decipher without knowing the alphabet keyword."; + this.infoURL = "https://wikipedia.org/wiki/Bifid_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Keyword", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if key is invalid + */ + run(input, args) { + const keywordStr = args[0].toUpperCase().replace("J", "I"), + keyword = keywordStr.split("").unique(), + alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ", + xCo = [], + yCo = [], + structure = []; + + let output = "", + count = 0; + + + if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0) + throw new OperationError("The key must consist only of letters in the English alphabet"); + + const polybius = genPolybiusSquare(keywordStr); + + input.replace("J", "I").split("").forEach(letter => { + const alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0; + let polInd; + + if (alpInd) { + for (let i = 0; i < 5; i++) { + polInd = polybius[i].indexOf(letter.toLocaleUpperCase()); + if (polInd >= 0) { + xCo.push(polInd); + yCo.push(i); + } + } + + if (alpha.split("").indexOf(letter) >= 0) { + structure.push(true); + } else if (alpInd) { + structure.push(false); + } + } else { + structure.push(letter); + } + }); + + const trans = `${yCo.join("")}${xCo.join("")}`; + + structure.forEach(pos => { + if (typeof pos === "boolean") { + const coords = trans.substr(2*count, 2).split(""); + + output += pos ? + polybius[coords[0]][coords[1]] : + polybius[coords[0]][coords[1]].toLocaleLowerCase(); + count++; + } else { + output += pos; + } + }); + + return output; + } + + /** + * Highlight Bifid Cipher Encode + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Bifid Cipher Encode in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default BifidCipherEncode; diff --git a/src/core/operations/BitShiftLeft.mjs b/src/core/operations/BitShiftLeft.mjs new file mode 100644 index 00000000..ceb19350 --- /dev/null +++ b/src/core/operations/BitShiftLeft.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Bit shift left operation + */ +class BitShiftLeft extends Operation { + + /** + * BitShiftLeft constructor + */ + constructor() { + super(); + + this.name = "Bit shift left"; + this.module = "Default"; + this.description = "Shifts the bits in each byte towards the left by the specified amount."; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Amount", + "type": "number", + "value": 1 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const amount = args[0]; + + return input.map(b => { + return (b << amount) & 0xff; + }); + } + + /** + * Highlight Bit shift left + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Bit shift left in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default BitShiftLeft; diff --git a/src/core/operations/BitShiftRight.mjs b/src/core/operations/BitShiftRight.mjs new file mode 100644 index 00000000..c6e4698f --- /dev/null +++ b/src/core/operations/BitShiftRight.mjs @@ -0,0 +1,83 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Bit shift right operation + */ +class BitShiftRight extends Operation { + + /** + * BitShiftRight constructor + */ + constructor() { + super(); + + this.name = "Bit shift right"; + this.module = "Default"; + this.description = "Shifts the bits in each byte towards the right by the specified amount.

Logical shifts replace the leftmost bits with zeros.
Arithmetic shifts preserve the most significant bit (MSB) of the original byte keeping the sign the same (positive or negative)."; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Amount", + "type": "number", + "value": 1 + }, + { + "name": "Type", + "type": "option", + "value": ["Logical shift", "Arithmetic shift"] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const amount = args[0], + type = args[1], + mask = type === "Logical shift" ? 0 : 0x80; + + return input.map(b => { + return (b >>> amount) ^ (b & mask); + }); + } + + /** + * Highlight Bit shift right + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Bit shift right in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default BitShiftRight; diff --git a/src/core/operations/BitwiseOp.js b/src/core/operations/BitwiseOp.js deleted file mode 100755 index 7a4675f6..00000000 --- a/src/core/operations/BitwiseOp.js +++ /dev/null @@ -1,310 +0,0 @@ -import Utils from "../Utils.js"; -import CryptoJS from "crypto-js"; - - -/** - * Bitwise operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const BitwiseOp = { - - /** - * Runs bitwise operations across the input data. - * - * @private - * @param {byteArray} input - * @param {byteArray} key - * @param {function} func - The bitwise calculation to carry out - * @param {boolean} nullPreserving - * @param {string} scheme - * @returns {byteArray} - */ - _bitOp: function (input, key, func, nullPreserving, scheme) { - if (!key || !key.length) key = [0]; - let result = [], - x = null, - k = null, - o = null; - - for (let i = 0; i < input.length; i++) { - k = key[i % key.length]; - o = input[i]; - x = nullPreserving && (o === 0 || o === k) ? o : func(o, k); - result.push(x); - if (scheme !== "Standard" && !(nullPreserving && (o === 0 || o === k))) { - switch (scheme) { - case "Input differential": - key[i % key.length] = x; - break; - case "Output differential": - key[i % key.length] = o; - break; - } - } - } - - return result; - }, - - - /** - * @constant - * @default - */ - XOR_PRESERVE_NULLS: false, - /** - * @constant - * @default - */ - XOR_SCHEME: ["Standard", "Input differential", "Output differential"], - /** - * @constant - * @default - */ - KEY_FORMAT: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"], - - /** - * XOR operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runXor: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""), - scheme = args[1], - nullPreserving = args[2]; - - key = Utils.wordArrayToByteArray(key); - - return BitwiseOp._bitOp(input, key, BitwiseOp._xor, nullPreserving, scheme); - }, - - - /** - * @constant - * @default - */ - XOR_BRUTE_KEY_LENGTH: ["1", "2"], - /** - * @constant - * @default - */ - XOR_BRUTE_SAMPLE_LENGTH: 100, - /** - * @constant - * @default - */ - XOR_BRUTE_SAMPLE_OFFSET: 0, - /** - * @constant - * @default - */ - XOR_BRUTE_PRINT_KEY: true, - /** - * @constant - * @default - */ - XOR_BRUTE_OUTPUT_HEX: false, - - /** - * XOR Brute Force operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runXorBrute: function (input, args) { - let keyLength = parseInt(args[0], 10), - sampleLength = args[1], - sampleOffset = args[2], - nullPreserving = args[3], - differential = args[4], - crib = args[5], - printKey = args[6], - outputHex = args[7], - regex; - - let output = "", - result, - resultUtf8; - - input = input.slice(sampleOffset, sampleOffset + sampleLength); - - if (crib !== "") { - regex = new RegExp(crib, "im"); - } - - - for (let key = 1, l = Math.pow(256, keyLength); key < l; key++) { - result = BitwiseOp._bitOp(input, Utils.hexToByteArray(key.toString(16)), BitwiseOp._xor, nullPreserving, differential); - resultUtf8 = Utils.byteArrayToUtf8(result); - if (crib !== "" && resultUtf8.search(regex) === -1) continue; - if (printKey) output += "Key = " + Utils.hex(key, (2*keyLength)) + ": "; - if (outputHex) - output += Utils.byteArrayToHex(result) + "\n"; - else - output += Utils.printable(resultUtf8, false) + "\n"; - if (printKey) output += "\n"; - } - return output; - }, - - - /** - * NOT operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runNot: function (input, args) { - return BitwiseOp._bitOp(input, null, BitwiseOp._not); - }, - - - /** - * AND operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runAnd: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); - - return BitwiseOp._bitOp(input, key, BitwiseOp._and); - }, - - - /** - * OR operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runOr: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); - - return BitwiseOp._bitOp(input, key, BitwiseOp._or); - }, - - - /** - * ADD operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runAdd: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); - - return BitwiseOp._bitOp(input, key, BitwiseOp._add); - }, - - - /** - * SUB operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runSub: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); - - return BitwiseOp._bitOp(input, key, BitwiseOp._sub); - }, - - - /** - * XOR bitwise calculation. - * - * @private - * @param {number} operand - * @param {number} key - * @returns {number} - */ - _xor: function (operand, key) { - return operand ^ key; - }, - - - /** - * NOT bitwise calculation. - * - * @private - * @param {number} operand - * @returns {number} - */ - _not: function (operand, _) { - return ~operand & 0xff; - }, - - - /** - * AND bitwise calculation. - * - * @private - * @param {number} operand - * @param {number} key - * @returns {number} - */ - _and: function (operand, key) { - return operand & key; - }, - - - /** - * OR bitwise calculation. - * - * @private - * @param {number} operand - * @param {number} key - * @returns {number} - */ - _or: function (operand, key) { - return operand | key; - }, - - - /** - * ADD bitwise calculation. - * - * @private - * @param {number} operand - * @param {number} key - * @returns {number} - */ - _add: function (operand, key) { - return (operand + key) % 256; - }, - - - /** - * SUB bitwise calculation. - * - * @private - * @param {number} operand - * @param {number} key - * @returns {number} - */ - _sub: function (operand, key) { - const result = operand - key; - return (result < 0) ? 256 + result : result; - }, - -}; - -export default BitwiseOp; diff --git a/src/core/operations/BlowfishDecrypt.mjs b/src/core/operations/BlowfishDecrypt.mjs new file mode 100644 index 00000000..4aa08af8 --- /dev/null +++ b/src/core/operations/BlowfishDecrypt.mjs @@ -0,0 +1,101 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import { Blowfish } from "../vendor/Blowfish"; +import { toBase64 } from "../lib/Base64"; +import { toHexFast } from "../lib/Hex"; + +/** + * Lookup table for Blowfish output types. + */ +const BLOWFISH_OUTPUT_TYPE_LOOKUP = { + Base64: 0, Hex: 1, String: 2, Raw: 3 +}; +/** + * Lookup table for Blowfish modes. + */ +const BLOWFISH_MODE_LOOKUP = { + ECB: 0, CBC: 1, PCBC: 2, CFB: 3, OFB: 4, CTR: 5 +}; + +/** + * Blowfish Decrypt operation + */ +class BlowfishDecrypt extends Operation { + + /** + * BlowfishDecrypt constructor + */ + constructor() { + super(); + + this.name = "Blowfish Decrypt"; + this.module = "Ciphers"; + this.description = "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes."; + this.infoURL = "https://wikipedia.org/wiki/Blowfish_(cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "PCBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Base64", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length === 0) throw new OperationError("Enter a key"); + + input = inputType === "Raw" ? Utils.strToByteArray(input) : input; + + Blowfish.setIV(toBase64(iv), 0); + + const result = Blowfish.decrypt(input, key, { + outputType: BLOWFISH_OUTPUT_TYPE_LOOKUP[inputType], // This actually means inputType. The library is weird. + cipherMode: BLOWFISH_MODE_LOOKUP[mode] + }); + + return outputType === "Hex" ? toHexFast(Utils.strToByteArray(result)) : result; + } + +} + +export default BlowfishDecrypt; diff --git a/src/core/operations/BlowfishEncrypt.mjs b/src/core/operations/BlowfishEncrypt.mjs new file mode 100644 index 00000000..813c359c --- /dev/null +++ b/src/core/operations/BlowfishEncrypt.mjs @@ -0,0 +1,102 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import { Blowfish } from "../vendor/Blowfish"; +import { toBase64 } from "../lib/Base64"; + +/** + * Lookup table for Blowfish output types. + */ +const BLOWFISH_OUTPUT_TYPE_LOOKUP = { + Base64: 0, Hex: 1, String: 2, Raw: 3 +}; + +/** + * Lookup table for Blowfish modes. + */ +const BLOWFISH_MODE_LOOKUP = { + ECB: 0, CBC: 1, PCBC: 2, CFB: 3, OFB: 4, CTR: 5 +}; + + +/** + * Blowfish Encrypt operation + */ +class BlowfishEncrypt extends Operation { + + /** + * BlowfishEncrypt constructor + */ + constructor() { + super(); + + this.name = "Blowfish Encrypt"; + this.module = "Ciphers"; + this.description = "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes."; + this.infoURL = "https://wikipedia.org/wiki/Blowfish_(cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "PCBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Base64", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length === 0) throw new OperationError("Enter a key"); + + input = Utils.convertToByteString(input, inputType); + + Blowfish.setIV(toBase64(iv), 0); + + const enc = Blowfish.encrypt(input, key, { + outputType: BLOWFISH_OUTPUT_TYPE_LOOKUP[outputType], + cipherMode: BLOWFISH_MODE_LOOKUP[mode] + }); + + return outputType === "Raw" ? Utils.byteArrayToChars(enc) : enc; + } + +} + +export default BlowfishEncrypt; diff --git a/src/core/operations/ByteRepr.js b/src/core/operations/ByteRepr.js deleted file mode 100755 index 0594765f..00000000 --- a/src/core/operations/ByteRepr.js +++ /dev/null @@ -1,427 +0,0 @@ -/* globals app */ -import Utils from "../Utils.js"; - - -/** - * Byte representation operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const ByteRepr = { - - /** - * @constant - * @default - */ - DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF"], - /** - * @constant - * @default - */ - HEX_DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"], - /** - * @constant - * @default - */ - BIN_DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "None"], - - /** - * To Hex operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runToHex: function(input, args) { - const delim = Utils.charRep[args[0] || "Space"]; - return Utils.toHex(input, delim, 2); - }, - - - /** - * From Hex operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromHex: function(input, args) { - const delim = args[0] || "Space"; - return Utils.fromHex(input, delim, 2); - }, - - - /** - * To Octal operation. - * - * @author Matt C [matt@artemisbot.pw] - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runToOct: function(input, args) { - const delim = Utils.charRep[args[0] || "Space"]; - return input.map(val => val.toString(8)).join(delim); - }, - - - /** - * From Octal operation. - * - * @author Matt C [matt@artemisbot.pw] - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromOct: function(input, args) { - const delim = Utils.charRep[args[0] || "Space"]; - if (input.length === 0) return []; - return input.split(delim).map(val => parseInt(val, 8)); - }, - - - /** - * @constant - * @default - */ - CHARCODE_BASE: 16, - - /** - * To Charcode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToCharcode: function(input, args) { - let delim = Utils.charRep[args[0] || "Space"], - base = args[1], - output = "", - padding = 2, - ordinal; - - if (base < 2 || base > 36) { - throw "Error: Base argument must be between 2 and 36"; - } - - for (let i = 0; i < input.length; i++) { - ordinal = Utils.ord(input[i]); - - if (base === 16) { - if (ordinal < 256) padding = 2; - else if (ordinal < 65536) padding = 4; - else if (ordinal < 16777216) padding = 6; - else if (ordinal < 4294967296) padding = 8; - else padding = 2; - - if (padding > 2 && app) app.options.attemptHighlight = false; - - output += Utils.hex(ordinal, padding) + delim; - } else { - if (app) app.options.attemptHighlight = false; - output += ordinal.toString(base) + delim; - } - } - - return output.slice(0, -delim.length); - }, - - - /** - * From Charcode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromCharcode: function(input, args) { - let delim = Utils.charRep[args[0] || "Space"], - base = args[1], - bites = input.split(delim), - i = 0; - - if (base < 2 || base > 36) { - throw "Error: Base argument must be between 2 and 36"; - } - - if (base !== 16 && app) { - app.options.attemptHighlight = false; - } - - // Split into groups of 2 if the whole string is concatenated and - // too long to be a single character - if (bites.length === 1 && input.length > 17) { - bites = []; - for (i = 0; i < input.length; i += 2) { - bites.push(input.slice(i, i+2)); - } - } - - let latin1 = ""; - for (i = 0; i < bites.length; i++) { - latin1 += Utils.chr(parseInt(bites[i], base)); - } - return Utils.strToByteArray(latin1); - }, - - - /** - * Highlight to hex - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightTo: function(pos, args) { - let delim = Utils.charRep[args[0] || "Space"], - len = delim === "\r\n" ? 1 : delim.length; - - pos[0].start = pos[0].start * (2 + len); - pos[0].end = pos[0].end * (2 + len) - len; - - // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly - if (delim === "0x" || delim === "\\x") { - pos[0].start += 2; - pos[0].end += 2; - } - return pos; - }, - - - /** - * Highlight to hex - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightFrom: function(pos, args) { - let delim = Utils.charRep[args[0] || "Space"], - len = delim === "\r\n" ? 1 : delim.length, - width = len + 2; - - // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly - if (delim === "0x" || delim === "\\x") { - if (pos[0].start > 1) pos[0].start -= 2; - else pos[0].start = 0; - if (pos[0].end > 1) pos[0].end -= 2; - else pos[0].end = 0; - } - - pos[0].start = pos[0].start === 0 ? 0 : Math.round(pos[0].start / width); - pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / width); - return pos; - }, - - - /** - * To Decimal operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runToDecimal: function(input, args) { - const delim = Utils.charRep[args[0]]; - return input.join(delim); - }, - - - /** - * From Decimal operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromDecimal: function(input, args) { - const delim = Utils.charRep[args[0]]; - let byteStr = input.split(delim), output = []; - if (byteStr[byteStr.length-1] === "") - byteStr = byteStr.slice(0, byteStr.length-1); - - for (let i = 0; i < byteStr.length; i++) { - output[i] = parseInt(byteStr[i], 10); - } - return output; - }, - - - /** - * To Binary operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runToBinary: function(input, args) { - let delim = Utils.charRep[args[0] || "Space"], - output = "", - padding = 8; - - for (let i = 0; i < input.length; i++) { - output += Utils.pad(input[i].toString(2), padding) + delim; - } - - if (delim.length) { - return output.slice(0, -delim.length); - } else { - return output; - } - }, - - - /** - * From Binary operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromBinary: function(input, args) { - if (args[0] !== "None") { - const delimRegex = Utils.regexRep[args[0] || "Space"]; - input = input.replace(delimRegex, ""); - } - - const output = []; - const byteLen = 8; - for (let i = 0; i < input.length; i += byteLen) { - output.push(parseInt(input.substr(i, byteLen), 2)); - } - return output; - }, - - - /** - * Highlight to binary - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightToBinary: function(pos, args) { - const delim = Utils.charRep[args[0] || "Space"]; - pos[0].start = pos[0].start * (8 + delim.length); - pos[0].end = pos[0].end * (8 + delim.length) - delim.length; - return pos; - }, - - - /** - * Highlight from binary - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightFromBinary: function(pos, args) { - const delim = Utils.charRep[args[0] || "Space"]; - pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length)); - pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length)); - return pos; - }, - - - /** - * @constant - * @default - */ - HEX_CONTENT_CONVERT_WHICH: ["Only special chars", "Only special chars including spaces", "All chars"], - /** - * @constant - * @default - */ - HEX_CONTENT_SPACES_BETWEEN_BYTES: false, - - /** - * To Hex Content operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runToHexContent: function(input, args) { - const convert = args[0]; - const spaces = args[1]; - if (convert === "All chars") { - let result = "|" + Utils.toHex(input) + "|"; - if (!spaces) result = result.replace(/ /g, ""); - return result; - } - - let output = "", - inHex = false, - convertSpaces = convert === "Only special chars including spaces", - b; - for (let i = 0; i < input.length; i++) { - b = input[i]; - if ((b === 32 && convertSpaces) || (b < 48 && b !== 32) || (b > 57 && b < 65) || (b > 90 && b < 97) || b > 122) { - if (!inHex) { - output += "|"; - inHex = true; - } else if (spaces) output += " "; - output += Utils.toHex([b]); - } else { - if (inHex) { - output += "|"; - inHex = false; - } - output += Utils.chr(input[i]); - } - } - if (inHex) output += "|"; - return output; - }, - - - /** - * From Hex Content operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromHexContent: function(input, args) { - const regex = /\|([a-f\d ]{2,})\|/gi; - let output = [], m, i = 0; - while ((m = regex.exec(input))) { - // Add up to match - for (; i < m.index;) - output.push(Utils.ord(input[i++])); - - // Add match - const bytes = Utils.fromHex(m[1]); - if (bytes) { - for (let a = 0; a < bytes.length;) - output.push(bytes[a++]); - } else { - // Not valid hex, print as normal - for (; i < regex.lastIndex;) - output.push(Utils.ord(input[i++])); - } - - i = regex.lastIndex; - } - // Add all after final match - for (; i < input.length;) - output.push(Utils.ord(input[i++])); - - return output; - }, - -}; - -export default ByteRepr; diff --git a/src/core/operations/Bzip2Decompress.mjs b/src/core/operations/Bzip2Decompress.mjs new file mode 100644 index 00000000..3b357486 --- /dev/null +++ b/src/core/operations/Bzip2Decompress.mjs @@ -0,0 +1,56 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import bzip2 from "../vendor/bzip2"; +import OperationError from "../errors/OperationError"; + +/** + * Bzip2 Decompress operation + */ +class Bzip2Decompress extends Operation { + + /** + * Bzip2Decompress constructor + */ + constructor() { + super(); + + this.name = "Bzip2 Decompress"; + this.module = "Compression"; + this.description = "Decompresses data using the Bzip2 algorithm."; + this.infoURL = "https://wikipedia.org/wiki/Bzip2"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + this.patterns = [ + { + "match": "^\\x42\\x5a\\x68", + "flags": "", + "args": [] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const compressed = new Uint8Array(input); + + try { + const bzip2Reader = bzip2.array(compressed); + return bzip2.simple(bzip2Reader); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default Bzip2Decompress; diff --git a/src/core/operations/CRC16Checksum.mjs b/src/core/operations/CRC16Checksum.mjs new file mode 100644 index 00000000..3171ad73 --- /dev/null +++ b/src/core/operations/CRC16Checksum.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import JSCRC from "js-crc"; + +/** + * CRC-16 Checksum operation + */ +class CRC16Checksum extends Operation { + + /** + * CRC16Checksum constructor + */ + constructor() { + super(); + + this.name = "CRC-16 Checksum"; + this.module = "Crypto"; + this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.

The CRC was invented by W. Wesley Peterson in 1961."; + this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return JSCRC.crc16(input); + } + +} + +export default CRC16Checksum; diff --git a/src/core/operations/CRC32Checksum.mjs b/src/core/operations/CRC32Checksum.mjs new file mode 100644 index 00000000..962253dc --- /dev/null +++ b/src/core/operations/CRC32Checksum.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import JSCRC from "js-crc"; + +/** + * CRC-32 Checksum operation + */ +class CRC32Checksum extends Operation { + + /** + * CRC32Checksum constructor + */ + constructor() { + super(); + + this.name = "CRC-32 Checksum"; + this.module = "Crypto"; + this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.

The CRC was invented by W. Wesley Peterson in 1961; the 32-bit CRC function of Ethernet and many other standards is the work of several researchers and was published in 1975."; + this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return JSCRC.crc32(input); + } + +} + +export default CRC32Checksum; diff --git a/src/core/operations/CSSBeautify.mjs b/src/core/operations/CSSBeautify.mjs new file mode 100644 index 00000000..d9835550 --- /dev/null +++ b/src/core/operations/CSSBeautify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * CSS Beautify operation + */ +class CSSBeautify extends Operation { + + /** + * CSSBeautify constructor + */ + constructor() { + super(); + + this.name = "CSS Beautify"; + this.module = "Code"; + this.description = "Indents and prettifies Cascading Style Sheets (CSS) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Indent string", + "type": "binaryShortString", + "value": "\\t" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const indentStr = args[0]; + return vkbeautify.css(input, indentStr); + } + +} + +export default CSSBeautify; diff --git a/src/core/operations/CSSMinify.mjs b/src/core/operations/CSSMinify.mjs new file mode 100644 index 00000000..2d489edc --- /dev/null +++ b/src/core/operations/CSSMinify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * CSS Minify operation + */ +class CSSMinify extends Operation { + + /** + * CSSMinify constructor + */ + constructor() { + super(); + + this.name = "CSS Minify"; + this.module = "Code"; + this.description = "Compresses Cascading Style Sheets (CSS) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Preserve comments", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const preserveComments = args[0]; + return vkbeautify.cssmin(input, preserveComments); + } + +} + +export default CSSMinify; diff --git a/src/core/operations/CSSSelector.mjs b/src/core/operations/CSSSelector.mjs new file mode 100644 index 00000000..c26d3142 --- /dev/null +++ b/src/core/operations/CSSSelector.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import xmldom from "xmldom"; +import nwmatcher from "nwmatcher"; + +/** + * CSS selector operation + */ +class CSSSelector extends Operation { + + /** + * CSSSelector constructor + */ + constructor() { + super(); + + this.name = "CSS selector"; + this.module = "Code"; + this.description = "Extract information from an HTML document with a CSS selector"; + this.infoURL = "https://wikipedia.org/wiki/Cascading_Style_Sheets#Selector"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "CSS selector", + "type": "string", + "value": "" + }, + { + "name": "Delimiter", + "type": "binaryShortString", + "value": "\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [query, delimiter] = args, + parser = new xmldom.DOMParser(); + let dom, + result; + + if (!query.length || !input.length) { + return ""; + } + + try { + dom = parser.parseFromString(input); + } catch (err) { + throw new OperationError("Invalid input HTML."); + } + + try { + const matcher = nwmatcher({document: dom}); + result = matcher.select(query, dom); + } catch (err) { + throw new OperationError("Invalid CSS Selector. Details:\n" + err.message); + } + + const nodeToString = function(node) { + return node.toString(); + /* xmldom does not return the outerHTML value. + switch (node.nodeType) { + case node.ELEMENT_NODE: return node.outerHTML; + case node.ATTRIBUTE_NODE: return node.value; + case node.TEXT_NODE: return node.wholeText; + case node.COMMENT_NODE: return node.data; + case node.DOCUMENT_NODE: return node.outerHTML; + default: throw new Error("Unknown Node Type: " + node.nodeType); + }*/ + }; + + return result + .map(nodeToString) + .join(delimiter); + } + +} + +export default CSSSelector; diff --git a/src/core/operations/CSVToJSON.mjs b/src/core/operations/CSVToJSON.mjs new file mode 100644 index 00000000..d2cdb53b --- /dev/null +++ b/src/core/operations/CSVToJSON.mjs @@ -0,0 +1,80 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; + +/** + * CSV to JSON operation + */ +class CSVToJSON extends Operation { + + /** + * CSVToJSON constructor + */ + constructor() { + super(); + + this.name = "CSV to JSON"; + this.module = "Default"; + this.description = "Converts a CSV file to JSON format."; + this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values"; + this.inputType = "string"; + this.outputType = "JSON"; + this.args = [ + { + name: "Cell delimiters", + type: "binaryShortString", + value: "," + }, + { + name: "Row delimiters", + type: "binaryShortString", + value: "\\r\\n" + }, + { + name: "Format", + type: "option", + value: ["Array of dictionaries", "Array of arrays"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + const [cellDelims, rowDelims, format] = args; + let json, header; + + try { + json = Utils.parseCSV(input, cellDelims.split(""), rowDelims.split("")); + } catch (err) { + throw new OperationError("Unable to parse CSV: " + err); + } + + switch (format) { + case "Array of dictionaries": + header = json[0]; + return json.slice(1).map(row => { + const obj = {}; + header.forEach((h, i) => { + obj[h] = row[i]; + }); + return obj; + }); + case "Array of arrays": + default: + return json; + } + } + +} + +export default CSVToJSON; diff --git a/src/core/operations/CTPH.mjs b/src/core/operations/CTPH.mjs new file mode 100644 index 00000000..feb58d44 --- /dev/null +++ b/src/core/operations/CTPH.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import ctphjs from "ctph.js"; + +/** + * CTPH operation + */ +class CTPH extends Operation { + + /** + * CTPH constructor + */ + constructor() { + super(); + + this.name = "CTPH"; + this.module = "Crypto"; + this.description = "Context Triggered Piecewise Hashing, also called Fuzzy Hashing, can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.

CTPH was originally based on the work of Dr. Andrew Tridgell and a spam email detector called SpamSum. This method was adapted by Jesse Kornblum and published at the DFRWS conference in 2006 in a paper 'Identifying Almost Identical Files Using Context Triggered Piecewise Hashing'."; + this.infoURL = "https://forensicswiki.org/wiki/Context_Triggered_Piecewise_Hashing"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return ctphjs.digest(input); + } + +} + +export default CTPH; diff --git a/src/core/operations/CartesianProduct.mjs b/src/core/operations/CartesianProduct.mjs new file mode 100644 index 00000000..cd32c72f --- /dev/null +++ b/src/core/operations/CartesianProduct.mjs @@ -0,0 +1,97 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Set cartesian product operation + */ +class CartesianProduct extends Operation { + + /** + * Cartesian Product constructor + */ + constructor() { + super(); + + this.name = "Cartesian Product"; + this.module = "Default"; + this.description = "Calculates the cartesian product of multiple sets of data, returning all possible combinations."; + this.infoURL = "https://wikipedia.org/wiki/Cartesian_product"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {OperationError} if fewer than 2 sets + */ + validateSampleNumbers(sets) { + if (!sets || sets.length < 2) { + throw new OperationError("Incorrect number of sets, perhaps you" + + " need to modify the sample delimiter or add more samples?"); + } + } + + /** + * Run the product operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runCartesianProduct(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Return the cartesian product of the two inputted sets. + * + * @param {Object[]} a + * @param {Object[]} b + * @param {Object[]} c + * @returns {string} + */ + runCartesianProduct(a, b, ...c) { + /** + * https://stackoverflow.com/a/43053803/7200497 + * @returns {Object[]} + */ + const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e)))); + /** + * https://stackoverflow.com/a/43053803/7200497 + * @returns {Object[][]} + */ + const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a); + + return cartesian(a, b, ...c) + .map(set => `(${set.join(",")})`) + .join(this.itemDelimiter); + } +} + +export default CartesianProduct; diff --git a/src/core/operations/ChangeIPFormat.mjs b/src/core/operations/ChangeIPFormat.mjs new file mode 100644 index 00000000..56c5ffe1 --- /dev/null +++ b/src/core/operations/ChangeIPFormat.mjs @@ -0,0 +1,120 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; +import {fromHex} from "../lib/Hex"; + +/** + * Change IP format operation + */ +class ChangeIPFormat extends Operation { + + /** + * ChangeIPFormat constructor + */ + constructor() { + super(); + + this.name = "Change IP format"; + this.module = "Default"; + this.description = "Convert an IP address from one format to another, e.g. 172.20.23.54 to ac141736"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["Dotted Decimal", "Decimal", "Hex"] + }, + { + "name": "Output format", + "type": "option", + "value": ["Dotted Decimal", "Decimal", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inFormat, outFormat] = args, + lines = input.split("\n"); + let output = "", + j = 0; + + for (let i = 0; i < lines.length; i++) { + if (lines[i] === "") continue; + let baIp = []; + let octets; + let decimal; + + if (inFormat === outFormat) { + output += lines[i] + "\n"; + continue; + } + + // Convert to byte array IP from input format + switch (inFormat) { + case "Dotted Decimal": + octets = lines[i].split("."); + for (j = 0; j < octets.length; j++) { + baIp.push(parseInt(octets[j], 10)); + } + break; + case "Decimal": + decimal = lines[i].toString(); + baIp.push(decimal >> 24 & 255); + baIp.push(decimal >> 16 & 255); + baIp.push(decimal >> 8 & 255); + baIp.push(decimal & 255); + break; + case "Hex": + baIp = fromHex(lines[i]); + break; + default: + throw new OperationError("Unsupported input IP format"); + } + + let ddIp; + let decIp; + let hexIp; + + // Convert byte array IP to output format + switch (outFormat) { + case "Dotted Decimal": + ddIp = ""; + for (j = 0; j < baIp.length; j++) { + ddIp += baIp[j] + "."; + } + output += ddIp.slice(0, ddIp.length-1) + "\n"; + break; + case "Decimal": + decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0; + output += decIp.toString() + "\n"; + break; + case "Hex": + hexIp = ""; + for (j = 0; j < baIp.length; j++) { + hexIp += Utils.hex(baIp[j]); + } + output += hexIp + "\n"; + break; + default: + throw new OperationError("Unsupported output IP format"); + } + } + + return output.slice(0, output.length-1); + } + +} + +export default ChangeIPFormat; diff --git a/src/core/operations/CharEnc.js b/src/core/operations/CharEnc.js deleted file mode 100755 index 24b3cccf..00000000 --- a/src/core/operations/CharEnc.js +++ /dev/null @@ -1,99 +0,0 @@ -import cptable from "../lib/js-codepage/cptable.js"; -import Utils from "../Utils.js"; -import CryptoJS from "crypto-js"; - - -/** - * Character encoding operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const CharEnc = { - - /** - * @constant - * @default - */ - IO_FORMAT: { - "UTF-8 (65001)": 65001, - "UTF-7 (65000)": 65000, - "UTF16LE (1200)": 1200, - "UTF16BE (1201)": 1201, - "UTF16 (1201)": 1201, - "IBM EBCDIC International (500)": 500, - "IBM EBCDIC US-Canada (37)": 37, - "Windows-874 Thai (874)": 874, - "Japanese Shift-JIS (932)": 932, - "Simplified Chinese GBK (936)": 936, - "Korean (949)": 949, - "Traditional Chinese Big5 (950)": 950, - "Windows-1250 Central European (1250)": 1250, - "Windows-1251 Cyrillic (1251)": 1251, - "Windows-1252 Latin (1252)": 1252, - "Windows-1253 Greek (1253)": 1253, - "Windows-1254 Turkish (1254)": 1254, - "Windows-1255 Hebrew (1255)": 1255, - "Windows-1256 Arabic (1256)": 1256, - "Windows-1257 Baltic (1257)": 1257, - "Windows-1258 Vietnam (1258)": 1258, - "US-ASCII (20127)": 20127, - "Simplified Chinese GB2312 (20936)": 20936, - "KOI8-R Russian Cyrillic (20866)": 20866, - "KOI8-U Ukrainian Cyrillic (21866)": 21866, - "ISO-8859-1 Latin 1 Western European (28591)": 28591, - "ISO-8859-2 Latin 2 Central European (28592)": 28592, - "ISO-8859-3 Latin 3 South European (28593)": 28593, - "ISO-8859-4 Latin 4 North European (28594)": 28594, - "ISO-8859-5 Latin/Cyrillic (28595)": 28595, - "ISO-8859-6 Latin/Arabic (28596)": 28596, - "ISO-8859-7 Latin/Greek (28597)": 28597, - "ISO-8859-8 Latin/Hebrew (28598)": 28598, - "ISO-8859-9 Latin 5 Turkish (28599)": 28599, - "ISO-8859-10 Latin 6 Nordic (28600)": 28600, - "ISO-8859-11 Latin/Thai (28601)": 28601, - "ISO-8859-13 Latin 7 Baltic Rim (28603)": 28603, - "ISO-8859-14 Latin 8 Celtic (28604)": 28604, - "ISO-8859-15 Latin 9 (28605)": 28605, - "ISO-8859-16 Latin 10 (28606)": 28606, - "ISO-2022 JIS Japanese (50222)": 50222, - "EUC Japanese (51932)": 51932, - "EUC Korean (51949)": 51949, - "Simplified Chinese GB18030 (54936)": 54936, - }, - - /** - * Encode text operation. - * @author tlwr [toby@toby.codes] - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runEncode: function(input, args) { - const format = CharEnc.IO_FORMAT[args[0]]; - let encoded = cptable.utils.encode(format, input); - encoded = Array.from(encoded); - return encoded; - }, - - - /** - * Decode text operation. - * @author tlwr [toby@toby.codes] - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runDecode: function(input, args) { - const format = CharEnc.IO_FORMAT[args[0]]; - let decoded = cptable.utils.decode(format, input); - return decoded; - }, -}; - -export default CharEnc; diff --git a/src/core/operations/Checksum.js b/src/core/operations/Checksum.js deleted file mode 100755 index f71e535d..00000000 --- a/src/core/operations/Checksum.js +++ /dev/null @@ -1,195 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Checksum operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Checksum = { - - /** - * Fletcher-8 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runFletcher8: function(input, args) { - let a = 0, - b = 0; - - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xf; - b = (b + a) % 0xf; - } - - return Utils.hex(((b << 4) | a) >>> 0, 2); - }, - - - /** - * Fletcher-16 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runFletcher16: function(input, args) { - let a = 0, - b = 0; - - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xff; - b = (b + a) % 0xff; - } - - return Utils.hex(((b << 8) | a) >>> 0, 4); - }, - - - /** - * Fletcher-32 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runFletcher32: function(input, args) { - let a = 0, - b = 0; - - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xffff; - b = (b + a) % 0xffff; - } - - return Utils.hex(((b << 16) | a) >>> 0, 8); - }, - - - /** - * Fletcher-64 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runFletcher64: function(input, args) { - let a = 0, - b = 0; - - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xffffffff; - b = (b + a) % 0xffffffff; - } - - return Utils.hex(b >>> 0, 8) + Utils.hex(a >>> 0, 8); - }, - - - /** - * Adler-32 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runAdler32: function(input, args) { - let MOD_ADLER = 65521, - a = 1, - b = 0; - - for (let i = 0; i < input.length; i++) { - a += input[i]; - b += a; - } - - a %= MOD_ADLER; - b %= MOD_ADLER; - - return Utils.hex(((b << 16) | a) >>> 0, 8); - }, - - - /** - * CRC-32 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runCRC32: function(input, args) { - let crcTable = global.crcTable || (global.crcTable = Checksum._genCRCTable()), - crc = 0 ^ (-1); - - for (let i = 0; i < input.length; i++) { - crc = (crc >>> 8) ^ crcTable[(crc ^ input[i]) & 0xff]; - } - - return Utils.hex((crc ^ (-1)) >>> 0); - }, - - - /** - * TCP/IP Checksum operation. - * - * @author GCHQ Contributor [1] - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - * - * @example - * // returns '3f2c' - * Checksum.runTcpIp([0x45,0x00,0x00,0x87,0xa3,0x1b,0x40,0x00,0x40,0x06, - * 0x00,0x00,0xac,0x11,0x00,0x04,0xac,0x11,0x00,0x03]) - * - * // returns 'a249' - * Checksum.runTcpIp([0x45,0x00,0x01,0x11,0x3f,0x74,0x40,0x00,0x40,0x06, - * 0x00,0x00,0xac,0x11,0x00,0x03,0xac,0x11,0x00,0x04]) - */ - runTCPIP: function(input, args) { - let csum = 0; - - for (let i = 0; i < input.length; i++) { - if (i % 2 === 0) { - csum += (input[i] << 8); - } else { - csum += input[i]; - } - } - - csum = (csum >> 16) + (csum & 0xffff); - - return Utils.hex(0xffff - csum); - }, - - - /** - * Generates a CRC table for use with CRC checksums. - * - * @private - * @returns {array} - */ - _genCRCTable: function() { - let c, - crcTable = []; - - for (let n = 0; n < 256; n++) { - c = n; - for (let k = 0; k < 8; k++) { - c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); - } - crcTable[n] = c; - } - - return crcTable; - }, - -}; - -export default Checksum; diff --git a/src/core/operations/ChiSquare.mjs b/src/core/operations/ChiSquare.mjs new file mode 100644 index 00000000..5681abc8 --- /dev/null +++ b/src/core/operations/ChiSquare.mjs @@ -0,0 +1,54 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Chi Square operation + */ +class ChiSquare extends Operation { + + /** + * ChiSquare constructor + */ + constructor() { + super(); + + this.name = "Chi Square"; + this.module = "Default"; + this.description = "Calculates the Chi Square distribution of values."; + this.infoURL = "https://wikipedia.org/wiki/Chi-squared_distribution"; + this.inputType = "ArrayBuffer"; + this.outputType = "number"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const data = new Uint8Array(input); + const distArray = new Array(256).fill(0); + let total = 0; + + for (let i = 0; i < data.length; i++) { + distArray[data[i]]++; + } + + for (let i = 0; i < distArray.length; i++) { + if (distArray[i] > 0) { + total += Math.pow(distArray[i] - data.length / 256, 2) / (data.length / 256); + } + } + + return total; + } + +} + +export default ChiSquare; diff --git a/src/core/operations/Cipher.js b/src/core/operations/Cipher.js deleted file mode 100755 index 8db28d0b..00000000 --- a/src/core/operations/Cipher.js +++ /dev/null @@ -1,676 +0,0 @@ -import Utils from "../Utils.js"; -import CryptoJS from "crypto-js"; -import {blowfish as Blowfish} from "sladex-blowfish"; - - -/** - * Cipher operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Cipher = { - - /** - * @constant - * @default - */ - IO_FORMAT1: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"], - /** - * @constant - * @default - */ - IO_FORMAT2: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"], - /** - * @constant - * @default - */ - IO_FORMAT3: ["Hex", "Base64", "UTF16", "UTF16LE", "UTF16BE", "Latin1"], - /** - * @constant - * @default - */ - IO_FORMAT4: ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"], - /** - * @constant - * @default - */ - MODES: ["CBC", "CFB", "CTR", "OFB", "ECB"], - /** - * @constant - * @default - */ - PADDING: ["Pkcs7", "Iso97971", "AnsiX923", "Iso10126", "ZeroPadding", "NoPadding"], - /** - * @constant - * @default - */ - RESULT_TYPE: ["Show all", "Ciphertext", "Key", "IV", "Salt"], - - - /** - * Runs encryption operations using the CryptoJS framework. - * - * @private - * @param {function} algo - The CryptoJS algorithm to use - * @param {byteArray} input - * @param {function} args - * @returns {string} - */ - _enc: function (algo, input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""), - iv = Utils.format[args[1].option].parse(args[1].string || ""), - salt = Utils.format[args[2].option].parse(args[2].string || ""), - mode = CryptoJS.mode[args[3]], - padding = CryptoJS.pad[args[4]], - resultOption = args[5].toLowerCase(), - outputFormat = args[6]; - - if (iv.sigBytes === 0) { - // Use passphrase rather than key. Need to convert it to a string. - key = key.toString(CryptoJS.enc.Latin1); - } - - const encrypted = algo.encrypt(input, key, { - salt: salt.sigBytes > 0 ? salt : false, - iv: iv.sigBytes > 0 ? iv : null, - mode: mode, - padding: padding - }); - - let result = ""; - if (resultOption === "show all") { - result += "Key: " + encrypted.key.toString(Utils.format[outputFormat]); - result += "\nIV: " + encrypted.iv.toString(Utils.format[outputFormat]); - if (encrypted.salt) result += "\nSalt: " + encrypted.salt.toString(Utils.format[outputFormat]); - result += "\n\nCiphertext: " + encrypted.ciphertext.toString(Utils.format[outputFormat]); - } else { - result = encrypted[resultOption].toString(Utils.format[outputFormat]); - } - - return result; - }, - - - /** - * Runs decryption operations using the CryptoJS framework. - * - * @private - * @param {function} algo - The CryptoJS algorithm to use - * @param {byteArray} input - * @param {function} args - * @returns {string} - */ - _dec: function (algo, input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""), - iv = Utils.format[args[1].option].parse(args[1].string || ""), - salt = Utils.format[args[2].option].parse(args[2].string || ""), - mode = CryptoJS.mode[args[3]], - padding = CryptoJS.pad[args[4]], - inputFormat = args[5], - outputFormat = args[6]; - - // The ZeroPadding option causes a crash when the input length is 0 - if (!input.length) { - return "No input"; - } - - const ciphertext = Utils.format[inputFormat].parse(input); - - if (iv.sigBytes === 0) { - // Use passphrase rather than key. Need to convert it to a string. - key = key.toString(CryptoJS.enc.Latin1); - } - - const decrypted = algo.decrypt({ - ciphertext: ciphertext, - salt: salt.sigBytes > 0 ? salt : false - }, key, { - iv: iv.sigBytes > 0 ? iv : null, - mode: mode, - padding: padding - }); - - let result; - try { - result = decrypted.toString(Utils.format[outputFormat]); - } catch (err) { - result = "Decrypt error: " + err.message; - } - - return result; - }, - - - /** - * AES Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAesEnc: function (input, args) { - return Cipher._enc(CryptoJS.AES, input, args); - }, - - - /** - * AES Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAesDec: function (input, args) { - return Cipher._dec(CryptoJS.AES, input, args); - }, - - - /** - * DES Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDesEnc: function (input, args) { - return Cipher._enc(CryptoJS.DES, input, args); - }, - - - /** - * DES Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDesDec: function (input, args) { - return Cipher._dec(CryptoJS.DES, input, args); - }, - - - /** - * Triple DES Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runTripleDesEnc: function (input, args) { - return Cipher._enc(CryptoJS.TripleDES, input, args); - }, - - - /** - * Triple DES Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runTripleDesDec: function (input, args) { - return Cipher._dec(CryptoJS.TripleDES, input, args); - }, - - - /** - * Rabbit Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRabbitEnc: function (input, args) { - return Cipher._enc(CryptoJS.Rabbit, input, args); - }, - - - /** - * Rabbit Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRabbitDec: function (input, args) { - return Cipher._dec(CryptoJS.Rabbit, input, args); - }, - - - /** - * @constant - * @default - */ - BLOWFISH_MODES: ["ECB", "CBC", "PCBC", "CFB", "OFB", "CTR"], - /** - * @constant - * @default - */ - BLOWFISH_OUTPUT_TYPES: ["Base64", "Hex", "String", "Raw"], - - /** - * Blowfish Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runBlowfishEnc: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string).toString(Utils.format.Latin1), - mode = args[1], - outputFormat = args[2]; - - if (key.length === 0) return "Enter a key"; - - let encHex = Blowfish.encrypt(input, key, { - outputType: 1, - cipherMode: Cipher.BLOWFISH_MODES.indexOf(mode) - }), - enc = CryptoJS.enc.Hex.parse(encHex); - - return enc.toString(Utils.format[outputFormat]); - }, - - - /** - * Blowfish Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runBlowfishDec: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string).toString(Utils.format.Latin1), - mode = args[1], - inputFormat = args[2]; - - if (key.length === 0) return "Enter a key"; - - input = Utils.format[inputFormat].parse(input); - - return Blowfish.decrypt(input.toString(CryptoJS.enc.Base64), key, { - outputType: 0, // This actually means inputType. The library is weird. - cipherMode: Cipher.BLOWFISH_MODES.indexOf(mode) - }); - }, - - - /** - * @constant - * @default - */ - KDF_KEY_SIZE: 256, - /** - * @constant - * @default - */ - KDF_ITERATIONS: 1, - /** - * @constant - * @default - */ - HASHERS: ["MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "RIPEMD160"], - - /** - * Derive PBKDF2 key operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runPbkdf2: function (input, args) { - let keySize = args[0] / 32, - iterations = args[1], - hasher = args[2], - salt = CryptoJS.enc.Hex.parse(args[3] || ""), - inputFormat = args[4], - outputFormat = args[5], - passphrase = Utils.format[inputFormat].parse(input), - key = CryptoJS.PBKDF2(passphrase, salt, { - keySize: keySize, - hasher: CryptoJS.algo[hasher], - iterations: iterations, - }); - - return key.toString(Utils.format[outputFormat]); - }, - - - /** - * Derive EVP key operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runEvpkdf: function (input, args) { - let keySize = args[0] / 32, - iterations = args[1], - hasher = args[2], - salt = CryptoJS.enc.Hex.parse(args[3] || ""), - inputFormat = args[4], - outputFormat = args[5], - passphrase = Utils.format[inputFormat].parse(input), - key = CryptoJS.EvpKDF(passphrase, salt, { - keySize: keySize, - hasher: CryptoJS.algo[hasher], - iterations: iterations, - }); - - return key.toString(Utils.format[outputFormat]); - }, - - - /** - * RC4 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRc4: function (input, args) { - let message = Utils.format[args[1]].parse(input), - passphrase = Utils.format[args[0].option].parse(args[0].string), - encrypted = CryptoJS.RC4.encrypt(message, passphrase); - - return encrypted.ciphertext.toString(Utils.format[args[2]]); - }, - - - /** - * @constant - * @default - */ - RC4DROP_BYTES: 768, - - /** - * RC4 Drop operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRc4drop: function (input, args) { - let message = Utils.format[args[1]].parse(input), - passphrase = Utils.format[args[0].option].parse(args[0].string), - drop = args[3], - encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop }); - - return encrypted.ciphertext.toString(Utils.format[args[2]]); - }, - - - /** - * Vigenère Encode operation. - * - * @author Matt C [matt@artemisbot.pw] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runVigenereEnc: function (input, args) { - let alphabet = "abcdefghijklmnopqrstuvwxyz", - key = args[0].toLowerCase(), - output = "", - fail = 0, - keyIndex, - msgIndex, - chr; - - if (!key) return "No key entered"; - if (!/^[a-zA-Z]+$/.test(key)) return "The key must consist only of letters"; - - for (let i = 0; i < input.length; i++) { - if (alphabet.indexOf(input[i]) >= 0) { - // Get the corresponding character of key for the current letter, accounting - // for chars not in alphabet - chr = key[(i - fail) % key.length]; - // Get the location in the vigenere square of the key char - keyIndex = alphabet.indexOf(chr); - // Get the location in the vigenere square of the message char - msgIndex = alphabet.indexOf(input[i]); - // Get the encoded letter by finding the sum of indexes modulo 26 and finding - // the letter corresponding to that - output += alphabet[(keyIndex + msgIndex) % 26]; - } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { - chr = key[(i - fail) % key.length].toLowerCase(); - keyIndex = alphabet.indexOf(chr); - msgIndex = alphabet.indexOf(input[i].toLowerCase()); - output += alphabet[(keyIndex + msgIndex) % 26].toUpperCase(); - } else { - output += input[i]; - fail++; - } - } - - return output; - }, - - - /** - * Vigenère Decode operation. - * - * @author Matt C [matt@artemisbot.pw] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runVigenereDec: function (input, args) { - let alphabet = "abcdefghijklmnopqrstuvwxyz", - key = args[0].toLowerCase(), - output = "", - fail = 0, - keyIndex, - msgIndex, - chr; - - if (!key) return "No key entered"; - if (!/^[a-zA-Z]+$/.test(key)) return "The key must consist only of letters"; - - for (let i = 0; i < input.length; i++) { - if (alphabet.indexOf(input[i]) >= 0) { - chr = key[(i - fail) % key.length]; - keyIndex = alphabet.indexOf(chr); - msgIndex = alphabet.indexOf(input[i]); - // Subtract indexes from each other, add 26 just in case the value is negative, - // modulo to remove if neccessary - output += alphabet[(msgIndex - keyIndex + alphabet.length) % 26]; - } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { - chr = key[(i - fail) % key.length].toLowerCase(); - keyIndex = alphabet.indexOf(chr); - msgIndex = alphabet.indexOf(input[i].toLowerCase()); - output += alphabet[(msgIndex + alphabet.length - keyIndex) % 26].toUpperCase(); - } else { - output += input[i]; - fail++; - } - } - - return output; - }, - - - /** - * @constant - * @default - */ - AFFINE_A: 1, - /** - * @constant - * @default - */ - AFFINE_B: 0, - - /** - * Affine Cipher Encode operation. - * - * @author Matt C [matt@artemisbot.pw] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAffineEnc: function (input, args) { - let alphabet = "abcdefghijklmnopqrstuvwxyz", - a = args[0], - b = args[1], - output = ""; - - if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) { - return "The values of a and b can only be integers."; - } - - for (let i = 0; i < input.length; i++) { - if (alphabet.indexOf(input[i]) >= 0) { - // Uses the affine function ax+b % m = y (where m is length of the alphabet) - output += alphabet[((a * alphabet.indexOf(input[i])) + b) % 26]; - } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { - // Same as above, accounting for uppercase - output += alphabet[((a * alphabet.indexOf(input[i].toLowerCase())) + b) % 26].toUpperCase(); - } else { - // Non-alphabetic characters - output += input[i]; - } - } - return output; - }, - - - /** - * Affine Cipher Encode operation. - * - * @author Matt C [matt@artemisbot.pw] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAffineDec: function (input, args) { - let alphabet = "abcdefghijklmnopqrstuvwxyz", - a = args[0], - b = args[1], - output = "", - aModInv; - - if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) { - return "The values of a and b can only be integers."; - } - - if (Utils.gcd(a, 26) !== 1) { - return "The value of a must be coprime to 26."; - } - - // Calculates modular inverse of a - aModInv = Utils.modInv(a, 26); - - for (let i = 0; i < input.length; i++) { - if (alphabet.indexOf(input[i]) >= 0) { - // Uses the affine decode function (y-b * A') % m = x (where m is length of the alphabet and A' is modular inverse) - output += alphabet[Utils.mod((alphabet.indexOf(input[i]) - b) * aModInv, 26)]; - } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { - // Same as above, accounting for uppercase - output += alphabet[Utils.mod((alphabet.indexOf(input[i].toLowerCase()) - b) * aModInv, 26)].toUpperCase(); - } else { - // Non-alphabetic characters - output += input[i]; - } - } - return output; - }, - - - /** - * Atbash Cipher Encode operation. - * - * @author Matt C [matt@artemisbot.pw] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAtbash: function (input, args) { - return Cipher.runAffineEnc(input, [25, 25]); - }, - - - /** - * @constant - * @default - */ - SUBS_PLAINTEXT: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - /** - * @constant - * @default - */ - SUBS_CIPHERTEXT: "XYZABCDEFGHIJKLMNOPQRSTUVW", - - /** - * Substitute operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runSubstitute: function (input, args) { - let plaintext = Utils.strToByteArray(Utils.expandAlphRange(args[0]).join()), - ciphertext = Utils.strToByteArray(Utils.expandAlphRange(args[1]).join()), - output = [], - index = -1; - - if (plaintext.length !== ciphertext.length) { - output = Utils.strToByteArray("Warning: Plaintext and Ciphertext lengths differ\n\n"); - } - - for (let i = 0; i < input.length; i++) { - index = plaintext.indexOf(input[i]); - output.push(index > -1 && index < ciphertext.length ? ciphertext[index] : input[i]); - } - - return output; - }, - -}; - -export default Cipher; - - -/** - * Overwriting the CryptoJS OpenSSL key derivation function so that it is possible to not pass a - * salt in. - - * @param {string} password - The password to derive from. - * @param {number} keySize - The size in words of the key to generate. - * @param {number} ivSize - The size in words of the IV to generate. - * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be - * generated randomly. If set to false, no salt will be added. - * - * @returns {CipherParams} A cipher params object with the key, IV, and salt. - * - * @static - * - * @example - * // Randomly generates a salt - * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); - * // Uses the salt 'saltsalt' - * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); - * // Does not use a salt - * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, false); - */ -CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) { - // Generate random salt if no salt specified and not set to false - // This line changed from `if (!salt) {` to the following - if (salt === undefined || salt === null) { - salt = CryptoJS.lib.WordArray.random(64/8); - } - - // Derive key and IV - const key = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); - - // Separate key and IV - const iv = CryptoJS.lib.WordArray.create(key.words.slice(keySize), ivSize * 4); - key.sigBytes = keySize * 4; - - // Return params - return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt }); -}; diff --git a/src/core/operations/CitrixCTX1Decode.mjs b/src/core/operations/CitrixCTX1Decode.mjs new file mode 100644 index 00000000..31c20ee9 --- /dev/null +++ b/src/core/operations/CitrixCTX1Decode.mjs @@ -0,0 +1,58 @@ +/** + * @author bwhitn [brian.m.whitney@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import cptable from "../vendor/js-codepage/cptable.js"; + +/** + * Citrix CTX1 Decode operation + */ +class CitrixCTX1Decode extends Operation { + + /** + * CitrixCTX1Decode constructor + */ + constructor() { + super(); + + this.name = "Citrix CTX1 Decode"; + this.module = "Encodings"; + this.description = "Decodes strings in a Citrix CTX1 password format to plaintext."; + this.infoURL = "https://www.reddit.com/r/AskNetsec/comments/1s3r6y/citrix_ctx1_hash_decoding/"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.length % 4 !== 0) { + throw new OperationError("Incorrect hash length"); + } + const revinput = input.reverse(); + const result = []; + let temp = 0; + for (let i = 0; i < revinput.length; i += 2) { + if (i + 2 >= revinput.length) { + temp = 0; + } else { + temp = ((revinput[i + 2] - 0x41) & 0xf) ^ (((revinput[i + 3]- 0x41) << 4) & 0xf0); + } + temp = (((revinput[i] - 0x41) & 0xf) ^ (((revinput[i + 1] - 0x41) << 4) & 0xf0)) ^ 0xa5 ^ temp; + result.push(temp); + } + // Decodes a utf-16le string + return cptable.utils.decode(1200, result.reverse()); + } + +} + +export default CitrixCTX1Decode; diff --git a/src/core/operations/CitrixCTX1Encode.mjs b/src/core/operations/CitrixCTX1Encode.mjs new file mode 100644 index 00000000..add563d8 --- /dev/null +++ b/src/core/operations/CitrixCTX1Encode.mjs @@ -0,0 +1,50 @@ +/** + * @author bwhitn [brian.m.whitney@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import cptable from "../vendor/js-codepage/cptable.js"; + +/** + * Citrix CTX1 Encode operation + */ +class CitrixCTX1Encode extends Operation { + + /** + * CitrixCTX1Encode constructor + */ + constructor() { + super(); + + this.name = "Citrix CTX1 Encode"; + this.module = "Encodings"; + this.description = "Encodes strings to Citrix CTX1 password format."; + this.infoURL = "https://www.reddit.com/r/AskNetsec/comments/1s3r6y/citrix_ctx1_hash_decoding/"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const utf16pass = Array.from(cptable.utils.encode(1200, input)); + const result = []; + let temp = 0; + for (let i = 0; i < utf16pass.length; i++) { + temp = utf16pass[i] ^ 0xa5 ^ temp; + result.push(((temp >>> 4) & 0xf) + 0x41); + result.push((temp & 0xf) + 0x41); + } + + return result; + } + +} + +export default CitrixCTX1Encode; diff --git a/src/core/operations/Code.js b/src/core/operations/Code.js deleted file mode 100755 index c1df6714..00000000 --- a/src/core/operations/Code.js +++ /dev/null @@ -1,501 +0,0 @@ -import {camelCase, kebabCase, snakeCase} from "lodash"; - -import Utils from "../Utils.js"; -import vkbeautify from "vkbeautify"; -import {DOMParser as dom} from "xmldom"; -import xpath from "xpath"; -import prettyPrintOne from "imports-loader?window=>global!exports-loader?prettyPrintOne!google-code-prettify/bin/prettify.min.js"; - - -/** - * Code operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Code = { - - /** - * @constant - * @default - */ - LANGUAGES: ["default-code", "default-markup", "bash", "bsh", "c", "cc", "coffee", "cpp", "cs", "csh", "cv", "cxx", "cyc", "htm", "html", "in.tag", "java", "javascript", "js", "json", "m", "mxml", "perl", "pl", "pm", "py", "python", "rb", "rc", "rs", "ruby", "rust", "sh", "uq.val", "xhtml", "xml", "xsl"], - /** - * @constant - * @default - */ - LINE_NUMS: false, - - /** - * Syntax highlighter operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runSyntaxHighlight: function(input, args) { - let language = args[0], - lineNums = args[1]; - return "" + prettyPrintOne(Utils.escapeHtml(input), language, lineNums) + ""; - }, - - - /** - * @constant - * @default - */ - BEAUTIFY_INDENT: "\\t", - - /** - * XML Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runXmlBeautify: function(input, args) { - const indentStr = args[0]; - return vkbeautify.xml(input, indentStr); - }, - - - /** - * JSON Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runJsonBeautify: function(input, args) { - const indentStr = args[0]; - if (!input) return ""; - return vkbeautify.json(input, indentStr); - }, - - - /** - * CSS Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runCssBeautify: function(input, args) { - const indentStr = args[0]; - return vkbeautify.css(input, indentStr); - }, - - - /** - * SQL Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSqlBeautify: function(input, args) { - const indentStr = args[0]; - return vkbeautify.sql(input, indentStr); - }, - - - /** - * @constant - * @default - */ - PRESERVE_COMMENTS: false, - - /** - * XML Minify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runXmlMinify: function(input, args) { - const preserveComments = args[0]; - return vkbeautify.xmlmin(input, preserveComments); - }, - - - /** - * JSON Minify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runJsonMinify: function(input, args) { - if (!input) return ""; - return vkbeautify.jsonmin(input); - }, - - - /** - * CSS Minify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runCssMinify: function(input, args) { - const preserveComments = args[0]; - return vkbeautify.cssmin(input, preserveComments); - }, - - - /** - * SQL Minify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSqlMinify: function(input, args) { - return vkbeautify.sqlmin(input); - }, - - - /** - * Generic Code Beautify operation. - * - * Yeeeaaah... - * - * I'm not proud of this code, but seriously, try writing a generic lexer and parser that - * correctly generates an AST for multiple different languages. I have tried, and I can tell - * you it's pretty much impossible. - * - * This basically works. That'll have to be good enough. It's not meant to produce working code, - * just slightly more readable code. - * - * Things that don't work: - * - For loop formatting - * - Do-While loop formatting - * - Switch/Case indentation - * - Bit shift operators - * - * @author n1474335 [n1474335@gmail.com] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runGenericBeautify: function(input, args) { - let code = input, - t = 0, - preservedTokens = [], - m; - - // Remove strings - const sstrings = /'([^'\\]|\\.)*'/g; - while ((m = sstrings.exec(code))) { - code = preserveToken(code, m, t++); - sstrings.lastIndex = m.index; - } - - const dstrings = /"([^"\\]|\\.)*"/g; - while ((m = dstrings.exec(code))) { - code = preserveToken(code, m, t++); - dstrings.lastIndex = m.index; - } - - // Remove comments - const scomments = /\/\/[^\n\r]*/g; - while ((m = scomments.exec(code))) { - code = preserveToken(code, m, t++); - scomments.lastIndex = m.index; - } - - const mcomments = /\/\*[\s\S]*?\*\//gm; - while ((m = mcomments.exec(code))) { - code = preserveToken(code, m, t++); - mcomments.lastIndex = m.index; - } - - const hcomments = /(^|\n)#[^\n\r#]+/g; - while ((m = hcomments.exec(code))) { - code = preserveToken(code, m, t++); - hcomments.lastIndex = m.index; - } - - // Remove regexes - const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi; - while ((m = regexes.exec(code))) { - code = preserveToken(code, m, t++); - regexes.lastIndex = m.index; - } - - code = code - // Create newlines after ; - .replace(/;/g, ";\n") - // Create newlines after { and around } - .replace(/{/g, "{\n") - .replace(/}/g, "\n}\n") - // Remove carriage returns - .replace(/\r/g, "") - // Remove all indentation - .replace(/^\s+/g, "") - .replace(/\n\s+/g, "\n") - // Remove trailing spaces - .replace(/\s*$/g, "") - .replace(/\n{/g, "{"); - - // Indent - let i = 0, - level = 0, - indent; - while (i < code.length) { - switch (code[i]) { - case "{": - level++; - break; - case "\n": - if (i+1 >= code.length) break; - - if (code[i+1] === "}") level--; - indent = (level >= 0) ? Array(level*4+1).join(" ") : ""; - - code = code.substring(0, i+1) + indent + code.substring(i+1); - if (level > 0) i += level*4; - break; - } - i++; - } - - code = code - // Add strategic spaces - .replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ") - .replace(/\s*<([=]?)\s*/g, " <$1 ") - .replace(/\s*>([=]?)\s*/g, " >$1 ") - .replace(/([^+])\+([^+=])/g, "$1 + $2") - .replace(/([^-])-([^-=])/g, "$1 - $2") - .replace(/([^*])\*([^*=])/g, "$1 * $2") - .replace(/([^/])\/([^/=])/g, "$1 / $2") - .replace(/\s*,\s*/g, ", ") - .replace(/\s*{/g, " {") - .replace(/}\n/g, "}\n\n") - // Hacky horribleness - .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3") - .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3") - .replace(/else\s*\n([^{])/gim, "else\n $1") - .replace(/else\s+([^{])/gim, "else $1") - // Remove strategic spaces - .replace(/\s+;/g, ";") - .replace(/\{\s+\}/g, "{}") - .replace(/\[\s+\]/g, "[]") - .replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1"); - - // Replace preserved tokens - const ptokens = /###preservedToken(\d+)###/g; - while ((m = ptokens.exec(code))) { - const ti = parseInt(m[1], 10); - code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length); - ptokens.lastIndex = m.index; - } - - return code; - - /** - * Replaces a matched token with a placeholder value. - */ - function preserveToken(str, match, t) { - preservedTokens[t] = match[0]; - return str.substring(0, match.index) + - "###preservedToken" + t + "###" + - str.substring(match.index + match[0].length); - } - }, - - - /** - * @constant - * @default - */ - XPATH_INITIAL: "", - - /** - * @constant - * @default - */ - XPATH_DELIMITER: "\\n", - - /** - * XPath expression operation. - * - * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runXpath:function(input, args) { - let query = args[0], - delimiter = args[1]; - - let doc; - try { - doc = new dom().parseFromString(input); - } catch (err) { - return "Invalid input XML."; - } - - let nodes; - try { - nodes = xpath.select(query, doc); - } catch (err) { - return "Invalid XPath. Details:\n" + err.message; - } - - const nodeToString = function(node) { - return node.toString(); - }; - - return nodes.map(nodeToString).join(delimiter); - }, - - - /** - * @constant - * @default - */ - CSS_SELECTOR_INITIAL: "", - - /** - * @constant - * @default - */ - CSS_QUERY_DELIMITER: "\\n", - - /** - * CSS selector operation. - * - * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) - * @author n1474335 [n1474335@gmail.com] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runCSSQuery: function(input, args) { - let query = args[0], - delimiter = args[1], - parser = new DOMParser(), - html, - result; - - if (!query.length || !input.length) { - return ""; - } - - try { - html = parser.parseFromString(input, "text/html"); - } catch (err) { - return "Invalid input HTML."; - } - - try { - result = html.querySelectorAll(query); - } catch (err) { - return "Invalid CSS Selector. Details:\n" + err.message; - } - - const nodeToString = function(node) { - switch (node.nodeType) { - case Node.ELEMENT_NODE: return node.outerHTML; - case Node.ATTRIBUTE_NODE: return node.value; - case Node.COMMENT_NODE: return node.data; - case Node.TEXT_NODE: return node.wholeText; - case Node.DOCUMENT_NODE: return node.outerHTML; - default: throw new Error("Unknown Node Type: " + node.nodeType); - } - }; - - return Array.apply(null, Array(result.length)) - .map(function(_, i) { - return result[i]; - }) - .map(nodeToString) - .join(delimiter); - }, - - /** - * This tries to rename variable names in a code snippet according to a function. - * - * @param {string} input - * @param {function} replacer - this function will be fed the token which should be renamed. - * @returns {string} - */ - _replaceVariableNames(input, replacer) { - const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig; - - return input.replace(tokenRegex, (...args) => { - let match = args[0], - quotes = args[1]; - - if (!quotes) { - return match; - } else { - return replacer(match); - } - }); - }, - - - /** - * Converts to snake_case. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - * - */ - runToSnakeCase(input, args) { - const smart = args[0]; - - if (smart) { - return Code._replaceVariableNames(input, snakeCase); - } else { - return snakeCase(input); - } - }, - - - /** - * Converts to camelCase. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - * - */ - runToCamelCase(input, args) { - const smart = args[0]; - - if (smart) { - return Code._replaceVariableNames(input, camelCase); - } else { - return camelCase(input); - } - }, - - - /** - * Converts to kebab-case. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - * - */ - runToKebabCase(input, args) { - const smart = args[0]; - - if (smart) { - return Code._replaceVariableNames(input, kebabCase); - } else { - return kebabCase(input); - } - }, -}; - -export default Code; diff --git a/src/core/operations/Comment.mjs b/src/core/operations/Comment.mjs new file mode 100644 index 00000000..2c941089 --- /dev/null +++ b/src/core/operations/Comment.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Comment operation + */ +class Comment extends Operation { + + /** + * Comment constructor + */ + constructor() { + super(); + + this.name = "Comment"; + this.flowControl = true; + this.module = "Default"; + this.description = "Provides a place to write comments within the flow of the recipe. This operation has no computational effect."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + run(state) { + return state; + } + +} + +export default Comment; diff --git a/src/core/operations/CompareCTPHHashes.mjs b/src/core/operations/CompareCTPHHashes.mjs new file mode 100644 index 00000000..b8cd4c55 --- /dev/null +++ b/src/core/operations/CompareCTPHHashes.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {HASH_DELIM_OPTIONS} from "../lib/Delim"; +import ctphjs from "ctph.js"; +import OperationError from "../errors/OperationError"; + +/** + * Compare CTPH hashes operation + */ +class CompareCTPHHashes extends Operation { + + /** + * CompareCTPHHashes constructor + */ + constructor() { + super(); + + this.name = "Compare CTPH hashes"; + this.module = "Crypto"; + this.description = "Compares two Context Triggered Piecewise Hashing (CTPH) fuzzy hashes to determine the similarity between them on a scale of 0 to 100."; + this.infoURL = "https://forensicswiki.org/wiki/Context_Triggered_Piecewise_Hashing"; + this.inputType = "string"; + this.outputType = "Number"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": HASH_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {Number} + */ + run(input, args) { + const samples = input.split(Utils.charRep(args[0])); + if (samples.length !== 2) throw new OperationError("Incorrect number of samples."); + return ctphjs.similarity(samples[0], samples[1]); + } + +} + +export default CompareCTPHHashes; diff --git a/src/core/operations/CompareSSDEEPHashes.mjs b/src/core/operations/CompareSSDEEPHashes.mjs new file mode 100644 index 00000000..46aa8dac --- /dev/null +++ b/src/core/operations/CompareSSDEEPHashes.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {HASH_DELIM_OPTIONS} from "../lib/Delim"; +import ssdeepjs from "ssdeep.js"; +import OperationError from "../errors/OperationError"; + +/** + * Compare SSDEEP hashes operation + */ +class CompareSSDEEPHashes extends Operation { + + /** + * CompareSSDEEPHashes constructor + */ + constructor() { + super(); + + this.name = "Compare SSDEEP hashes"; + this.module = "Crypto"; + this.description = "Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100."; + this.infoURL = "https://forensicswiki.org/wiki/Ssdeep"; + this.inputType = "string"; + this.outputType = "Number"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": HASH_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {Number} + */ + run(input, args) { + const samples = input.split(Utils.charRep(args[0])); + if (samples.length !== 2) throw new OperationError("Incorrect number of samples."); + return ssdeepjs.similarity(samples[0], samples[1]); + } + +} + +export default CompareSSDEEPHashes; diff --git a/src/core/operations/Compress.js b/src/core/operations/Compress.js deleted file mode 100755 index 020d40cf..00000000 --- a/src/core/operations/Compress.js +++ /dev/null @@ -1,578 +0,0 @@ -import Utils from "../Utils.js"; -import rawdeflate from "zlibjs/bin/rawdeflate.min"; -import rawinflate from "zlibjs/bin/rawinflate.min"; -import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; -import zip from "zlibjs/bin/zip.min"; -import unzip from "zlibjs/bin/unzip.min"; -import bzip2 from "exports-loader?bzip2!../lib/bzip2.js"; - -const Zlib = { - RawDeflate: rawdeflate.Zlib.RawDeflate, - RawInflate: rawinflate.Zlib.RawInflate, - Deflate: zlibAndGzip.Zlib.Deflate, - Inflate: zlibAndGzip.Zlib.Inflate, - Gzip: zlibAndGzip.Zlib.Gzip, - Gunzip: zlibAndGzip.Zlib.Gunzip, - Zip: zip.Zlib.Zip, - Unzip: unzip.Zlib.Unzip, -}; - - -/** - * Compression operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Compress = { - - /** - * @constant - * @default - */ - COMPRESSION_TYPE: ["Dynamic Huffman Coding", "Fixed Huffman Coding", "None (Store)"], - /** - * @constant - * @default - */ - INFLATE_BUFFER_TYPE: ["Adaptive", "Block"], - /** - * @constant - * @default - */ - COMPRESSION_METHOD: ["Deflate", "None (Store)"], - /** - * @constant - * @default - */ - OS: ["MSDOS", "Unix", "Macintosh"], - /** - * @constant - * @default - */ - RAW_COMPRESSION_TYPE_LOOKUP: { - "Fixed Huffman Coding" : Zlib.RawDeflate.CompressionType.FIXED, - "Dynamic Huffman Coding" : Zlib.RawDeflate.CompressionType.DYNAMIC, - "None (Store)" : Zlib.RawDeflate.CompressionType.NONE, - }, - - /** - * Raw Deflate operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRawDeflate: function(input, args) { - const deflate = new Zlib.RawDeflate(input, { - compressionType: Compress.RAW_COMPRESSION_TYPE_LOOKUP[args[0]] - }); - return Array.prototype.slice.call(deflate.compress()); - }, - - - /** - * @constant - * @default - */ - INFLATE_INDEX: 0, - /** - * @constant - * @default - */ - INFLATE_BUFFER_SIZE: 0, - /** - * @constant - * @default - */ - INFLATE_RESIZE: false, - /** - * @constant - * @default - */ - INFLATE_VERIFY: false, - /** - * @constant - * @default - */ - RAW_BUFFER_TYPE_LOOKUP: { - "Adaptive" : Zlib.RawInflate.BufferType.ADAPTIVE, - "Block" : Zlib.RawInflate.BufferType.BLOCK, - }, - - /** - * Raw Inflate operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRawInflate: function(input, args) { - // Deal with character encoding issues - input = Utils.strToByteArray(Utils.byteArrayToUtf8(input)); - let inflate = new Zlib.RawInflate(input, { - index: args[0], - bufferSize: args[1], - bufferType: Compress.RAW_BUFFER_TYPE_LOOKUP[args[2]], - resize: args[3], - verify: args[4] - }), - result = Array.prototype.slice.call(inflate.decompress()); - - // Raw Inflate somethimes messes up and returns nonsense like this: - // ]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]... - // e.g. Input data of [8b, 1d, dc, 44] - // Look for the first two square brackets: - if (result.length > 158 && result[0] === 93 && result[5] === 93) { - // If the first two square brackets are there, check that the others - // are also there. If they are, throw an error. If not, continue. - let valid = false; - for (let i = 0; i < 155; i += 5) { - if (result[i] !== 93) { - valid = true; - } - } - - if (!valid) { - throw "Error: Unable to inflate data"; - } - } - // Trust me, this is the easiest way... - return result; - }, - - - /** - * @constant - * @default - */ - ZLIB_COMPRESSION_TYPE_LOOKUP: { - "Fixed Huffman Coding" : Zlib.Deflate.CompressionType.FIXED, - "Dynamic Huffman Coding" : Zlib.Deflate.CompressionType.DYNAMIC, - "None (Store)" : Zlib.Deflate.CompressionType.NONE, - }, - - /** - * Zlib Deflate operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runZlibDeflate: function(input, args) { - const deflate = new Zlib.Deflate(input, { - compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] - }); - return Array.prototype.slice.call(deflate.compress()); - }, - - - /** - * @constant - * @default - */ - ZLIB_BUFFER_TYPE_LOOKUP: { - "Adaptive" : Zlib.Inflate.BufferType.ADAPTIVE, - "Block" : Zlib.Inflate.BufferType.BLOCK, - }, - - /** - * Zlib Inflate operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runZlibInflate: function(input, args) { - // Deal with character encoding issues - input = Utils.strToByteArray(Utils.byteArrayToUtf8(input)); - const inflate = new Zlib.Inflate(input, { - index: args[0], - bufferSize: args[1], - bufferType: Compress.ZLIB_BUFFER_TYPE_LOOKUP[args[2]], - resize: args[3], - verify: args[4] - }); - return Array.prototype.slice.call(inflate.decompress()); - }, - - - /** - * @constant - * @default - */ - GZIP_CHECKSUM: false, - - /** - * Gzip operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runGzip: function(input, args) { - let filename = args[1], - comment = args[2], - options = { - deflateOptions: { - compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] - }, - flags: { - fhcrc: args[3] - } - }; - - if (filename.length) { - options.flags.fname = true; - options.filename = filename; - } - if (comment.length) { - options.flags.fcommenct = true; - options.comment = comment; - } - - const gzip = new Zlib.Gzip(input, options); - return Array.prototype.slice.call(gzip.compress()); - }, - - - /** - * Gunzip operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runGunzip: function(input, args) { - // Deal with character encoding issues - input = Utils.strToByteArray(Utils.byteArrayToUtf8(input)); - const gunzip = new Zlib.Gunzip(input); - return Array.prototype.slice.call(gunzip.decompress()); - }, - - - /** - * @constant - * @default - */ - PKZIP_FILENAME: "file.txt", - /** - * @constant - * @default - */ - ZIP_COMPRESSION_METHOD_LOOKUP: { - "Deflate" : Zlib.Zip.CompressionMethod.DEFLATE, - "None (Store)" : Zlib.Zip.CompressionMethod.STORE - }, - /** - * @constant - * @default - */ - ZIP_OS_LOOKUP: { - "MSDOS" : Zlib.Zip.OperatingSystem.MSDOS, - "Unix" : Zlib.Zip.OperatingSystem.UNIX, - "Macintosh" : Zlib.Zip.OperatingSystem.MACINTOSH - }, - - /** - * Zip operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runPkzip: function(input, args) { - let password = Utils.strToByteArray(args[2]), - options = { - filename: Utils.strToByteArray(args[0]), - comment: Utils.strToByteArray(args[1]), - compressionMethod: Compress.ZIP_COMPRESSION_METHOD_LOOKUP[args[3]], - os: Compress.ZIP_OS_LOOKUP[args[4]], - deflateOption: { - compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[5]] - }, - }, - zip = new Zlib.Zip(); - - if (password.length) - zip.setPassword(password); - zip.addFile(input, options); - return Array.prototype.slice.call(zip.compress()); - }, - - - /** - * @constant - * @default - */ - PKUNZIP_VERIFY: false, - - /** - * Unzip operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runPkunzip: function(input, args) { - let options = { - password: Utils.strToByteArray(args[0]), - verify: args[1] - }, - unzip = new Zlib.Unzip(input, options), - filenames = unzip.getFilenames(), - files = []; - - filenames.forEach(function(fileName) { - const bytes = unzip.decompress(fileName); - const contents = Utils.byteArrayToUtf8(bytes); - - const file = { - fileName: fileName, - size: contents.length, - }; - - const isDir = contents.length === 0 && fileName.endsWith("/"); - if (!isDir) { - file.bytes = bytes; - file.contents = contents; - } - - files.push(file); - }); - - return Utils.displayFilesAsHTML(files); - }, - - - /** - * Bzip2 Decompress operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runBzip2Decompress: function(input, args) { - let compressed = new Uint8Array(input), - bzip2Reader, - plain = ""; - - bzip2Reader = bzip2.array(compressed); - plain = bzip2.simple(bzip2Reader); - return plain; - }, - - - /** - * @constant - * @default - */ - TAR_FILENAME: "file.txt", - - - /** - * Tar pack operation. - * - * @author tlwr [toby@toby.codes] - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runTar: function(input, args) { - const Tarball = function() { - this.bytes = new Array(512); - this.position = 0; - }; - - Tarball.prototype.addEmptyBlock = function() { - const filler = new Array(512); - filler.fill(0); - this.bytes = this.bytes.concat(filler); - }; - - Tarball.prototype.writeBytes = function(bytes) { - const self = this; - - if (this.position + bytes.length > this.bytes.length) { - this.addEmptyBlock(); - } - - Array.prototype.forEach.call(bytes, function(b, i) { - if (typeof b.charCodeAt !== "undefined") { - b = b.charCodeAt(); - } - - self.bytes[self.position] = b; - self.position += 1; - }); - }; - - Tarball.prototype.writeEndBlocks = function() { - const numEmptyBlocks = 2; - for (let i = 0; i < numEmptyBlocks; i++) { - this.addEmptyBlock(); - } - }; - - const fileSize = Utils.padLeft(input.length.toString(8), 11, "0"); - const currentUnixTimestamp = Math.floor(Date.now() / 1000); - const lastModTime = Utils.padLeft(currentUnixTimestamp.toString(8), 11, "0"); - - const file = { - fileName: Utils.padBytesRight(args[0], 100), - fileMode: Utils.padBytesRight("0000664", 8), - ownerUID: Utils.padBytesRight("0", 8), - ownerGID: Utils.padBytesRight("0", 8), - size: Utils.padBytesRight(fileSize, 12), - lastModTime: Utils.padBytesRight(lastModTime, 12), - checksum: " ", - type: "0", - linkedFileName: Utils.padBytesRight("", 100), - USTARFormat: Utils.padBytesRight("ustar", 6), - version: "00", - ownerUserName: Utils.padBytesRight("", 32), - ownerGroupName: Utils.padBytesRight("", 32), - deviceMajor: Utils.padBytesRight("", 8), - deviceMinor: Utils.padBytesRight("", 8), - fileNamePrefix: Utils.padBytesRight("", 155), - }; - - let checksum = 0; - for (const key in file) { - const bytes = file[key]; - Array.prototype.forEach.call(bytes, function(b) { - if (typeof b.charCodeAt !== "undefined") { - checksum += b.charCodeAt(); - } else { - checksum += b; - } - }); - } - checksum = Utils.padBytesRight(Utils.padLeft(checksum.toString(8), 7, "0"), 8); - file.checksum = checksum; - - const tarball = new Tarball(); - tarball.writeBytes(file.fileName); - tarball.writeBytes(file.fileMode); - tarball.writeBytes(file.ownerUID); - tarball.writeBytes(file.ownerGID); - tarball.writeBytes(file.size); - tarball.writeBytes(file.lastModTime); - tarball.writeBytes(file.checksum); - tarball.writeBytes(file.type); - tarball.writeBytes(file.linkedFileName); - tarball.writeBytes(file.USTARFormat); - tarball.writeBytes(file.version); - tarball.writeBytes(file.ownerUserName); - tarball.writeBytes(file.ownerGroupName); - tarball.writeBytes(file.deviceMajor); - tarball.writeBytes(file.deviceMinor); - tarball.writeBytes(file.fileNamePrefix); - tarball.writeBytes(Utils.padBytesRight("", 12)); - tarball.writeBytes(input); - tarball.writeEndBlocks(); - - return tarball.bytes; - }, - - - /** - * Untar unpack operation. - * - * @author tlwr [toby@toby.codes] - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {html} - */ - runUntar: function(input, args) { - const Stream = function(input) { - this.bytes = input; - this.position = 0; - }; - - Stream.prototype.getBytes = function(bytesToGet) { - const newPosition = this.position + bytesToGet; - const bytes = this.bytes.slice(this.position, newPosition); - this.position = newPosition; - return bytes; - }; - - Stream.prototype.readString = function(numBytes) { - let result = ""; - for (let i = this.position; i < this.position + numBytes; i++) { - const currentByte = this.bytes[i]; - if (currentByte === 0) break; - result += String.fromCharCode(currentByte); - } - this.position += numBytes; - return result; - }; - - Stream.prototype.readInt = function(numBytes, base) { - const string = this.readString(numBytes); - return parseInt(string, base); - }; - - Stream.prototype.hasMore = function() { - return this.position < this.bytes.length; - }; - - let stream = new Stream(input), - files = []; - - while (stream.hasMore()) { - const dataPosition = stream.position + 512; - - const file = { - fileName: stream.readString(100), - fileMode: stream.readString(8), - ownerUID: stream.readString(8), - ownerGID: stream.readString(8), - size: parseInt(stream.readString(12), 8), // Octal - lastModTime: new Date(1000 * stream.readInt(12, 8)), // Octal - checksum: stream.readString(8), - type: stream.readString(1), - linkedFileName: stream.readString(100), - USTARFormat: stream.readString(6).indexOf("ustar") >= 0, - }; - - if (file.USTARFormat) { - file.version = stream.readString(2); - file.ownerUserName = stream.readString(32); - file.ownerGroupName = stream.readString(32); - file.deviceMajor = stream.readString(8); - file.deviceMinor = stream.readString(8); - file.filenamePrefix = stream.readString(155); - } - - stream.position = dataPosition; - - if (file.type === "0") { - // File - files.push(file); - let endPosition = stream.position + file.size; - if (file.size % 512 !== 0) { - endPosition += 512 - (file.size % 512); - } - - file.bytes = stream.getBytes(file.size); - file.contents = Utils.byteArrayToUtf8(file.bytes); - stream.position = endPosition; - } else if (file.type === "5") { - // Directory - files.push(file); - } else { - // Symlink or empty bytes - } - } - - return Utils.displayFilesAsHTML(files); - }, -}; - -export default Compress; diff --git a/src/core/operations/ConditionalJump.mjs b/src/core/operations/ConditionalJump.mjs new file mode 100644 index 00000000..d102ea72 --- /dev/null +++ b/src/core/operations/ConditionalJump.mjs @@ -0,0 +1,84 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Dish from "../Dish"; +import { getLabelIndex } from "../lib/FlowControl"; + +/** + * Conditional Jump operation + */ +class ConditionalJump extends Operation { + + /** + * ConditionalJump constructor + */ + constructor() { + super(); + + this.name = "Conditional Jump"; + this.flowControl = true; + this.module = "Default"; + this.description = "Conditionally jump forwards or backwards to the specified Label based on whether the data matches the specified regular expression."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Match (regex)", + "type": "string", + "value": "" + }, + { + "name": "Invert match", + "type": "boolean", + "value": false + }, + { + "name": "Label name", + "type": "shortString", + "value": "" + }, + { + "name": "Maximum jumps (if jumping backwards)", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @param {number} state.numJumps - The number of jumps taken so far. + * @returns {Object} The updated state of the recipe. + */ + async run(state) { + const ings = state.opList[state.progress].ingValues, + dish = state.dish, + [regexStr, invert, label, maxJumps] = ings, + jmpIndex = getLabelIndex(label, state); + + if (state.numJumps >= maxJumps || jmpIndex === -1) { + return state; + } + + if (regexStr !== "") { + const str = await dish.get(Dish.STRING); + const strMatch = str.search(regexStr) > -1; + if (!invert && strMatch || invert && !strMatch) { + state.progress = jmpIndex; + state.numJumps++; + } + } + + return state; + } + +} + +export default ConditionalJump; diff --git a/src/core/operations/Convert.js b/src/core/operations/Convert.js deleted file mode 100755 index cb2d860f..00000000 --- a/src/core/operations/Convert.js +++ /dev/null @@ -1,414 +0,0 @@ -/** - * Unit conversion operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Convert = { - - /** - * @constant - * @default - */ - DISTANCE_UNITS: [ - "[Metric]", "Nanometres (nm)", "Micrometres (µm)", "Millimetres (mm)", "Centimetres (cm)", "Metres (m)", "Kilometers (km)", "[/Metric]", - "[Imperial]", "Thou (th)", "Inches (in)", "Feet (ft)", "Yards (yd)", "Chains (ch)", "Furlongs (fur)", "Miles (mi)", "Leagues (lea)", "[/Imperial]", - "[Maritime]", "Fathoms (ftm)", "Cables", "Nautical miles", "[/Maritime]", - "[Comparisons]", "Cars (4m)", "Buses (8.4m)", "American football fields (91m)", "Football pitches (105m)", "[/Comparisons]", - "[Astronomical]", "Earth-to-Moons", "Earth's equators", "Astronomical units (au)", "Light-years (ly)", "Parsecs (pc)", "[/Astronomical]", - ], - /** - * @constant - * @default - */ - DISTANCE_FACTOR: { // Multiples of a metre - "Nanometres (nm)" : 1e-9, - "Micrometres (µm)" : 1e-6, - "Millimetres (mm)" : 1e-3, - "Centimetres (cm)" : 1e-2, - "Metres (m)" : 1, - "Kilometers (km)" : 1e3, - - "Thou (th)" : 0.0000254, - "Inches (in)" : 0.0254, - "Feet (ft)" : 0.3048, - "Yards (yd)" : 0.9144, - "Chains (ch)" : 20.1168, - "Furlongs (fur)" : 201.168, - "Miles (mi)" : 1609.344, - "Leagues (lea)" : 4828.032, - - "Fathoms (ftm)" : 1.853184, - "Cables" : 185.3184, - "Nautical miles" : 1853.184, - - "Cars (4m)" : 4, - "Buses (8.4m)" : 8.4, - "American football fields (91m)": 91, - "Football pitches (105m)": 105, - - "Earth-to-Moons" : 380000000, - "Earth's equators" : 40075016.686, - "Astronomical units (au)": 149597870700, - "Light-years (ly)" : 9460730472580800, - "Parsecs (pc)" : 3.0856776e16 - }, - - /** - * Convert distance operation. - * - * @param {number} input - * @param {Object[]} args - * @returns {number} - */ - runDistance: function (input, args) { - let inputUnits = args[0], - outputUnits = args[1]; - - input = input * Convert.DISTANCE_FACTOR[inputUnits]; - return input / Convert.DISTANCE_FACTOR[outputUnits]; - // TODO Remove rounding errors (e.g. 1.000000000001) - }, - - - /** - * @constant - * @default - */ - DATA_UNITS: [ - "Bits (b)", "Nibbles", "Octets", "Bytes (B)", - "[Binary bits (2^n)]", "Kibibits (Kib)", "Mebibits (Mib)", "Gibibits (Gib)", "Tebibits (Tib)", "Pebibits (Pib)", "Exbibits (Eib)", "Zebibits (Zib)", "Yobibits (Yib)", "[/Binary bits (2^n)]", - "[Decimal bits (10^n)]", "Decabits", "Hectobits", "Kilobits (kb)", "Megabits (Mb)", "Gigabits (Gb)", "Terabits (Tb)", "Petabits (Pb)", "Exabits (Eb)", "Zettabits (Zb)", "Yottabits (Yb)", "[/Decimal bits (10^n)]", - "[Binary bytes (8 x 2^n)]", "Kibibytes (KiB)", "Mebibytes (MiB)", "Gibibytes (GiB)", "Tebibytes (TiB)", "Pebibytes (PiB)", "Exbibytes (EiB)", "Zebibytes (ZiB)", "Yobibytes (YiB)", "[/Binary bytes (8 x 2^n)]", - "[Decimal bytes (8 x 10^n)]", "Kilobytes (KB)", "Megabytes (MB)", "Gigabytes (GB)", "Terabytes (TB)", "Petabytes (PB)", "Exabytes (EB)", "Zettabytes (ZB)", "Yottabytes (YB)", "[/Decimal bytes (8 x 10^n)]" - ], - /** - * @constant - * @default - */ - DATA_FACTOR: { // Multiples of a bit - "Bits (b)" : 1, - "Nibbles" : 4, - "Octets" : 8, - "Bytes (B)" : 8, - - // Binary bits (2^n) - "Kibibits (Kib)" : 1024, - "Mebibits (Mib)" : 1048576, - "Gibibits (Gib)" : 1073741824, - "Tebibits (Tib)" : 1099511627776, - "Pebibits (Pib)" : 1125899906842624, - "Exbibits (Eib)" : 1152921504606846976, - "Zebibits (Zib)" : 1180591620717411303424, - "Yobibits (Yib)" : 1208925819614629174706176, - - // Decimal bits (10^n) - "Decabits" : 10, - "Hectobits" : 100, - "Kilobits (Kb)" : 1e3, - "Megabits (Mb)" : 1e6, - "Gigabits (Gb)" : 1e9, - "Terabits (Tb)" : 1e12, - "Petabits (Pb)" : 1e15, - "Exabits (Eb)" : 1e18, - "Zettabits (Zb)" : 1e21, - "Yottabits (Yb)" : 1e24, - - // Binary bytes (8 x 2^n) - "Kibibytes (KiB)" : 8192, - "Mebibytes (MiB)" : 8388608, - "Gibibytes (GiB)" : 8589934592, - "Tebibytes (TiB)" : 8796093022208, - "Pebibytes (PiB)" : 9007199254740992, - "Exbibytes (EiB)" : 9223372036854775808, - "Zebibytes (ZiB)" : 9444732965739290427392, - "Yobibytes (YiB)" : 9671406556917033397649408, - - // Decimal bytes (8 x 10^n) - "Kilobytes (KB)" : 8e3, - "Megabytes (MB)" : 8e6, - "Gigabytes (GB)" : 8e9, - "Terabytes (TB)" : 8e12, - "Petabytes (PB)" : 8e15, - "Exabytes (EB)" : 8e18, - "Zettabytes (ZB)" : 8e21, - "Yottabytes (YB)" : 8e24, - }, - - /** - * Convert data units operation. - * - * @param {number} input - * @param {Object[]} args - * @returns {number} - */ - runDataSize: function (input, args) { - let inputUnits = args[0], - outputUnits = args[1]; - - input = input * Convert.DATA_FACTOR[inputUnits]; - return input / Convert.DATA_FACTOR[outputUnits]; - }, - - - /** - * @constant - * @default - */ - AREA_UNITS: [ - "[Metric]", "Square metre (sq m)", "Square kilometre (sq km)", "Centiare (ca)", "Deciare (da)", "Are (a)", "Decare (daa)", "Hectare (ha)", "[/Metric]", - "[Imperial]", "Square inch (sq in)", "Square foot (sq ft)", "Square yard (sq yd)", "Square mile (sq mi)", "Perch (sq per)", "Rood (ro)", "International acre (ac)", "[/Imperial]", - "[US customary units]", "US survey acre (ac)", "US survey square mile (sq mi)", "US survey township", "[/US customary units]", - "[Nuclear physics]", "Yoctobarn (yb)", "Zeptobarn (zb)", "Attobarn (ab)", "Femtobarn (fb)", "Picobarn (pb)", "Nanobarn (nb)", "Microbarn (μb)", "Millibarn (mb)", "Barn (b)", "Kilobarn (kb)", "Megabarn (Mb)", "Outhouse", "Shed", "Planck area", "[/Nuclear physics]", - "[Comparisons]", "Washington D.C.", "Isle of Wight", "Wales", "Texas", "[/Comparisons]", - ], - /** - * @constant - * @default - */ - AREA_FACTOR: { // Multiples of a square metre - // Metric - "Square metre (sq m)" : 1, - "Square kilometre (sq km)" : 1e6, - - "Centiare (ca)" : 1, - "Deciare (da)" : 10, - "Are (a)" : 100, - "Decare (daa)" : 1e3, - "Hectare (ha)" : 1e4, - - // Imperial - "Square inch (sq in)" : 0.00064516, - "Square foot (sq ft)" : 0.09290304, - "Square yard (sq yd)" : 0.83612736, - "Square mile (sq mi)" : 2589988.110336, - "Perch (sq per)" : 42.21, - "Rood (ro)" : 1011, - "International acre (ac)" : 4046.8564224, - - // US customary units - "US survey acre (ac)" : 4046.87261, - "US survey square mile (sq mi)" : 2589998.470305239, - "US survey township" : 93239944.9309886, - - // Nuclear physics - "Yoctobarn (yb)" : 1e-52, - "Zeptobarn (zb)" : 1e-49, - "Attobarn (ab)" : 1e-46, - "Femtobarn (fb)" : 1e-43, - "Picobarn (pb)" : 1e-40, - "Nanobarn (nb)" : 1e-37, - "Microbarn (μb)" : 1e-34, - "Millibarn (mb)" : 1e-31, - "Barn (b)" : 1e-28, - "Kilobarn (kb)" : 1e-25, - "Megabarn (Mb)" : 1e-22, - - "Planck area" : 2.6e-70, - "Shed" : 1e-52, - "Outhouse" : 1e-34, - - // Comparisons - "Washington D.C." : 176119191.502848, - "Isle of Wight" : 380000000, - "Wales" : 20779000000, - "Texas" : 696241000000, - }, - - /** - * Convert area operation. - * - * @param {number} input - * @param {Object[]} args - * @returns {number} - */ - runArea: function (input, args) { - let inputUnits = args[0], - outputUnits = args[1]; - - input = input * Convert.AREA_FACTOR[inputUnits]; - return input / Convert.AREA_FACTOR[outputUnits]; - }, - - - /** - * @constant - * @default - */ - MASS_UNITS: [ - "[Metric]", "Yoctogram (yg)", "Zeptogram (zg)", "Attogram (ag)", "Femtogram (fg)", "Picogram (pg)", "Nanogram (ng)", "Microgram (μg)", "Milligram (mg)", "Centigram (cg)", "Decigram (dg)", "Gram (g)", "Decagram (dag)", "Hectogram (hg)", "Kilogram (kg)", "Megagram (Mg)", "Tonne (t)", "Gigagram (Gg)", "Teragram (Tg)", "Petagram (Pg)", "Exagram (Eg)", "Zettagram (Zg)", "Yottagram (Yg)", "[/Metric]", - "[Imperial Avoirdupois]", "Grain (gr)", "Dram (dr)", "Ounce (oz)", "Pound (lb)", "Nail", "Stone (st)", "Quarter (gr)", "Tod", "US hundredweight (cwt)", "Imperial hundredweight (cwt)", "US ton (t)", "Imperial ton (t)", "[/Imperial Avoirdupois]", - "[Imperial Troy]", "Grain (gr)", "Pennyweight (dwt)", "Troy dram (dr t)", "Troy ounce (oz t)", "Troy pound (lb t)", "Mark", "[/Imperial Troy]", - "[Archaic]", "Wey", "Wool wey", "Suffolk wey", "Wool sack", "Coal sack", "Load", "Last", "Flax or feather last", "Gunpowder last", "Picul", "Rice last", "[/Archaic]", - "[Comparisons]", "Big Ben (14 tonnes)", "Blue whale (180 tonnes)", "International Space Station (417 tonnes)", "Space Shuttle (2,041 tonnes)", "RMS Titanic (52,000 tonnes)", "Great Pyramid of Giza (6,000,000 tonnes)", "Earth's oceans (1.4 yottagrams)", "[/Comparisons]", - "[Astronomical]", "A teaspoon of neutron star (5,500 million tonnes)", "Lunar mass (ML)", "Earth mass (M⊕)", "Jupiter mass (MJ)", "Solar mass (M☉)", "Sagittarius A* (7.5 x 10^36 kgs-ish)", "Milky Way galaxy (1.2 x 10^42 kgs)", "The observable universe (1.45 x 10^53 kgs)", "[/Astronomical]", - ], - /** - * @constant - * @default - */ - MASS_FACTOR: { // Multiples of a gram - // Metric - "Yoctogram (yg)" : 1e-24, - "Zeptogram (zg)" : 1e-21, - "Attogram (ag)" : 1e-18, - "Femtogram (fg)" : 1e-15, - "Picogram (pg)" : 1e-12, - "Nanogram (ng)" : 1e-9, - "Microgram (μg)" : 1e-6, - "Milligram (mg)" : 1e-3, - "Centigram (cg)" : 1e-2, - "Decigram (dg)" : 1e-1, - "Gram (g)" : 1, - "Decagram (dag)" : 10, - "Hectogram (hg)" : 100, - "Kilogram (kg)" : 1000, - "Megagram (Mg)" : 1e6, - "Tonne (t)" : 1e6, - "Gigagram (Gg)" : 1e9, - "Teragram (Tg)" : 1e12, - "Petagram (Pg)" : 1e15, - "Exagram (Eg)" : 1e18, - "Zettagram (Zg)" : 1e21, - "Yottagram (Yg)" : 1e24, - - // Imperial Avoirdupois - "Grain (gr)" : 64.79891e-3, - "Dram (dr)" : 1.7718451953125, - "Ounce (oz)" : 28.349523125, - "Pound (lb)" : 453.59237, - "Nail" : 3175.14659, - "Stone (st)" : 6.35029318e3, - "Quarter (gr)" : 12700.58636, - "Tod" : 12700.58636, - "US hundredweight (cwt)" : 45.359237e3, - "Imperial hundredweight (cwt)" : 50.80234544e3, - "US ton (t)" : 907.18474e3, - "Imperial ton (t)" : 1016.0469088e3, - - // Imperial Troy - "Pennyweight (dwt)" : 1.55517384, - "Troy dram (dr t)" : 3.8879346, - "Troy ounce (oz t)" : 31.1034768, - "Troy pound (lb t)" : 373.2417216, - "Mark" : 248.8278144, - - // Archaic - "Wey" : 76.5e3, - "Wool wey" : 101.7e3, - "Suffolk wey" : 161.5e3, - "Wool sack" : 153000, - "Coal sack" : 50.80234544e3, - "Load" : 918000, - "Last" : 1836000, - "Flax or feather last" : 770e3, - "Gunpowder last" : 1090e3, - "Picul" : 60.478982e3, - "Rice last" : 1200e3, - - // Comparisons - "Big Ben (14 tonnes)" : 14e6, - "Blue whale (180 tonnes)" : 180e6, - "International Space Station (417 tonnes)" : 417e6, - "Space Shuttle (2,041 tonnes)" : 2041e6, - "RMS Titanic (52,000 tonnes)" : 52000e6, - "Great Pyramid of Giza (6,000,000 tonnes)" : 6e12, - "Earth's oceans (1.4 yottagrams)" : 1.4e24, - - // Astronomical - "A teaspoon of neutron star (5,500 million tonnes)" : 5.5e15, - "Lunar mass (ML)" : 7.342e25, - "Earth mass (M⊕)" : 5.97219e27, - "Jupiter mass (MJ)" : 1.8981411476999997e30, - "Solar mass (M☉)" : 1.98855e33, - "Sagittarius A* (7.5 x 10^36 kgs-ish)" : 7.5e39, - "Milky Way galaxy (1.2 x 10^42 kgs)" : 1.2e45, - "The observable universe (1.45 x 10^53 kgs)" : 1.45e56, - }, - - /** - * Convert mass operation. - * - * @param {number} input - * @param {Object[]} args - * @returns {number} - */ - runMass: function (input, args) { - let inputUnits = args[0], - outputUnits = args[1]; - - input = input * Convert.MASS_FACTOR[inputUnits]; - return input / Convert.MASS_FACTOR[outputUnits]; - }, - - - /** - * @constant - * @default - */ - SPEED_UNITS: [ - "[Metric]", "Metres per second (m/s)", "Kilometres per hour (km/h)", "[/Metric]", - "[Imperial]", "Miles per hour (mph)", "Knots (kn)", "[/Imperial]", - "[Comparisons]", "Human hair growth rate", "Bamboo growth rate", "World's fastest snail", "Usain Bolt's top speed", "Jet airliner cruising speed", "Concorde", "SR-71 Blackbird", "Space Shuttle", "International Space Station", "[/Comparisons]", - "[Scientific]", "Sound in standard atmosphere", "Sound in water", "Lunar escape velocity", "Earth escape velocity", "Earth's solar orbit", "Solar system's Milky Way orbit", "Milky Way relative to the cosmic microwave background", "Solar escape velocity", "Neutron star escape velocity (0.3c)", "Light in a diamond (0.4136c)", "Signal in an optical fibre (0.667c)", "Light (c)", "[/Scientific]", - ], - /** - * @constant - * @default - */ - SPEED_FACTOR: { // Multiples of m/s - // Metric - "Metres per second (m/s)" : 1, - "Kilometres per hour (km/h)" : 0.2778, - - // Imperial - "Miles per hour (mph)" : 0.44704, - "Knots (kn)" : 0.5144, - - // Comparisons - "Human hair growth rate" : 4.8e-9, - "Bamboo growth rate" : 1.4e-5, - "World's fastest snail" : 0.00275, - "Usain Bolt's top speed" : 12.42, - "Jet airliner cruising speed" : 250, - "Concorde" : 603, - "SR-71 Blackbird" : 981, - "Space Shuttle" : 1400, - "International Space Station" : 7700, - - // Scientific - "Sound in standard atmosphere" : 340.3, - "Sound in water" : 1500, - "Lunar escape velocity" : 2375, - "Earth escape velocity" : 11200, - "Earth's solar orbit" : 29800, - "Solar system's Milky Way orbit" : 200000, - "Milky Way relative to the cosmic microwave background" : 552000, - "Solar escape velocity" : 617700, - "Neutron star escape velocity (0.3c)" : 100000000, - "Light in a diamond (0.4136c)" : 124000000, - "Signal in an optical fibre (0.667c)" : 200000000, - "Light (c)" : 299792458, - }, - - /** - * Convert speed operation. - * - * @param {number} input - * @param {Object[]} args - * @returns {number} - */ - runSpeed: function (input, args) { - let inputUnits = args[0], - outputUnits = args[1]; - - input = input * Convert.SPEED_FACTOR[inputUnits]; - return input / Convert.SPEED_FACTOR[outputUnits]; - }, - -}; - -export default Convert; diff --git a/src/core/operations/ConvertArea.mjs b/src/core/operations/ConvertArea.mjs new file mode 100644 index 00000000..1c4a80c3 --- /dev/null +++ b/src/core/operations/ConvertArea.mjs @@ -0,0 +1,113 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Convert area operation + */ +class ConvertArea extends Operation { + + /** + * ConvertArea constructor + */ + constructor() { + super(); + + this.name = "Convert area"; + this.module = "Default"; + this.description = "Converts a unit of area to another format."; + this.infoURL = "https://wikipedia.org/wiki/Orders_of_magnitude_(area)"; + this.inputType = "BigNumber"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": AREA_UNITS + }, + { + "name": "Output units", + "type": "option", + "value": AREA_UNITS + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const [inputUnits, outputUnits] = args; + + input = input.times(AREA_FACTOR[inputUnits]); + return input.div(AREA_FACTOR[outputUnits]); + } + +} + + +const AREA_UNITS = [ + "[Metric]", "Square metre (sq m)", "Square kilometre (sq km)", "Centiare (ca)", "Deciare (da)", "Are (a)", "Decare (daa)", "Hectare (ha)", "[/Metric]", + "[Imperial]", "Square inch (sq in)", "Square foot (sq ft)", "Square yard (sq yd)", "Square mile (sq mi)", "Perch (sq per)", "Rood (ro)", "International acre (ac)", "[/Imperial]", + "[US customary units]", "US survey acre (ac)", "US survey square mile (sq mi)", "US survey township", "[/US customary units]", + "[Nuclear physics]", "Yoctobarn (yb)", "Zeptobarn (zb)", "Attobarn (ab)", "Femtobarn (fb)", "Picobarn (pb)", "Nanobarn (nb)", "Microbarn (μb)", "Millibarn (mb)", "Barn (b)", "Kilobarn (kb)", "Megabarn (Mb)", "Outhouse", "Shed", "Planck area", "[/Nuclear physics]", + "[Comparisons]", "Washington D.C.", "Isle of Wight", "Wales", "Texas", "[/Comparisons]", +]; + +const AREA_FACTOR = { // Multiples of a square metre + // Metric + "Square metre (sq m)": 1, + "Square kilometre (sq km)": 1e6, + + "Centiare (ca)": 1, + "Deciare (da)": 10, + "Are (a)": 100, + "Decare (daa)": 1e3, + "Hectare (ha)": 1e4, + + // Imperial + "Square inch (sq in)": 0.00064516, + "Square foot (sq ft)": 0.09290304, + "Square yard (sq yd)": 0.83612736, + "Square mile (sq mi)": 2589988.110336, + "Perch (sq per)": 42.21, + "Rood (ro)": 1011, + "International acre (ac)": 4046.8564224, + + // US customary units + "US survey acre (ac)": 4046.87261, + "US survey square mile (sq mi)": 2589998.470305239, + "US survey township": 93239944.9309886, + + // Nuclear physics + "Yoctobarn (yb)": 1e-52, + "Zeptobarn (zb)": 1e-49, + "Attobarn (ab)": 1e-46, + "Femtobarn (fb)": 1e-43, + "Picobarn (pb)": 1e-40, + "Nanobarn (nb)": 1e-37, + "Microbarn (μb)": 1e-34, + "Millibarn (mb)": 1e-31, + "Barn (b)": 1e-28, + "Kilobarn (kb)": 1e-25, + "Megabarn (Mb)": 1e-22, + + "Planck area": 2.6e-70, + "Shed": 1e-52, + "Outhouse": 1e-34, + + // Comparisons + "Washington D.C.": 176119191.502848, + "Isle of Wight": 380000000, + "Wales": 20779000000, + "Texas": 696241000000, +}; + + +export default ConvertArea; diff --git a/src/core/operations/ConvertCoordinateFormat.mjs b/src/core/operations/ConvertCoordinateFormat.mjs new file mode 100644 index 00000000..87e44bf2 --- /dev/null +++ b/src/core/operations/ConvertCoordinateFormat.mjs @@ -0,0 +1,95 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates"; + +/** + * Convert co-ordinate format operation + */ +class ConvertCoordinateFormat extends Operation { + + /** + * ConvertCoordinateFormat constructor + */ + constructor() { + super(); + + this.name = "Convert co-ordinate format"; + this.module = "Hashing"; + this.description = "Converts geographical coordinates between different formats.

Supported formats:
  • Degrees Minutes Seconds (DMS)
  • Degrees Decimal Minutes (DDM)
  • Decimal Degrees (DD)
  • Geohash
  • Military Grid Reference System (MGRS)
  • Ordnance Survey National Grid (OSNG)
  • Universal Transverse Mercator (UTM)

The operation can try to detect the input co-ordinate format and delimiter automatically, but this may not always work correctly."; + this.infoURL = "https://wikipedia.org/wiki/Geographic_coordinate_conversion"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "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" + ] + }, + { + "name": "Output Format", + "type": "option", + "value": FORMATS + }, + { + "name": "Output Delimiter", + "type": "option", + "value": [ + "Space", + "\\n", + "Comma", + "Semi-colon", + "Colon" + ] + }, + { + "name": "Include Compass Directions", + "type": "option", + "value": [ + "None", + "Before", + "After" + ] + }, + { + "name": "Precision", + "type": "number", + "value": 3 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.replace(/[\s+]/g, "") !== "") { + const [inFormat, inDelim, outFormat, outDelim, incDirection, precision] = args; + const result = convertCoordinates(input, inFormat, inDelim, outFormat, outDelim, incDirection, precision); + return result; + } else { + return input; + } + } +} + +export default ConvertCoordinateFormat; diff --git a/src/core/operations/ConvertDataUnits.mjs b/src/core/operations/ConvertDataUnits.mjs new file mode 100644 index 00000000..c35bd6fe --- /dev/null +++ b/src/core/operations/ConvertDataUnits.mjs @@ -0,0 +1,112 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Convert data units operation + */ +class ConvertDataUnits extends Operation { + + /** + * ConvertDataUnits constructor + */ + constructor() { + super(); + + this.name = "Convert data units"; + this.module = "Default"; + this.description = "Converts a unit of data to another format."; + this.infoURL = "https://wikipedia.org/wiki/Orders_of_magnitude_(data)"; + this.inputType = "BigNumber"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": DATA_UNITS + }, + { + "name": "Output units", + "type": "option", + "value": DATA_UNITS + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const [inputUnits, outputUnits] = args; + + input = input.times(DATA_FACTOR[inputUnits]); + return input.div(DATA_FACTOR[outputUnits]); + } + +} + +const DATA_UNITS = [ + "Bits (b)", "Nibbles", "Octets", "Bytes (B)", + "[Binary bits (2^n)]", "Kibibits (Kib)", "Mebibits (Mib)", "Gibibits (Gib)", "Tebibits (Tib)", "Pebibits (Pib)", "Exbibits (Eib)", "Zebibits (Zib)", "Yobibits (Yib)", "[/Binary bits (2^n)]", + "[Decimal bits (10^n)]", "Decabits", "Hectobits", "Kilobits (kb)", "Megabits (Mb)", "Gigabits (Gb)", "Terabits (Tb)", "Petabits (Pb)", "Exabits (Eb)", "Zettabits (Zb)", "Yottabits (Yb)", "[/Decimal bits (10^n)]", + "[Binary bytes (8 x 2^n)]", "Kibibytes (KiB)", "Mebibytes (MiB)", "Gibibytes (GiB)", "Tebibytes (TiB)", "Pebibytes (PiB)", "Exbibytes (EiB)", "Zebibytes (ZiB)", "Yobibytes (YiB)", "[/Binary bytes (8 x 2^n)]", + "[Decimal bytes (8 x 10^n)]", "Kilobytes (KB)", "Megabytes (MB)", "Gigabytes (GB)", "Terabytes (TB)", "Petabytes (PB)", "Exabytes (EB)", "Zettabytes (ZB)", "Yottabytes (YB)", "[/Decimal bytes (8 x 10^n)]" +]; + +const DATA_FACTOR = { // Multiples of a bit + "Bits (b)": 1, + "Nibbles": 4, + "Octets": 8, + "Bytes (B)": 8, + + // Binary bits (2^n) + "Kibibits (Kib)": 1024, + "Mebibits (Mib)": 1048576, + "Gibibits (Gib)": 1073741824, + "Tebibits (Tib)": 1099511627776, + "Pebibits (Pib)": 1125899906842624, + "Exbibits (Eib)": 1152921504606846976, + "Zebibits (Zib)": 1180591620717411303424, + "Yobibits (Yib)": 1208925819614629174706176, + + // Decimal bits (10^n) + "Decabits": 10, + "Hectobits": 100, + "Kilobits (Kb)": 1e3, + "Megabits (Mb)": 1e6, + "Gigabits (Gb)": 1e9, + "Terabits (Tb)": 1e12, + "Petabits (Pb)": 1e15, + "Exabits (Eb)": 1e18, + "Zettabits (Zb)": 1e21, + "Yottabits (Yb)": 1e24, + + // Binary bytes (8 x 2^n) + "Kibibytes (KiB)": 8192, + "Mebibytes (MiB)": 8388608, + "Gibibytes (GiB)": 8589934592, + "Tebibytes (TiB)": 8796093022208, + "Pebibytes (PiB)": 9007199254740992, + "Exbibytes (EiB)": 9223372036854775808, + "Zebibytes (ZiB)": 9444732965739290427392, + "Yobibytes (YiB)": 9671406556917033397649408, + + // Decimal bytes (8 x 10^n) + "Kilobytes (KB)": 8e3, + "Megabytes (MB)": 8e6, + "Gigabytes (GB)": 8e9, + "Terabytes (TB)": 8e12, + "Petabytes (PB)": 8e15, + "Exabytes (EB)": 8e18, + "Zettabytes (ZB)": 8e21, + "Yottabytes (YB)": 8e24, +}; + + +export default ConvertDataUnits; diff --git a/src/core/operations/ConvertDistance.mjs b/src/core/operations/ConvertDistance.mjs new file mode 100644 index 00000000..1151abc6 --- /dev/null +++ b/src/core/operations/ConvertDistance.mjs @@ -0,0 +1,96 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Convert distance operation + */ +class ConvertDistance extends Operation { + + /** + * ConvertDistance constructor + */ + constructor() { + super(); + + this.name = "Convert distance"; + this.module = "Default"; + this.description = "Converts a unit of distance to another format."; + this.infoURL = "https://wikipedia.org/wiki/Orders_of_magnitude_(length)"; + this.inputType = "BigNumber"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": DISTANCE_UNITS + }, + { + "name": "Output units", + "type": "option", + "value": DISTANCE_UNITS + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const [inputUnits, outputUnits] = args; + + input = input.times(DISTANCE_FACTOR[inputUnits]); + return input.div(DISTANCE_FACTOR[outputUnits]); + } + +} + +const DISTANCE_UNITS = [ + "[Metric]", "Nanometres (nm)", "Micrometres (µm)", "Millimetres (mm)", "Centimetres (cm)", "Metres (m)", "Kilometers (km)", "[/Metric]", + "[Imperial]", "Thou (th)", "Inches (in)", "Feet (ft)", "Yards (yd)", "Chains (ch)", "Furlongs (fur)", "Miles (mi)", "Leagues (lea)", "[/Imperial]", + "[Maritime]", "Fathoms (ftm)", "Cables", "Nautical miles", "[/Maritime]", + "[Comparisons]", "Cars (4m)", "Buses (8.4m)", "American football fields (91m)", "Football pitches (105m)", "[/Comparisons]", + "[Astronomical]", "Earth-to-Moons", "Earth's equators", "Astronomical units (au)", "Light-years (ly)", "Parsecs (pc)", "[/Astronomical]", +]; + +const DISTANCE_FACTOR = { // Multiples of a metre + "Nanometres (nm)": 1e-9, + "Micrometres (µm)": 1e-6, + "Millimetres (mm)": 1e-3, + "Centimetres (cm)": 1e-2, + "Metres (m)": 1, + "Kilometers (km)": 1e3, + + "Thou (th)": 0.0000254, + "Inches (in)": 0.0254, + "Feet (ft)": 0.3048, + "Yards (yd)": 0.9144, + "Chains (ch)": 20.1168, + "Furlongs (fur)": 201.168, + "Miles (mi)": 1609.344, + "Leagues (lea)": 4828.032, + + "Fathoms (ftm)": 1.853184, + "Cables": 185.3184, + "Nautical miles": 1853.184, + + "Cars (4m)": 4, + "Buses (8.4m)": 8.4, + "American football fields (91m)": 91, + "Football pitches (105m)": 105, + + "Earth-to-Moons": 380000000, + "Earth's equators": 40075016.686, + "Astronomical units (au)": 149597870700, + "Light-years (ly)": 9460730472580800, + "Parsecs (pc)": 3.0856776e16 +}; + + +export default ConvertDistance; diff --git a/src/core/operations/ConvertMass.mjs b/src/core/operations/ConvertMass.mjs new file mode 100644 index 00000000..3373aa57 --- /dev/null +++ b/src/core/operations/ConvertMass.mjs @@ -0,0 +1,144 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Convert mass operation + */ +class ConvertMass extends Operation { + + /** + * ConvertMass constructor + */ + constructor() { + super(); + + this.name = "Convert mass"; + this.module = "Default"; + this.description = "Converts a unit of mass to another format."; + this.infoURL = "https://wikipedia.org/wiki/Orders_of_magnitude_(mass)"; + this.inputType = "BigNumber"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": MASS_UNITS + }, + { + "name": "Output units", + "type": "option", + "value": MASS_UNITS + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const [inputUnits, outputUnits] = args; + + input = input.times(MASS_FACTOR[inputUnits]); + return input.div(MASS_FACTOR[outputUnits]); + } + +} + + +const MASS_UNITS = [ + "[Metric]", "Yoctogram (yg)", "Zeptogram (zg)", "Attogram (ag)", "Femtogram (fg)", "Picogram (pg)", "Nanogram (ng)", "Microgram (μg)", "Milligram (mg)", "Centigram (cg)", "Decigram (dg)", "Gram (g)", "Decagram (dag)", "Hectogram (hg)", "Kilogram (kg)", "Megagram (Mg)", "Tonne (t)", "Gigagram (Gg)", "Teragram (Tg)", "Petagram (Pg)", "Exagram (Eg)", "Zettagram (Zg)", "Yottagram (Yg)", "[/Metric]", + "[Imperial Avoirdupois]", "Grain (gr)", "Dram (dr)", "Ounce (oz)", "Pound (lb)", "Nail", "Stone (st)", "Quarter (gr)", "Tod", "US hundredweight (cwt)", "Imperial hundredweight (cwt)", "US ton (t)", "Imperial ton (t)", "[/Imperial Avoirdupois]", + "[Imperial Troy]", "Grain (gr)", "Pennyweight (dwt)", "Troy dram (dr t)", "Troy ounce (oz t)", "Troy pound (lb t)", "Mark", "[/Imperial Troy]", + "[Archaic]", "Wey", "Wool wey", "Suffolk wey", "Wool sack", "Coal sack", "Load", "Last", "Flax or feather last", "Gunpowder last", "Picul", "Rice last", "[/Archaic]", + "[Comparisons]", "Big Ben (14 tonnes)", "Blue whale (180 tonnes)", "International Space Station (417 tonnes)", "Space Shuttle (2,041 tonnes)", "RMS Titanic (52,000 tonnes)", "Great Pyramid of Giza (6,000,000 tonnes)", "Earth's oceans (1.4 yottagrams)", "[/Comparisons]", + "[Astronomical]", "A teaspoon of neutron star (5,500 million tonnes)", "Lunar mass (ML)", "Earth mass (M⊕)", "Jupiter mass (MJ)", "Solar mass (M☉)", "Sagittarius A* (7.5 x 10^36 kgs-ish)", "Milky Way galaxy (1.2 x 10^42 kgs)", "The observable universe (1.45 x 10^53 kgs)", "[/Astronomical]", +]; + +const MASS_FACTOR = { // Multiples of a gram + // Metric + "Yoctogram (yg)": 1e-24, + "Zeptogram (zg)": 1e-21, + "Attogram (ag)": 1e-18, + "Femtogram (fg)": 1e-15, + "Picogram (pg)": 1e-12, + "Nanogram (ng)": 1e-9, + "Microgram (μg)": 1e-6, + "Milligram (mg)": 1e-3, + "Centigram (cg)": 1e-2, + "Decigram (dg)": 1e-1, + "Gram (g)": 1, + "Decagram (dag)": 10, + "Hectogram (hg)": 100, + "Kilogram (kg)": 1000, + "Megagram (Mg)": 1e6, + "Tonne (t)": 1e6, + "Gigagram (Gg)": 1e9, + "Teragram (Tg)": 1e12, + "Petagram (Pg)": 1e15, + "Exagram (Eg)": 1e18, + "Zettagram (Zg)": 1e21, + "Yottagram (Yg)": 1e24, + + // Imperial Avoirdupois + "Grain (gr)": 64.79891e-3, + "Dram (dr)": 1.7718451953125, + "Ounce (oz)": 28.349523125, + "Pound (lb)": 453.59237, + "Nail": 3175.14659, + "Stone (st)": 6.35029318e3, + "Quarter (gr)": 12700.58636, + "Tod": 12700.58636, + "US hundredweight (cwt)": 45.359237e3, + "Imperial hundredweight (cwt)": 50.80234544e3, + "US ton (t)": 907.18474e3, + "Imperial ton (t)": 1016.0469088e3, + + // Imperial Troy + "Pennyweight (dwt)": 1.55517384, + "Troy dram (dr t)": 3.8879346, + "Troy ounce (oz t)": 31.1034768, + "Troy pound (lb t)": 373.2417216, + "Mark": 248.8278144, + + // Archaic + "Wey": 76.5e3, + "Wool wey": 101.7e3, + "Suffolk wey": 161.5e3, + "Wool sack": 153000, + "Coal sack": 50.80234544e3, + "Load": 918000, + "Last": 1836000, + "Flax or feather last": 770e3, + "Gunpowder last": 1090e3, + "Picul": 60.478982e3, + "Rice last": 1200e3, + + // Comparisons + "Big Ben (14 tonnes)": 14e6, + "Blue whale (180 tonnes)": 180e6, + "International Space Station (417 tonnes)": 417e6, + "Space Shuttle (2,041 tonnes)": 2041e6, + "RMS Titanic (52,000 tonnes)": 52000e6, + "Great Pyramid of Giza (6,000,000 tonnes)": 6e12, + "Earth's oceans (1.4 yottagrams)": 1.4e24, + + // Astronomical + "A teaspoon of neutron star (5,500 million tonnes)": 5.5e15, + "Lunar mass (ML)": 7.342e25, + "Earth mass (M⊕)": 5.97219e27, + "Jupiter mass (MJ)": 1.8981411476999997e30, + "Solar mass (M☉)": 1.98855e33, + "Sagittarius A* (7.5 x 10^36 kgs-ish)": 7.5e39, + "Milky Way galaxy (1.2 x 10^42 kgs)": 1.2e45, + "The observable universe (1.45 x 10^53 kgs)": 1.45e56, +}; + + +export default ConvertMass; diff --git a/src/core/operations/ConvertSpeed.mjs b/src/core/operations/ConvertSpeed.mjs new file mode 100644 index 00000000..ec92ad95 --- /dev/null +++ b/src/core/operations/ConvertSpeed.mjs @@ -0,0 +1,97 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Convert speed operation + */ +class ConvertSpeed extends Operation { + + /** + * ConvertSpeed constructor + */ + constructor() { + super(); + + this.name = "Convert speed"; + this.module = "Default"; + this.description = "Converts a unit of speed to another format."; + this.infoURL = "https://wikipedia.org/wiki/Orders_of_magnitude_(speed)"; + this.inputType = "BigNumber"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": SPEED_UNITS + }, + { + "name": "Output units", + "type": "option", + "value": SPEED_UNITS + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const [inputUnits, outputUnits] = args; + + input = input.times(SPEED_FACTOR[inputUnits]); + return input.div(SPEED_FACTOR[outputUnits]); + } + +} + +const SPEED_UNITS = [ + "[Metric]", "Metres per second (m/s)", "Kilometres per hour (km/h)", "[/Metric]", + "[Imperial]", "Miles per hour (mph)", "Knots (kn)", "[/Imperial]", + "[Comparisons]", "Human hair growth rate", "Bamboo growth rate", "World's fastest snail", "Usain Bolt's top speed", "Jet airliner cruising speed", "Concorde", "SR-71 Blackbird", "Space Shuttle", "International Space Station", "[/Comparisons]", + "[Scientific]", "Sound in standard atmosphere", "Sound in water", "Lunar escape velocity", "Earth escape velocity", "Earth's solar orbit", "Solar system's Milky Way orbit", "Milky Way relative to the cosmic microwave background", "Solar escape velocity", "Neutron star escape velocity (0.3c)", "Light in a diamond (0.4136c)", "Signal in an optical fibre (0.667c)", "Light (c)", "[/Scientific]", +]; + +const SPEED_FACTOR = { // Multiples of m/s + // Metric + "Metres per second (m/s)": 1, + "Kilometres per hour (km/h)": 0.2778, + + // Imperial + "Miles per hour (mph)": 0.44704, + "Knots (kn)": 0.5144, + + // Comparisons + "Human hair growth rate": 4.8e-9, + "Bamboo growth rate": 1.4e-5, + "World's fastest snail": 0.00275, + "Usain Bolt's top speed": 12.42, + "Jet airliner cruising speed": 250, + "Concorde": 603, + "SR-71 Blackbird": 981, + "Space Shuttle": 1400, + "International Space Station": 7700, + + // Scientific + "Sound in standard atmosphere": 340.3, + "Sound in water": 1500, + "Lunar escape velocity": 2375, + "Earth escape velocity": 11200, + "Earth's solar orbit": 29800, + "Solar system's Milky Way orbit": 200000, + "Milky Way relative to the cosmic microwave background": 552000, + "Solar escape velocity": 617700, + "Neutron star escape velocity (0.3c)": 100000000, + "Light in a diamond (0.4136c)": 124000000, + "Signal in an optical fibre (0.667c)": 200000000, + "Light (c)": 299792458, +}; + + +export default ConvertSpeed; diff --git a/src/core/operations/CountOccurrences.mjs b/src/core/operations/CountOccurrences.mjs new file mode 100644 index 00000000..5027a0f0 --- /dev/null +++ b/src/core/operations/CountOccurrences.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Count occurrences operation + */ +class CountOccurrences extends Operation { + + /** + * CountOccurrences constructor + */ + constructor() { + super(); + + this.name = "Count occurrences"; + this.module = "Default"; + this.description = "Counts the number of times the provided string occurs in the input."; + this.inputType = "string"; + this.outputType = "number"; + this.args = [ + { + "name": "Search string", + "type": "toggleString", + "value": "", + "toggleValues": ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + let search = args[0].string; + const type = args[0].option; + + if (type === "Regex" && search) { + try { + const regex = new RegExp(search, "gi"), + matches = input.match(regex); + return matches.length; + } catch (err) { + return 0; + } + } else if (search) { + if (type.indexOf("Extended") === 0) { + search = Utils.parseEscapedChars(search); + } + return input.count(search); + } else { + return 0; + } + } + +} + +export default CountOccurrences; diff --git a/src/core/operations/DESDecrypt.mjs b/src/core/operations/DESDecrypt.mjs new file mode 100644 index 00000000..620256c7 --- /dev/null +++ b/src/core/operations/DESDecrypt.mjs @@ -0,0 +1,93 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * DES Decrypt operation + */ +class DESDecrypt extends Operation { + + /** + * DESDecrypt constructor + */ + constructor() { + super(); + + this.name = "DES Decrypt"; + this.module = "Ciphers"; + this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.

Key: DES uses a key length of 8 bytes (64 bits).
Triple DES uses a key length of 24 bytes (192 bits).

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used."; + this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length !== 8) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +DES uses a key length of 8 bytes (64 bits). +Triple DES uses a key length of 24 bytes (192 bits).`); + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = forge.cipher.createDecipher("DES-" + mode, key); + decipher.start({iv: iv}); + decipher.update(forge.util.createBuffer(input)); + const result = decipher.finish(); + + if (result) { + return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); + } else { + throw new OperationError("Unable to decrypt input with these parameters."); + } + } + +} + +export default DESDecrypt; diff --git a/src/core/operations/DESEncrypt.mjs b/src/core/operations/DESEncrypt.mjs new file mode 100644 index 00000000..774be674 --- /dev/null +++ b/src/core/operations/DESEncrypt.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * DES Encrypt operation + */ +class DESEncrypt extends Operation { + + /** + * DESEncrypt constructor + */ + constructor() { + super(); + + this.name = "DES Encrypt"; + this.module = "Ciphers"; + this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.

Key: DES uses a key length of 8 bytes (64 bits).
Triple DES uses a key length of 24 bytes (192 bits).

You can generate a password-based key using one of the KDF operations.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used."; + this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length !== 8) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +DES uses a key length of 8 bytes (64 bits). +Triple DES uses a key length of 24 bytes (192 bits).`); + } + + input = Utils.convertToByteString(input, inputType); + + const cipher = forge.cipher.createCipher("DES-" + mode, key); + cipher.start({iv: iv}); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); + } + +} + +export default DESEncrypt; diff --git a/src/core/operations/DNSOverHTTPS.mjs b/src/core/operations/DNSOverHTTPS.mjs new file mode 100644 index 00000000..b56feb6a --- /dev/null +++ b/src/core/operations/DNSOverHTTPS.mjs @@ -0,0 +1,125 @@ +/** + * @author h345983745 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * DNS over HTTPS operation + */ +class DNSOverHTTPS extends Operation { + + /** + * DNSOverHTTPS constructor + */ + constructor() { + super(); + + this.name = "DNS over HTTPS"; + this.module = "Default"; + this.description = [ + "Takes a single domain name and performs a DNS lookup using DNS over HTTPS.", + "

", + "By default, Cloudflare and Google DNS over HTTPS services are supported.", + "

", + "Can be used with any service that supports the GET parameters name and type." + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/DNS_over_HTTPS"; + this.inputType = "string"; + this.outputType = "JSON"; + this.manualBake = true; + this.args = [ + { + name: "Resolver", + type: "editableOption", + value: [ + { + name: "Google", + value: "https://dns.google.com/resolve" + }, + { + name: "Cloudflare", + value: "https://cloudflare-dns.com/dns-query" + } + ] + }, + { + name: "Request Type", + type: "option", + value: [ + "A", + "AAAA", + "TXT", + "MX", + "DNSKEY", + "NS" + ] + }, + { + name: "Answer Data Only", + type: "boolean", + value: false + }, + { + name: "Validate DNSSEC", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + const [resolver, requestType, justAnswer, DNSSEC] = args; + let url = URL; + try { + url = new URL(resolver); + } catch (error) { + throw new OperationError(error.toString() + + "\n\nThis error could be caused by one of the following:\n" + + " - An invalid Resolver URL\n"); + } + const params = {name: input, type: requestType, cd: DNSSEC}; + + url.search = new URLSearchParams(params); + + return fetch(url, {headers: {"accept": "application/dns-json"}}).then(response => { + return response.json(); + }).then(data => { + if (justAnswer) { + return extractData(data.Answer); + } + return data; + }).catch(e => { + throw new OperationError(`Error making request to ${url}\n${e.toString()}`); + }); + + } +} + +/** + * Construct an array of just data from a DNS Answer section + * + * @private + * @param {JSON} data + * @returns {JSON} + */ +function extractData(data) { + if (typeof(data) == "undefined"){ + return []; + } else { + const dataValues = []; + data.forEach(element => { + dataValues.push(element.data); + }); + return dataValues; + } +} + +export default DNSOverHTTPS; diff --git a/src/core/operations/DateTime.js b/src/core/operations/DateTime.js deleted file mode 100755 index 523206b2..00000000 --- a/src/core/operations/DateTime.js +++ /dev/null @@ -1,460 +0,0 @@ -/** - * Date and time operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const DateTime = { - - /** - * @constant - * @default - */ - UNITS: ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"], - - /** - * From UNIX Timestamp operation. - * - * @param {number} input - * @param {Object[]} args - * @returns {string} - */ - runFromUnixTimestamp: function(input, args) { - let units = args[0], - d; - - input = parseFloat(input); - - if (units === "Seconds (s)") { - d = moment.unix(input); - return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss") + " UTC"; - } else if (units === "Milliseconds (ms)") { - d = moment(input); - return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; - } else if (units === "Microseconds (μs)") { - d = moment(input / 1000); - return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; - } else if (units === "Nanoseconds (ns)") { - d = moment(input / 1000000); - return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; - } else { - throw "Unrecognised unit"; - } - }, - - - /** - * @constant - * @default - */ - TREAT_AS_UTC: true, - - /** - * To UNIX Timestamp operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {number} - */ - runToUnixTimestamp: function(input, args) { - let units = args[0], - treatAsUTC = args[1], - d = treatAsUTC ? moment.utc(input) : moment(input); - - if (units === "Seconds (s)") { - return d.unix(); - } else if (units === "Milliseconds (ms)") { - return d.valueOf(); - } else if (units === "Microseconds (μs)") { - return d.valueOf() * 1000; - } else if (units === "Nanoseconds (ns)") { - return d.valueOf() * 1000000; - } else { - throw "Unrecognised unit"; - } - }, - - - /** - * @constant - * @default - */ - DATETIME_FORMATS: [ - { - name: "Standard date and time", - value: "DD/MM/YYYY HH:mm:ss" - }, - { - name: "American-style date and time", - value: "MM/DD/YYYY HH:mm:ss" - }, - { - name: "International date and time", - value: "YYYY-MM-DD HH:mm:ss" - }, - { - name: "Verbose date and time", - value: "dddd Do MMMM YYYY HH:mm:ss Z z" - }, - { - name: "UNIX timestamp (seconds)", - value: "X" - }, - { - name: "UNIX timestamp offset (milliseconds)", - value: "x" - }, - { - name: "Automatic", - value: "" - }, - ], - /** - * @constant - * @default - */ - INPUT_FORMAT_STRING: "DD/MM/YYYY HH:mm:ss", - /** - * @constant - * @default - */ - OUTPUT_FORMAT_STRING: "dddd Do MMMM YYYY HH:mm:ss Z z", - /** - * @constant - * @default - */ - TIMEZONES: ["UTC"].concat(moment.tz.names()), - - /** - * Translate DateTime Format operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runTranslateFormat: function(input, args) { - let inputFormat = args[1], - inputTimezone = args[2], - outputFormat = args[3], - outputTimezone = args[4], - date; - - try { - date = moment.tz(input, inputFormat, inputTimezone); - if (!date || date.format() === "Invalid date") throw Error; - } catch (err) { - return "Invalid format.\n\n" + DateTime.FORMAT_EXAMPLES; - } - - return date.tz(outputTimezone).format(outputFormat); - }, - - - /** - * Parse DateTime operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runParse: function(input, args) { - let inputFormat = args[1], - inputTimezone = args[2], - date, - output = ""; - - try { - date = moment.tz(input, inputFormat, inputTimezone); - if (!date || date.format() === "Invalid date") throw Error; - } catch (err) { - return "Invalid format.\n\n" + DateTime.FORMAT_EXAMPLES; - } - - output += "Date: " + date.format("dddd Do MMMM YYYY") + - "\nTime: " + date.format("HH:mm:ss") + - "\nPeriod: " + date.format("A") + - "\nTimezone: " + date.format("z") + - "\nUTC offset: " + date.format("ZZ") + - "\n\nDaylight Saving Time: " + date.isDST() + - "\nLeap year: " + date.isLeapYear() + - "\nDays in this month: " + date.daysInMonth() + - "\n\nDay of year: " + date.dayOfYear() + - "\nWeek number: " + date.weekYear() + - "\nQuarter: " + date.quarter(); - - return output; - }, - - - /** - * @constant - */ - FORMAT_EXAMPLES: "Format string tokens:\n\n\ -\ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ -
CategoryTokenOutput
MonthM1 2 ... 11 12
Mo1st 2nd ... 11th 12th
MM01 02 ... 11 12
MMMJan Feb ... Nov Dec
MMMMJanuary February ... November December
QuarterQ1 2 3 4
Day of MonthD1 2 ... 30 31
Do1st 2nd ... 30th 31st
DD01 02 ... 30 31
Day of YearDDD1 2 ... 364 365
DDDo1st 2nd ... 364th 365th
DDDD001 002 ... 364 365
Day of Weekd0 1 ... 5 6
do0th 1st ... 5th 6th
ddSu Mo ... Fr Sa
dddSun Mon ... Fri Sat
ddddSunday Monday ... Friday Saturday
Day of Week (Locale)e0 1 ... 5 6
Day of Week (ISO)E1 2 ... 6 7
Week of Yearw1 2 ... 52 53
wo1st 2nd ... 52nd 53rd
ww01 02 ... 52 53
Week of Year (ISO)W1 2 ... 52 53
Wo1st 2nd ... 52nd 53rd
WW01 02 ... 52 53
YearYY70 71 ... 29 30
YYYY1970 1971 ... 2029 2030
Week Yeargg70 71 ... 29 30
gggg1970 1971 ... 2029 2030
Week Year (ISO)GG70 71 ... 29 30
GGGG1970 1971 ... 2029 2030
AM/PMAAM PM
aam pm
HourH0 1 ... 22 23
HH00 01 ... 22 23
h1 2 ... 11 12
hh01 02 ... 11 12
Minutem0 1 ... 58 59
mm00 01 ... 58 59
Seconds0 1 ... 58 59
ss00 01 ... 58 59
Fractional SecondS0 1 ... 8 9
SS00 01 ... 98 99
SSS000 001 ... 998 999
SSSS ... SSSSSSSSS000[0..] 001[0..] ... 998[0..] 999[0..]
Timezonez or zzEST CST ... MST PST
Z-07:00 -06:00 ... +06:00 +07:00
ZZ-0700 -0600 ... +0600 +0700
Unix TimestampX1360013296
Unix Millisecond Timestampx1360013296123
", - -}; - -export default DateTime; diff --git a/src/core/operations/DechunkHTTPResponse.mjs b/src/core/operations/DechunkHTTPResponse.mjs new file mode 100644 index 00000000..61af035e --- /dev/null +++ b/src/core/operations/DechunkHTTPResponse.mjs @@ -0,0 +1,51 @@ +/** + * @author sevzero [sevzero@protonmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Dechunk HTTP response operation + */ +class DechunkHTTPResponse extends Operation { + + /** + * DechunkHTTPResponse constructor + */ + constructor() { + super(); + + this.name = "Dechunk HTTP response"; + this.module = "Default"; + this.description = "Parses an HTTP response transferred using Transfer-Encoding: Chunked"; + this.infoURL = "https://wikipedia.org/wiki/Chunked_transfer_encoding"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const chunks = []; + let chunkSizeEnd = input.indexOf("\n") + 1; + const lineEndings = input.charAt(chunkSizeEnd - 2) === "\r" ? "\r\n" : "\n"; + const lineEndingsLength = lineEndings.length; + let chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16); + while (!isNaN(chunkSize)) { + chunks.push(input.slice(chunkSizeEnd, chunkSize + chunkSizeEnd)); + input = input.slice(chunkSizeEnd + chunkSize + lineEndingsLength); + chunkSizeEnd = input.indexOf(lineEndings) + lineEndingsLength; + chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16); + } + return chunks.join("") + input; + } + +} + +export default DechunkHTTPResponse; diff --git a/src/core/operations/DecodeNetBIOSName.mjs b/src/core/operations/DecodeNetBIOSName.mjs new file mode 100644 index 00000000..bbfe2b6e --- /dev/null +++ b/src/core/operations/DecodeNetBIOSName.mjs @@ -0,0 +1,60 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Decode NetBIOS Name operation + */ +class DecodeNetBIOSName extends Operation { + + /** + * DecodeNetBIOSName constructor + */ + constructor() { + super(); + + this.name = "Decode NetBIOS Name"; + this.module = "Default"; + this.description = "NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a longer representation is used.

There are two levels of encoding. The first level maps a NetBIOS name into a domain system name. The second level maps the domain system name into the 'compressed' representation required for interaction with the domain name system.

This operation decodes the first level of encoding. See RFC 1001 for full details."; + this.infoURL = "https://wikipedia.org/wiki/NetBIOS"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Offset", + "type": "number", + "value": 65 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = [], + offset = args[0]; + + if (input.length <= 32 && (input.length % 2) === 0) { + for (let i = 0; i < input.length; i += 2) { + output.push((((input[i] & 0xff) - offset) << 4) | + (((input[i + 1] & 0xff) - offset) & 0xf)); + } + for (let i = output.length - 1; i > 0; i--) { + if (output[i] === 32) output.splice(i, i); + else break; + } + } + + return output; + } + +} + +export default DecodeNetBIOSName; diff --git a/src/core/operations/DecodeText.mjs b/src/core/operations/DecodeText.mjs new file mode 100644 index 00000000..a5d60706 --- /dev/null +++ b/src/core/operations/DecodeText.mjs @@ -0,0 +1,56 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import cptable from "../vendor/js-codepage/cptable.js"; +import {IO_FORMAT} from "../lib/ChrEnc"; + +/** + * Decode text operation + */ +class DecodeText extends Operation { + + /** + * DecodeText constructor + */ + constructor() { + super(); + + this.name = "Decode text"; + this.module = "Encodings"; + this.description = [ + "Decodes text from the chosen character encoding.", + "

", + "Supported charsets are:", + "
    ", + Object.keys(IO_FORMAT).map(e => `
  • ${e}
  • `).join("\n"), + "
", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Encoding", + "type": "option", + "value": Object.keys(IO_FORMAT) + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const format = IO_FORMAT[args[0]]; + return cptable.utils.decode(format, input); + } + +} + +export default DecodeText; diff --git a/src/core/operations/DefangURL.mjs b/src/core/operations/DefangURL.mjs new file mode 100644 index 00000000..57d4298e --- /dev/null +++ b/src/core/operations/DefangURL.mjs @@ -0,0 +1,102 @@ +/** + * @author arnydo [arnydo@protonmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {URL_REGEX, DOMAIN_REGEX} from "../lib/Extract"; + +/** + * DefangURL operation + */ +class DefangURL extends Operation { + + /** + * DefangURL constructor + */ + constructor() { + super(); + + this.name = "Defang URL"; + this.module = "Default"; + this.description = "Takes a Universal Resource Locator (URL) and 'Defangs' it; meaning the URL becomes invalid, neutralising the risk of accidentally clicking on a malicious link.

This is often used when dealing with malicious links or IOCs.

Works well when combined with the 'Extract URLs' operation."; + this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Escape dots", + type: "boolean", + value: true + }, + { + name: "Escape http", + type: "boolean", + value: true + }, + { + name: "Escape ://", + type: "boolean", + value: true + }, + { + name: "Process", + type: "option", + value: ["Valid domains and full URLs", "Only full URLs", "Everything"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [dots, http, slashes, process] = args; + + switch (process) { + case "Valid domains and full URLs": + input = input.replace(URL_REGEX, x => { + return defangURL(x, dots, http, slashes); + }); + input = input.replace(DOMAIN_REGEX, x => { + return defangURL(x, dots, http, slashes); + }); + break; + case "Only full URLs": + input = input.replace(URL_REGEX, x => { + return defangURL(x, dots, http, slashes); + }); + break; + case "Everything": + input = defangURL(input, dots, http, slashes); + break; + } + + return input; + } + +} + + +/** + * Defangs a given URL + * + * @param {string} url + * @param {boolean} dots + * @param {boolean} http + * @param {boolean} slashes + * @returns {string} + */ +function defangURL(url, dots, http, slashes) { + if (dots) url = url.replace(/\./g, "[.]"); + if (http) url = url.replace(/http/gi, "hxxp"); + if (slashes) url = url.replace(/:\/\//g, "[://]"); + + return url; +} + +export default DefangURL; diff --git a/src/core/operations/DeriveEVPKey.mjs b/src/core/operations/DeriveEVPKey.mjs new file mode 100644 index 00000000..a14d2249 --- /dev/null +++ b/src/core/operations/DeriveEVPKey.mjs @@ -0,0 +1,145 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import CryptoJS from "crypto-js"; + +/** + * Derive EVP key operation + */ +class DeriveEVPKey extends Operation { + + /** + * DeriveEVPKey constructor + */ + constructor() { + super(); + + this.name = "Derive EVP key"; + this.module = "Ciphers"; + this.description = "This operation performs a password-based key derivation function (PBKDF) used extensively in OpenSSL. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.

A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.

If you leave the salt argument empty, a random salt will be generated."; + this.infoURL = "https://wikipedia.org/wiki/Key_derivation_function"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Passphrase", + "type": "toggleString", + "value": "", + "toggleValues": ["UTF8", "Latin1", "Hex", "Base64"] + }, + { + "name": "Key size", + "type": "number", + "value": 128 + }, + { + "name": "Iterations", + "type": "number", + "value": 1 + }, + { + "name": "Hashing function", + "type": "option", + "value": ["SHA1", "SHA256", "SHA384", "SHA512", "MD5"] + }, + { + "name": "Salt", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const passphrase = Utils.convertToByteString(args[0].string, args[0].option), + keySize = args[1] / 32, + iterations = args[2], + hasher = args[3], + salt = Utils.convertToByteString(args[4].string, args[4].option), + key = CryptoJS.EvpKDF(passphrase, salt, { + keySize: keySize, + hasher: CryptoJS.algo[hasher], + iterations: iterations, + }); + + return key.toString(CryptoJS.enc.Hex); + } + +} + +export default DeriveEVPKey; + +/** + * Overwriting the CryptoJS OpenSSL key derivation function so that it is possible to not pass a + * salt in. + + * @param {string} password - The password to derive from. + * @param {number} keySize - The size in words of the key to generate. + * @param {number} ivSize - The size in words of the IV to generate. + * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be + * generated randomly. If set to false, no salt will be added. + * + * @returns {CipherParams} A cipher params object with the key, IV, and salt. + * + * @static + * + * @example + * // Randomly generates a salt + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * // Uses the salt 'saltsalt' + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + * // Does not use a salt + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, false); + */ +CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) { + // Generate random salt if no salt specified and not set to false + // This line changed from `if (!salt) {` to the following + if (salt === undefined || salt === null) { + salt = CryptoJS.lib.WordArray.random(64/8); + } + + // Derive key and IV + const key = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); + + // Separate key and IV + const iv = CryptoJS.lib.WordArray.create(key.words.slice(keySize), ivSize * 4); + key.sigBytes = keySize * 4; + + // Return params + return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt }); +}; + + +/** + * Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse + * the hex string. + * + * @param {string} hexStr + * @returns {CryptoJS.lib.WordArray} + */ +CryptoJS.enc.Hex.parse = function (hexStr) { + // Remove whitespace + hexStr = hexStr.replace(/\s/g, ""); + + // Shortcut + const hexStrLength = hexStr.length; + + // Convert + const words = []; + for (let i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2); +}; diff --git a/src/core/operations/DerivePBKDF2Key.mjs b/src/core/operations/DerivePBKDF2Key.mjs new file mode 100644 index 00000000..53fedb4d --- /dev/null +++ b/src/core/operations/DerivePBKDF2Key.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * Derive PBKDF2 key operation + */ +class DerivePBKDF2Key extends Operation { + + /** + * DerivePBKDF2Key constructor + */ + constructor() { + super(); + + this.name = "Derive PBKDF2 key"; + this.module = "Ciphers"; + this.description = "PBKDF2 is a password-based key derivation function. It is part of RSA Laboratories' Public-Key Cryptography Standards (PKCS) series, specifically PKCS #5 v2.0, also published as Internet Engineering Task Force's RFC 2898.

In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.

A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.

If you leave the salt argument empty, a random salt will be generated."; + this.infoURL = "https://wikipedia.org/wiki/PBKDF2"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Passphrase", + "type": "toggleString", + "value": "", + "toggleValues": ["UTF8", "Latin1", "Hex", "Base64"] + }, + { + "name": "Key size", + "type": "number", + "value": 128 + }, + { + "name": "Iterations", + "type": "number", + "value": 1 + }, + { + "name": "Hashing function", + "type": "option", + "value": ["SHA1", "SHA256", "SHA384", "SHA512", "MD5"] + }, + { + "name": "Salt", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const passphrase = Utils.convertToByteString(args[0].string, args[0].option), + keySize = args[1], + iterations = args[2], + hasher = args[3], + salt = Utils.convertToByteString(args[4].string, args[4].option) || + forge.random.getBytesSync(keySize), + derivedKey = forge.pkcs5.pbkdf2(passphrase, salt, iterations, keySize / 8, hasher.toLowerCase()); + + return forge.util.bytesToHex(derivedKey); + } + +} + +export default DerivePBKDF2Key; diff --git a/src/core/operations/DetectFileType.mjs b/src/core/operations/DetectFileType.mjs new file mode 100644 index 00000000..1d6897a0 --- /dev/null +++ b/src/core/operations/DetectFileType.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Magic from "../lib/Magic"; + +/** + * Detect File Type operation + */ +class DetectFileType extends Operation { + + /** + * DetectFileType constructor + */ + constructor() { + super(); + + this.name = "Detect File Type"; + this.module = "Default"; + this.description = "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.

Currently supports the following file types: 7z, amr, avi, bmp, bz2, class, cr2, crx, dex, dmg, doc, elf, eot, epub, exe, flac, flv, gif, gz, ico, iso, jpg, jxr, m4a, m4v, mid, mkv, mov, mp3, mp4, mpg, ogg, otf, pdf, png, ppt, ps, psd, rar, rtf, sqlite, swf, tar, tar.z, tif, ttf, utf8, vmdk, wav, webm, webp, wmv, woff, woff2, xls, xz, zip."; + this.infoURL = "https://wikipedia.org/wiki/List_of_file_signatures"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const data = new Uint8Array(input), + type = Magic.magicFileType(data); + + if (!type) { + return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?"; + } else { + let output = "File extension: " + type.ext + "\n" + + "MIME type: " + type.mime; + + if (type.desc && type.desc.length) { + output += "\nDescription: " + type.desc; + } + + return output; + } + } + +} + +export default DetectFileType; diff --git a/src/core/operations/Diff.mjs b/src/core/operations/Diff.mjs new file mode 100644 index 00000000..9e094e99 --- /dev/null +++ b/src/core/operations/Diff.mjs @@ -0,0 +1,129 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import * as JsDiff from "diff"; +import OperationError from "../errors/OperationError"; + +/** + * Diff operation + */ +class Diff extends Operation { + + /** + * Diff constructor + */ + constructor() { + super(); + + this.name = "Diff"; + this.module = "Diff"; + this.description = "Compares two inputs (separated by the specified delimiter) and highlights the differences between them."; + this.infoURL = "https://wikipedia.org/wiki/File_comparison"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Sample delimiter", + "type": "binaryString", + "value": "\\n\\n" + }, + { + "name": "Diff by", + "type": "option", + "value": ["Character", "Word", "Line", "Sentence", "CSS", "JSON"] + }, + { + "name": "Show added", + "type": "boolean", + "value": true + }, + { + "name": "Show removed", + "type": "boolean", + "value": true + }, + { + "name": "Ignore whitespace", + "type": "boolean", + "value": false, + "hint": "Relevant for word and line" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [ + sampleDelim, + diffBy, + showAdded, + showRemoved, + ignoreWhitespace + ] = args, + samples = input.split(sampleDelim); + let output = "", + diff; + + // Node and Webpack load modules slightly differently + const jsdiff = JsDiff.default ? JsDiff.default : JsDiff; + + if (!samples || samples.length !== 2) { + throw new OperationError("Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?"); + } + + switch (diffBy) { + case "Character": + diff = jsdiff.diffChars(samples[0], samples[1]); + break; + case "Word": + if (ignoreWhitespace) { + diff = jsdiff.diffWords(samples[0], samples[1]); + } else { + diff = jsdiff.diffWordsWithSpace(samples[0], samples[1]); + } + break; + case "Line": + if (ignoreWhitespace) { + diff = jsdiff.diffTrimmedLines(samples[0], samples[1]); + } else { + diff = jsdiff.diffLines(samples[0], samples[1]); + } + break; + case "Sentence": + diff = jsdiff.diffSentences(samples[0], samples[1]); + break; + case "CSS": + diff = jsdiff.diffCss(samples[0], samples[1]); + break; + case "JSON": + diff = jsdiff.diffJson(samples[0], samples[1]); + break; + default: + throw new OperationError("Invalid 'Diff by' option."); + } + + for (let i = 0; i < diff.length; i++) { + if (diff[i].added) { + if (showAdded) output += "" + Utils.escapeHtml(diff[i].value) + ""; + } else if (diff[i].removed) { + if (showRemoved) output += "" + Utils.escapeHtml(diff[i].value) + ""; + } else { + output += Utils.escapeHtml(diff[i].value); + } + } + + return output; + } + +} + +export default Diff; diff --git a/src/core/operations/DisassembleX86.mjs b/src/core/operations/DisassembleX86.mjs new file mode 100644 index 00000000..b25dcb48 --- /dev/null +++ b/src/core/operations/DisassembleX86.mjs @@ -0,0 +1,134 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import * as disassemble from "../vendor/DisassembleX86-64"; +import OperationError from "../errors/OperationError"; + +/** + * Disassemble x86 operation + */ +class DisassembleX86 extends Operation { + + /** + * DisassembleX86 constructor + */ + constructor() { + super(); + + this.name = "Disassemble x86"; + this.module = "Shellcode"; + this.description = "Disassembly is the process of translating machine language into assembly language.

This operation supports 64-bit, 32-bit and 16-bit code written for Intel or AMD x86 processors. It is particularly useful for reverse engineering shellcode.

Input should be in hexadecimal."; + this.infoURL = "https://wikipedia.org/wiki/X86"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Bit mode", + "type": "option", + "value": ["64", "32", "16"] + }, + { + "name": "Compatibility", + "type": "option", + "value": [ + "Full x86 architecture", + "Knights Corner", + "Larrabee", + "Cyrix", + "Geode", + "Centaur", + "X86/486" + ] + }, + { + "name": "Code Segment (CS)", + "type": "number", + "value": 16 + }, + { + "name": "Offset (IP)", + "type": "number", + "value": 0 + }, + { + "name": "Show instruction hex", + "type": "boolean", + "value": true + }, + { + "name": "Show instruction position", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid mode value + */ + run(input, args) { + const [ + mode, + compatibility, + codeSegment, + offset, + showInstructionHex, + showInstructionPos + ] = args; + + switch (mode) { + case "64": + disassemble.setBitMode(2); + break; + case "32": + disassemble.setBitMode(1); + break; + case "16": + disassemble.setBitMode(0); + break; + default: + throw new OperationError("Invalid mode value"); + } + + switch (compatibility) { + case "Full x86 architecture": + disassemble.CompatibilityMode(0); + break; + case "Knights Corner": + disassemble.CompatibilityMode(1); + break; + case "Larrabee": + disassemble.CompatibilityMode(2); + break; + case "Cyrix": + disassemble.CompatibilityMode(3); + break; + case "Geode": + disassemble.CompatibilityMode(4); + break; + case "Centaur": + disassemble.CompatibilityMode(5); + break; + case "X86/486": + disassemble.CompatibilityMode(6); + break; + } + + disassemble.SetBasePosition(codeSegment + ":" + offset); + disassemble.setShowInstructionHex(showInstructionHex); + disassemble.setShowInstructionPos(showInstructionPos); + disassemble.LoadBinCode(input.replace(/\s/g, "")); + return disassemble.LDisassemble(); + } + +} + +export default DisassembleX86; diff --git a/src/core/operations/Divide.mjs b/src/core/operations/Divide.mjs new file mode 100644 index 00000000..5ca88be0 --- /dev/null +++ b/src/core/operations/Divide.mjs @@ -0,0 +1,51 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { div, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + + +/** + * Divide operation + */ +class Divide extends Operation { + + /** + * Divide constructor + */ + constructor() { + super(); + + this.name = "Divide"; + this.module = "Default"; + this.description = "Divides a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 2.5"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = div(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Divide; diff --git a/src/core/operations/DropBytes.mjs b/src/core/operations/DropBytes.mjs new file mode 100644 index 00000000..80492c34 --- /dev/null +++ b/src/core/operations/DropBytes.mjs @@ -0,0 +1,123 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Drop bytes operation + */ +class DropBytes extends Operation { + + /** + * DropBytes constructor + */ + constructor() { + super(); + + this.name = "Drop bytes"; + this.module = "Default"; + this.description = "Cuts a slice of the specified number of bytes out of the data. Negative values are allowed."; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Start", + "type": "number", + "value": 0 + }, + { + "name": "Length", + "type": "number", + "value": 5 + }, + { + "name": "Apply to each line", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + * + * @throws {OperationError} if invalid input + */ + run(input, args) { + let start = args[0], + length = args[1]; + const applyToEachLine = args[2]; + + if (!applyToEachLine) { + if (start < 0) { // Take from the end + start = input.byteLength + start; + } + + if (length < 0) { // Flip start point + start = start + length; + if (start < 0) { + start = input.byteLength + start; + length = start - length; + } else { + length = -length; + } + } + + const left = input.slice(0, start), + right = input.slice(start + length, input.byteLength); + const result = new Uint8Array(left.byteLength + right.byteLength); + result.set(new Uint8Array(left), 0); + result.set(new Uint8Array(right), left.byteLength); + return result.buffer; + } + + // Split input into lines + const data = new Uint8Array(input); + const lines = []; + let line = [], + i; + + for (i = 0; i < data.length; i++) { + if (data[i] === 0x0a) { + lines.push(line); + line = []; + } else { + line.push(data[i]); + } + } + lines.push(line); + + let output = [], + s = start, + l = length; + for (i = 0; i < lines.length; i++) { + if (s < 0) { // Take from the end + s = lines[i].length + s; + } + + if (l < 0) { // Flip start point + s = s + l; + if (s < 0) { + s = lines[i].length + s; + l = s - l; + } else { + l = -l; + } + } + + output = output.concat(lines[i].slice(0, s).concat(lines[i].slice(s+l, lines[i].length))); + output.push(0x0a); + s = start; + l = length; + } + return new Uint8Array(output.slice(0, output.length-1)).buffer; + } + +} + +export default DropBytes; diff --git a/src/core/operations/EncodeNetBIOSName.mjs b/src/core/operations/EncodeNetBIOSName.mjs new file mode 100644 index 00000000..9352ed4f --- /dev/null +++ b/src/core/operations/EncodeNetBIOSName.mjs @@ -0,0 +1,60 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Encode NetBIOS Name operation + */ +class EncodeNetBIOSName extends Operation { + + /** + * EncodeNetBIOSName constructor + */ + constructor() { + super(); + + this.name = "Encode NetBIOS Name"; + this.module = "Default"; + this.description = "NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a longer representation is used.

There are two levels of encoding. The first level maps a NetBIOS name into a domain system name. The second level maps the domain system name into the 'compressed' representation required for interaction with the domain name system.

This operation carries out the first level of encoding. See RFC 1001 for full details."; + this.infoURL = "https://wikipedia.org/wiki/NetBIOS"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Offset", + "type": "number", + "value": 65 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = [], + offset = args[0]; + + if (input.length <= 16) { + const len = input.length; + input.length = 16; + input.fill(32, len, 16); + for (let i = 0; i < input.length; i++) { + output.push((input[i] >> 4) + offset); + output.push((input[i] & 0xf) + offset); + } + } + + return output; + + } + +} + +export default EncodeNetBIOSName; diff --git a/src/core/operations/EncodeText.mjs b/src/core/operations/EncodeText.mjs new file mode 100644 index 00000000..dd3241a2 --- /dev/null +++ b/src/core/operations/EncodeText.mjs @@ -0,0 +1,59 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import cptable from "../vendor/js-codepage/cptable.js"; +import {IO_FORMAT} from "../lib/ChrEnc"; + +/** + * Encode text operation + */ +class EncodeText extends Operation { + + /** + * EncodeText constructor + */ + constructor() { + super(); + + this.name = "Encode text"; + this.module = "Encodings"; + this.description = [ + "Encodes text into the chosen character encoding.", + "

", + "Supported charsets are:", + "
    ", + Object.keys(IO_FORMAT).map(e => `
  • ${e}
  • `).join("\n"), + "
", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Encoding", + "type": "option", + "value": Object.keys(IO_FORMAT) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const format = IO_FORMAT[args[0]]; + let encoded = cptable.utils.encode(format, input); + encoded = Array.from(encoded); + return encoded; + } + +} + + +export default EncodeText; diff --git a/src/core/operations/Endian.js b/src/core/operations/Endian.js deleted file mode 100755 index bb0f9136..00000000 --- a/src/core/operations/Endian.js +++ /dev/null @@ -1,99 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Endian operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Endian = { - - /** - * @constant - * @default - */ - DATA_FORMAT: ["Hex", "Raw"], - /** - * @constant - * @default - */ - WORD_LENGTH: 4, - /** - * @constant - * @default - */ - PAD_INCOMPLETE_WORDS: true, - - /** - * Swap endianness operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSwapEndianness: function(input, args) { - let dataFormat = args[0], - wordLength = args[1], - padIncompleteWords = args[2], - data = [], - result = [], - words = [], - i = 0, - j = 0; - - if (wordLength <= 0) { - return "Word length must be greater than 0"; - } - - // Convert input to raw data based on specified data format - switch (dataFormat) { - case "Hex": - data = Utils.fromHex(input); - break; - case "Raw": - data = Utils.strToByteArray(input); - break; - default: - data = input; - } - - // Split up into words - for (i = 0; i < data.length; i += wordLength) { - const word = data.slice(i, i + wordLength); - - // Pad word if too short - if (padIncompleteWords && word.length < wordLength){ - for (j = word.length; j < wordLength; j++) { - word.push(0); - } - } - - words.push(word); - } - - // Swap endianness and flatten - for (i = 0; i < words.length; i++) { - j = words[i].length; - while (j--) { - result.push(words[i][j]); - } - } - - // Convert data back to specified data format - switch (dataFormat) { - case "Hex": - return Utils.toHex(result); - case "Raw": - return Utils.byteArrayToUtf8(result); - default: - return result; - } - }, - -}; - -export default Endian; diff --git a/src/core/operations/Entropy.js b/src/core/operations/Entropy.js deleted file mode 100755 index 3451914d..00000000 --- a/src/core/operations/Entropy.js +++ /dev/null @@ -1,168 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Entropy operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Entropy = { - - /** - * @constant - * @default - */ - CHUNK_SIZE: 1000, - - /** - * Entropy operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {html} - */ - runEntropy: function(input, args) { - let chunkSize = args[0], - output = "", - entropy = Entropy._calcEntropy(input); - - output += "Shannon entropy: " + entropy + "\n" + - "

\n" + - "- 0 represents no randomness (i.e. all the bytes in the data have the same value) whereas 8, the maximum, represents a completely random string.\n" + - "- Standard English text usually falls somewhere between 3.5 and 5.\n" + - "- Properly encrypted or compressed data of a reasonable length should have an entropy of over 7.5.\n\n" + - "The following results show the entropy of chunks of the input data. Chunks with particularly high entropy could suggest encrypted or compressed sections.\n\n" + - "
"; - - let chunkEntropy = 0; - if (chunkSize !== 0) { - for (let i = 0; i < input.length; i += chunkSize) { - chunkEntropy = Entropy._calcEntropy(input.slice(i, i+chunkSize)); - output += "Bytes " + i + " to " + (i+chunkSize) + ": " + chunkEntropy + "\n"; - } - } else { - output += "Chunk size cannot be 0."; - } - - return output; - }, - - - /** - * @constant - * @default - */ - FREQ_ZEROS: false, - - /** - * Frequency distribution operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {html} - */ - runFreqDistrib: function (input, args) { - if (!input.length) return "No data"; - - let distrib = new Array(256).fill(0), - percentages = new Array(256), - len = input.length, - showZeroes = args[0], - i; - - // Count bytes - for (i = 0; i < len; i++) { - distrib[input[i]]++; - } - - // Calculate percentages - let repr = 0; - for (i = 0; i < 256; i++) { - if (distrib[i] > 0) repr++; - percentages[i] = distrib[i] / len * 100; - } - - // Print - let output = "
" + - "Total data length: " + len + - "\nNumber of bytes represented: " + repr + - "\nNumber of bytes not represented: " + (256-repr) + - "\n\nByte Percentage\n" + - ""; - - for (i = 0; i < 256; i++) { - if (distrib[i] || showZeroes) { - output += " " + Utils.hex(i, 2) + " (" + - Utils.padRight(percentages[i].toFixed(2).replace(".00", "") + "%)", 8) + - Array(Math.ceil(percentages[i])+1).join("|") + "\n"; - } - } - - return output; - }, - - - /** - * Calculates the Shannon entropy for a given chunk of data. - * - * @private - * @param {byteArray} data - * @returns {number} - */ - _calcEntropy: function(data) { - let prob = [], - uniques = data.unique(), - str = Utils.byteArrayToChars(data), - i; - - for (i = 0; i < uniques.length; i++) { - prob.push(str.count(Utils.chr(uniques[i])) / data.length); - } - - let entropy = 0, - p; - - for (i = 0; i < prob.length; i++) { - p = prob[i]; - entropy += p * Math.log(p) / Math.log(2); - } - - return -entropy; - }, - -}; - -export default Entropy; diff --git a/src/core/operations/Entropy.mjs b/src/core/operations/Entropy.mjs new file mode 100644 index 00000000..868178fc --- /dev/null +++ b/src/core/operations/Entropy.mjs @@ -0,0 +1,97 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Entropy operation + */ +class Entropy extends Operation { + + /** + * Entropy constructor + */ + constructor() { + super(); + + this.name = "Entropy"; + this.module = "Default"; + this.description = "Shannon Entropy, in the context of information theory, is a measure of the rate at which information is produced by a source of data. It can be used, in a broad sense, to detect whether data is likely to be structured or unstructured. 8 is the maximum, representing highly unstructured, 'random' data. English language text usually falls somewhere between 3.5 and 5. Properly encrypted or compressed data should have an entropy of over 7.5."; + this.infoURL = "https://wikipedia.org/wiki/Entropy_(information_theory)"; + this.inputType = "byteArray"; + this.outputType = "number"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const prob = [], + uniques = input.unique(), + str = Utils.byteArrayToChars(input); + let i; + + for (i = 0; i < uniques.length; i++) { + prob.push(str.count(Utils.chr(uniques[i])) / input.length); + } + + let entropy = 0, + p; + + for (i = 0; i < prob.length; i++) { + p = prob[i]; + entropy += p * Math.log(p) / Math.log(2); + } + + return -entropy; + } + + /** + * Displays the entropy as a scale bar for web apps. + * + * @param {number} entropy + * @returns {html} + */ + present(entropy) { + return `Shannon entropy: ${entropy} +

+- 0 represents no randomness (i.e. all the bytes in the data have the same value) whereas 8, the maximum, represents a completely random string. +- Standard English text usually falls somewhere between 3.5 and 5. +- Properly encrypted or compressed data of a reasonable length should have an entropy of over 7.5. + +The following results show the entropy of chunks of the input data. Chunks with particularly high entropy could suggest encrypted or compressed sections. + +
`; + } + +} + +export default Entropy; diff --git a/src/core/operations/EscapeString.mjs b/src/core/operations/EscapeString.mjs new file mode 100644 index 00000000..2d945e24 --- /dev/null +++ b/src/core/operations/EscapeString.mjs @@ -0,0 +1,88 @@ +/** + * @author Vel0x [dalemy@microsoft.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import jsesc from "jsesc"; + +/** + * Escape string operation + */ +class EscapeString extends Operation { + + /** + * EscapeString constructor + */ + constructor() { + super(); + + this.name = "Escape string"; + this.module = "Default"; + this.description = "Escapes special characters in a string so that they do not cause conflicts. For example, Don't stop me now becomes Don\\'t stop me now.

Supports the following escape sequences:
  • \\n (Line feed/newline)
  • \\r (Carriage return)
  • \\t (Horizontal tab)
  • \\b (Backspace)
  • \\f (Form feed)
  • \\xnn (Hex, where n is 0-f)
  • \\\\ (Backslash)
  • \\' (Single quote)
  • \\" (Double quote)
  • \\unnnn (Unicode character)
  • \\u{nnnnnn} (Unicode code point)
"; + this.infoURL = "https://wikipedia.org/wiki/Escape_sequence"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Escape level", + "type": "option", + "value": ["Special chars", "Everything", "Minimal"] + }, + { + "name": "Escape quote", + "type": "option", + "value": ["Single", "Double", "Backtick"] + }, + { + "name": "JSON compatible", + "type": "boolean", + "value": false + }, + { + "name": "ES6 compatible", + "type": "boolean", + "value": true + }, + { + "name": "Uppercase hex", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @example + * EscapeString.run("Don't do that", []) + * > "Don\'t do that" + * EscapeString.run(`Hello + * World`, []) + * > "Hello\nWorld" + */ + run(input, args) { + const level = args[0], + quotes = args[1], + jsonCompat = args[2], + es6Compat = args[3], + lowercaseHex = !args[4]; + + return jsesc(input, { + quotes: quotes.toLowerCase(), + es6: es6Compat, + escapeEverything: level === "Everything", + minimal: level === "Minimal", + json: jsonCompat, + lowercaseHex: lowercaseHex, + }); + } + +} + +export default EscapeString; diff --git a/src/core/operations/EscapeUnicodeCharacters.mjs b/src/core/operations/EscapeUnicodeCharacters.mjs new file mode 100644 index 00000000..9bbf4f69 --- /dev/null +++ b/src/core/operations/EscapeUnicodeCharacters.mjs @@ -0,0 +1,96 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Escape Unicode Characters operation + */ +class EscapeUnicodeCharacters extends Operation { + + /** + * EscapeUnicodeCharacters constructor + */ + constructor() { + super(); + + this.name = "Escape Unicode Characters"; + this.module = "Default"; + this.description = "Converts characters to their unicode-escaped notations.

Supports the prefixes:
  • \\u
  • %u
  • U+
e.g. σου becomes \\u03C3\\u03BF\\u03C5"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Prefix", + "type": "option", + "value": ["\\u", "%u", "U+"] + }, + { + "name": "Encode all chars", + "type": "boolean", + "value": false + }, + { + "name": "Padding", + "type": "number", + "value": 4 + }, + { + "name": "Uppercase hex", + "type": "boolean", + "value": true + } + ]; + this.patterns = [ + { + match: "\\\\u(?:[\\da-f]{4,6})", + flags: "i", + args: ["\\u"] + }, + { + match: "%u(?:[\\da-f]{4,6})", + flags: "i", + args: ["%u"] + }, + { + match: "U\\+(?:[\\da-f]{4,6})", + flags: "i", + args: ["U+"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const regexWhitelist = /[ -~]/i, + [prefix, encodeAll, padding, uppercaseHex] = args; + + let output = "", + character = ""; + + for (let i = 0; i < input.length; i++) { + character = input[i]; + if (!encodeAll && regexWhitelist.test(character)) { + // It’s a printable ASCII character so don’t escape it. + output += character; + continue; + } + + let cp = character.codePointAt(0).toString(16); + if (uppercaseHex) cp = cp.toUpperCase(); + output += prefix + cp.padStart(padding, "0"); + } + + return output; + } + +} + +export default EscapeUnicodeCharacters; diff --git a/src/core/operations/ExpandAlphabetRange.mjs b/src/core/operations/ExpandAlphabetRange.mjs new file mode 100644 index 00000000..761a7a3b --- /dev/null +++ b/src/core/operations/ExpandAlphabetRange.mjs @@ -0,0 +1,46 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Expand alphabet range operation + */ +class ExpandAlphabetRange extends Operation { + + /** + * ExpandAlphabetRange constructor + */ + constructor() { + super(); + + this.name = "Expand alphabet range"; + this.module = "Default"; + this.description = "Expand an alphabet range string into a list of the characters in that range.

e.g. a-z becomes abcdefghijklmnopqrstuvwxyz."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "binaryString", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return Utils.expandAlphRange(input).join(args[0]); + } + +} + +export default ExpandAlphabetRange; diff --git a/src/core/operations/Extract.js b/src/core/operations/Extract.js deleted file mode 100755 index 080da27f..00000000 --- a/src/core/operations/Extract.js +++ /dev/null @@ -1,299 +0,0 @@ -/** - * Identifier extraction operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Extract = { - - /** - * Runs search operations across the input data using regular expressions. - * - * @private - * @param {string} input - * @param {RegExp} searchRegex - * @param {RegExp} removeRegex - A regular expression defining results to remove from the - * final list - * @param {boolean} includeTotal - Whether or not to include the total number of results - * @returns {string} - */ - _search: function(input, searchRegex, removeRegex, includeTotal) { - let output = "", - total = 0, - match; - - while ((match = searchRegex.exec(input))) { - if (removeRegex && removeRegex.test(match[0])) - continue; - total++; - output += match[0] + "\n"; - } - - if (includeTotal) - output = "Total found: " + total + "\n\n" + output; - - return output; - }, - - - /** - * @constant - * @default - */ - MIN_STRING_LEN: 3, - /** - * @constant - * @default - */ - DISPLAY_TOTAL: false, - - /** - * Strings operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runStrings: function(input, args) { - let minLen = args[0] || Extract.MIN_STRING_LEN, - displayTotal = args[1], - strings = "[A-Z\\d/\\-:.,_$%'\"()<>= !\\[\\]{}@]", - regex = new RegExp(strings + "{" + minLen + ",}", "ig"); - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * @constant - * @default - */ - INCLUDE_IPV4: true, - /** - * @constant - * @default - */ - INCLUDE_IPV6: false, - /** - * @constant - * @default - */ - REMOVE_LOCAL: false, - - /** - * Extract IP addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runIp: function(input, args) { - let includeIpv4 = args[0], - includeIpv6 = args[1], - removeLocal = args[2], - displayTotal = args[3], - ipv4 = "(?:(?:\\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})?", - ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})", - ips = ""; - - if (includeIpv4 && includeIpv6) { - ips = ipv4 + "|" + ipv6; - } else if (includeIpv4) { - ips = ipv4; - } else if (includeIpv6) { - ips = ipv6; - } - - if (ips) { - const regex = new RegExp(ips, "ig"); - - if (removeLocal) { - let ten = "10\\..+", - oneninetwo = "192\\.168\\..+", - oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+", - onetwoseven = "127\\..+", - removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo + - "|" + oneseventwo + "|" + onetwoseven + ")"); - - return Extract._search(input, regex, removeRegex, displayTotal); - } else { - return Extract._search(input, regex, null, displayTotal); - } - } else { - return ""; - } - }, - - - /** - * Extract email addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runEmail: function(input, args) { - let displayTotal = args[0], - regex = /\w[-.\w]*@[-\w]+(?:\.[-\w]+)*\.[A-Z]{2,4}/ig; - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * Extract MAC addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runMac: function(input, args) { - let displayTotal = args[0], - regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig; - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * Extract URLs operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runUrls: function(input, args) { - let displayTotal = args[0], - protocol = "[A-Z]+://", - hostname = "[-\\w]+(?:\\.\\w[-\\w]*)+", - port = ":\\d+", - path = "/[^.!,?;\"'<>()\\[\\]{}\\s\\x7F-\\xFF]*"; - - path += "(?:[.!,?]+[^.!,?;\"'<>()\\[\\]{}\\s\\x7F-\\xFF]+)*"; - const regex = new RegExp(protocol + hostname + "(?:" + port + - ")?(?:" + path + ")?", "ig"); - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * Extract domains operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDomains: function(input, args) { - let displayTotal = args[0], - protocol = "https?://", - hostname = "[-\\w\\.]+", - tld = "\\.(?:com|net|org|biz|info|co|uk|onion|int|mobi|name|edu|gov|mil|eu|ac|ae|af|de|ca|ch|cn|cy|es|gb|hk|il|in|io|tv|me|nl|no|nz|ro|ru|tr|us|az|ir|kz|uz|pk)+", - regex = new RegExp("(?:" + protocol + ")?" + hostname + tld, "ig"); - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * @constant - * @default - */ - INCLUDE_WIN_PATH: true, - /** - * @constant - * @default - */ - INCLUDE_UNIX_PATH: true, - - /** - * Extract file paths operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFilePaths: function(input, args) { - let includeWinPath = args[0], - includeUnixPath = args[1], - displayTotal = args[2], - winDrive = "[A-Z]:\\\\", - winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)]{0,61}", - winExt = "[A-Z\\d]{1,6}", - winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName + - "(?:\\." + winExt + ")?", - unixPath = "(?:/[A-Z\\d.][A-Z\\d\\-.]{0,61})+", - filePaths = ""; - - if (includeWinPath && includeUnixPath) { - filePaths = winPath + "|" + unixPath; - } else if (includeWinPath) { - filePaths = winPath; - } else if (includeUnixPath) { - filePaths = unixPath; - } - - if (filePaths) { - const regex = new RegExp(filePaths, "ig"); - return Extract._search(input, regex, null, displayTotal); - } else { - return ""; - } - }, - - - /** - * Extract dates operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDates: function(input, args) { - let displayTotal = args[0], - date1 = "(?:19|20)\\d\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])", // yyyy-mm-dd - date2 = "(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\d\\d", // dd/mm/yyyy - date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy - regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig"); - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * Extract all identifiers operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAllIdents: function(input, args) { - let output = ""; - output += "IP addresses\n"; - output += Extract.runIp(input, [true, true, false]); - - output += "\nEmail addresses\n"; - output += Extract.runEmail(input, []); - - output += "\nMAC addresses\n"; - output += Extract.runMac(input, []); - - output += "\nURLs\n"; - output += Extract.runUrls(input, []); - - output += "\nDomain names\n"; - output += Extract.runDomains(input, []); - - output += "\nFile paths\n"; - output += Extract.runFilePaths(input, [true, true]); - - output += "\nDates\n"; - output += Extract.runDates(input, []); - return output; - }, - -}; - -export default Extract; diff --git a/src/core/operations/ExtractDates.mjs b/src/core/operations/ExtractDates.mjs new file mode 100644 index 00000000..530db194 --- /dev/null +++ b/src/core/operations/ExtractDates.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract dates operation + */ +class ExtractDates extends Operation { + + /** + * ExtractDates constructor + */ + constructor() { + super(); + + this.name = "Extract dates"; + this.module = "Regex"; + this.description = "Extracts dates in the following formats
  • yyyy-mm-dd
  • dd/mm/yyyy
  • mm/dd/yyyy
Dividers can be any of /, -, . or space"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + date1 = "(?:19|20)\\d\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])", // yyyy-mm-dd + date2 = "(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\d\\d", // dd/mm/yyyy + date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy + regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig"); + + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractDates; diff --git a/src/core/operations/ExtractDomains.mjs b/src/core/operations/ExtractDomains.mjs new file mode 100644 index 00000000..ddd7ca97 --- /dev/null +++ b/src/core/operations/ExtractDomains.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search, DOMAIN_REGEX } from "../lib/Extract"; + +/** + * Extract domains operation + */ +class ExtractDomains extends Operation { + + /** + * ExtractDomains constructor + */ + constructor() { + super(); + + this.name = "Extract domains"; + this.module = "Regex"; + this.description = "Extracts domain names.
Note that this will not include paths. Use Extract URLs to find entire URLs."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": "Extract.DISPLAY_TOTAL" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0]; + return search(input, DOMAIN_REGEX, null, displayTotal); + } + +} + +export default ExtractDomains; diff --git a/src/core/operations/ExtractEXIF.mjs b/src/core/operations/ExtractEXIF.mjs new file mode 100644 index 00000000..7a96d24a --- /dev/null +++ b/src/core/operations/ExtractEXIF.mjs @@ -0,0 +1,63 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import ExifParser from "exif-parser"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Extract EXIF operation + */ +class ExtractEXIF extends Operation { + + /** + * ExtractEXIF constructor + */ + constructor() { + super(); + + this.name = "Extract EXIF"; + this.module = "Image"; + this.description = [ + "Extracts EXIF data from an image.", + "

", + "EXIF data is metadata embedded in images (JPEG, JPG, TIFF) and audio files.", + "

", + "EXIF data from 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + try { + const parser = ExifParser.create(input); + const result = parser.parse(); + + const lines = []; + for (const tagName in result.tags) { + const value = result.tags[tagName]; + lines.push(`${tagName}: ${value}`); + } + + const numTags = lines.length; + lines.unshift(`Found ${numTags} tags.\n`); + return lines.join("\n"); + } catch (err) { + throw new OperationError(`Could not extract EXIF data from image: ${err}`); + } + } + +} + +export default ExtractEXIF; diff --git a/src/core/operations/ExtractEmailAddresses.mjs b/src/core/operations/ExtractEmailAddresses.mjs new file mode 100644 index 00000000..54ccf95b --- /dev/null +++ b/src/core/operations/ExtractEmailAddresses.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract email addresses operation + */ +class ExtractEmailAddresses extends Operation { + + /** + * ExtractEmailAddresses constructor + */ + constructor() { + super(); + + this.name = "Extract email addresses"; + this.module = "Regex"; + this.description = "Extracts all email addresses from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + // email regex from: https://www.regextester.com/98066 + regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/ig; + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractEmailAddresses; diff --git a/src/core/operations/ExtractFilePaths.mjs b/src/core/operations/ExtractFilePaths.mjs new file mode 100644 index 00000000..4b268192 --- /dev/null +++ b/src/core/operations/ExtractFilePaths.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract file paths operation + */ +class ExtractFilePaths extends Operation { + + /** + * ExtractFilePaths constructor + */ + constructor() { + super(); + + this.name = "Extract file paths"; + this.module = "Regex"; + this.description = "Extracts anything that looks like a Windows or UNIX file path.

Note that if UNIX is selected, there will likely be a lot of false positives."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Windows", + "type": "boolean", + "value": true + }, + { + "name": "UNIX", + "type": "boolean", + "value": true + }, + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [includeWinPath, includeUnixPath, displayTotal] = args, + winDrive = "[A-Z]:\\\\", + winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}", + winExt = "[A-Z\\d]{1,6}", + winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName + + "(?:\\." + winExt + ")?", + unixPath = "(?:/[A-Z\\d.][A-Z\\d\\-.]{0,61})+"; + let filePaths = ""; + + if (includeWinPath && includeUnixPath) { + filePaths = winPath + "|" + unixPath; + } else if (includeWinPath) { + filePaths = winPath; + } else if (includeUnixPath) { + filePaths = unixPath; + } + + if (filePaths) { + const regex = new RegExp(filePaths, "ig"); + return search(input, regex, null, displayTotal); + } else { + return ""; + } + } + +} + +export default ExtractFilePaths; diff --git a/src/core/operations/ExtractIPAddresses.mjs b/src/core/operations/ExtractIPAddresses.mjs new file mode 100644 index 00000000..1cca2098 --- /dev/null +++ b/src/core/operations/ExtractIPAddresses.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract IP addresses operation + */ +class ExtractIPAddresses extends Operation { + + /** + * ExtractIPAddresses constructor + */ + constructor() { + super(); + + this.name = "Extract IP addresses"; + this.module = "Regex"; + this.description = "Extracts all IPv4 and IPv6 addresses.

Warning: Given a string 710.65.0.456, this will match 10.65.0.45 so always check the original input!"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "IPv4", + "type": "boolean", + "value": true + }, + { + "name": "IPv6", + "type": "boolean", + "value": false + }, + { + "name": "Remove local IPv4 addresses", + "type": "boolean", + "value": false + }, + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [includeIpv4, includeIpv6, removeLocal, displayTotal] = args, + ipv4 = "(?:(?:\\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})?", + ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})"; + let ips = ""; + + if (includeIpv4 && includeIpv6) { + ips = ipv4 + "|" + ipv6; + } else if (includeIpv4) { + ips = ipv4; + } else if (includeIpv6) { + ips = ipv6; + } + + if (ips) { + const regex = new RegExp(ips, "ig"); + + if (removeLocal) { + const ten = "10\\..+", + oneninetwo = "192\\.168\\..+", + oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+", + onetwoseven = "127\\..+", + removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo + + "|" + oneseventwo + "|" + onetwoseven + ")"); + + return search(input, regex, removeRegex, displayTotal); + } else { + return search(input, regex, null, displayTotal); + } + } else { + return ""; + } + } + +} + +export default ExtractIPAddresses; diff --git a/src/core/operations/ExtractMACAddresses.mjs b/src/core/operations/ExtractMACAddresses.mjs new file mode 100644 index 00000000..9c3c2a5b --- /dev/null +++ b/src/core/operations/ExtractMACAddresses.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract MAC addresses operation + */ +class ExtractMACAddresses extends Operation { + + /** + * ExtractMACAddresses constructor + */ + constructor() { + super(); + + this.name = "Extract MAC addresses"; + this.module = "Regex"; + this.description = "Extracts all Media Access Control (MAC) addresses from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig; + + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractMACAddresses; diff --git a/src/core/operations/ExtractURLs.mjs b/src/core/operations/ExtractURLs.mjs new file mode 100644 index 00000000..5e76c6d9 --- /dev/null +++ b/src/core/operations/ExtractURLs.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search, URL_REGEX } from "../lib/Extract"; + +/** + * Extract URLs operation + */ +class ExtractURLs extends Operation { + + /** + * ExtractURLs constructor + */ + constructor() { + super(); + + this.name = "Extract URLs"; + this.module = "Regex"; + this.description = "Extracts Uniform Resource Locators (URLs) from the input. The protocol (http, ftp etc.) is required otherwise there will be far too many false positives."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0]; + return search(input, URL_REGEX, null, displayTotal); + } + +} + +export default ExtractURLs; diff --git a/src/core/operations/FileType.js b/src/core/operations/FileType.js deleted file mode 100755 index ad3e5ba7..00000000 --- a/src/core/operations/FileType.js +++ /dev/null @@ -1,530 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * File type operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const FileType = { - - /** - * Detect File Type operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runDetect: function(input, args) { - const type = FileType.magicType(input); - - if (!type) { - return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?"; - } else { - let output = "File extension: " + type.ext + "\n" + - "MIME type: " + type.mime; - - if (type.desc && type.desc.length) { - output += "\nDescription: " + type.desc; - } - - return output; - } - }, - - - /** - * @constant - * @default - */ - IGNORE_COMMON_BYTE_SEQUENCES: true, - - /** - * Scan for Embedded Files operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runScanForEmbeddedFiles: function(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", - type, - ignoreCommon = args[0], - commonExts = ["ico", "ttf", ""], - numFound = 0, - numCommonFound = 0; - - for (let i = 0; i < input.length; i++) { - type = FileType.magicType(input.slice(i)); - if (type) { - if (ignoreCommon && commonExts.indexOf(type.ext) > -1) { - numCommonFound++; - continue; - } - numFound++; - output += "\nOffset " + i + " (0x" + Utils.hex(i) + "):\n" + - " File extension: " + type.ext + "\n" + - " MIME type: " + type.mime + "\n"; - - if (type.desc && type.desc.length) { - output += " Description: " + type.desc + "\n"; - } - } - } - - if (numFound === 0) { - output += "\nNo embedded files were found."; - } - - if (numCommonFound > 0) { - output += "\n\n" + numCommonFound; - output += numCommonFound === 1 ? - " file type was detected that has a common byte sequence. This is likely to be a false positive." : - " file types were detected that have common byte sequences. These are likely to be false positives."; - output += " Run this operation with the 'Ignore common byte sequences' option unchecked to see details."; - } - - return output; - }, - - - /** - * Given a buffer, detects magic byte sequences at specific positions and returns the - * extension and mime type. - * - * @param {byteArray} buf - * @returns {Object} type - * @returns {string} type.ext - File extension - * @returns {string} type.mime - Mime type - * @returns {string} [type.desc] - Description - */ - magicType: function (buf) { - if (!(buf && buf.length > 1)) { - return null; - } - - if (buf[0] === 0xFF && buf[1] === 0xD8 && buf[2] === 0xFF) { - return { - ext: "jpg", - mime: "image/jpeg" - }; - } - - if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4E && buf[3] === 0x47) { - return { - ext: "png", - mime: "image/png" - }; - } - - if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) { - return { - ext: "gif", - mime: "image/gif" - }; - } - - if (buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) { - return { - ext: "webp", - mime: "image/webp" - }; - } - - // needs to be before `tif` check - if (((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) && buf[8] === 0x43 && buf[9] === 0x52) { - return { - ext: "cr2", - mime: "image/x-canon-cr2" - }; - } - - if ((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) { - return { - ext: "tif", - mime: "image/tiff" - }; - } - - if (buf[0] === 0x42 && buf[1] === 0x4D) { - return { - ext: "bmp", - mime: "image/bmp" - }; - } - - if (buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0xBC) { - return { - ext: "jxr", - mime: "image/vnd.ms-photo" - }; - } - - if (buf[0] === 0x38 && buf[1] === 0x42 && buf[2] === 0x50 && buf[3] === 0x53) { - return { - ext: "psd", - mime: "image/vnd.adobe.photoshop" - }; - } - - // needs to be before `zip` check - if (buf[0] === 0x50 && buf[1] === 0x4B && buf[2] === 0x3 && buf[3] === 0x4 && buf[30] === 0x6D && buf[31] === 0x69 && buf[32] === 0x6D && buf[33] === 0x65 && buf[34] === 0x74 && buf[35] === 0x79 && buf[36] === 0x70 && buf[37] === 0x65 && buf[38] === 0x61 && buf[39] === 0x70 && buf[40] === 0x70 && buf[41] === 0x6C && buf[42] === 0x69 && buf[43] === 0x63 && buf[44] === 0x61 && buf[45] === 0x74 && buf[46] === 0x69 && buf[47] === 0x6F && buf[48] === 0x6E && buf[49] === 0x2F && buf[50] === 0x65 && buf[51] === 0x70 && buf[52] === 0x75 && buf[53] === 0x62 && buf[54] === 0x2B && buf[55] === 0x7A && buf[56] === 0x69 && buf[57] === 0x70) { - return { - ext: "epub", - mime: "application/epub+zip" - }; - } - - if (buf[0] === 0x50 && buf[1] === 0x4B && (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) && (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)) { - return { - ext: "zip", - mime: "application/zip" - }; - } - - if (buf[257] === 0x75 && buf[258] === 0x73 && buf[259] === 0x74 && buf[260] === 0x61 && buf[261] === 0x72) { - return { - ext: "tar", - mime: "application/x-tar" - }; - } - - if (buf[0] === 0x52 && buf[1] === 0x61 && buf[2] === 0x72 && buf[3] === 0x21 && buf[4] === 0x1A && buf[5] === 0x7 && (buf[6] === 0x0 || buf[6] === 0x1)) { - return { - ext: "rar", - mime: "application/x-rar-compressed" - }; - } - - if (buf[0] === 0x1F && buf[1] === 0x8B && buf[2] === 0x8) { - return { - ext: "gz", - mime: "application/gzip" - }; - } - - if (buf[0] === 0x42 && buf[1] === 0x5A && buf[2] === 0x68) { - return { - ext: "bz2", - mime: "application/x-bzip2" - }; - } - - if (buf[0] === 0x37 && buf[1] === 0x7A && buf[2] === 0xBC && buf[3] === 0xAF && buf[4] === 0x27 && buf[5] === 0x1C) { - return { - ext: "7z", - mime: "application/x-7z-compressed" - }; - } - - if (buf[0] === 0x78 && buf[1] === 0x01) { - return { - ext: "dmg", - mime: "application/x-apple-diskimage" - }; - } - - if ((buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && (buf[3] === 0x18 || buf[3] === 0x20) && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) || (buf[0] === 0x33 && buf[1] === 0x67 && buf[2] === 0x70 && buf[3] === 0x35) || (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x1C && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x6D && buf[9] === 0x70 && buf[10] === 0x34 && buf[11] === 0x32 && buf[16] === 0x6D && buf[17] === 0x70 && buf[18] === 0x34 && buf[19] === 0x31 && buf[20] === 0x6D && buf[21] === 0x70 && buf[22] === 0x34 && buf[23] === 0x32 && buf[24] === 0x69 && buf[25] === 0x73 && buf[26] === 0x6F && buf[27] === 0x6D)) { - return { - ext: "mp4", - mime: "video/mp4" - }; - } - - if ((buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x1C && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x4D && buf[9] === 0x34 && buf[10] === 0x56)) { - return { - ext: "m4v", - mime: "video/x-m4v" - }; - } - - if (buf[0] === 0x4D && buf[1] === 0x54 && buf[2] === 0x68 && buf[3] === 0x64) { - return { - ext: "mid", - mime: "audio/midi" - }; - } - - // needs to be before the `webm` check - if (buf[31] === 0x6D && buf[32] === 0x61 && buf[33] === 0x74 && buf[34] === 0x72 && buf[35] === 0x6f && buf[36] === 0x73 && buf[37] === 0x6B && buf[38] === 0x61) { - return { - ext: "mkv", - mime: "video/x-matroska" - }; - } - - if (buf[0] === 0x1A && buf[1] === 0x45 && buf[2] === 0xDF && buf[3] === 0xA3) { - return { - ext: "webm", - mime: "video/webm" - }; - } - - if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x14 && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) { - return { - ext: "mov", - mime: "video/quicktime" - }; - } - - if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x41 && buf[9] === 0x56 && buf[10] === 0x49) { - return { - ext: "avi", - mime: "video/x-msvideo" - }; - } - - if (buf[0] === 0x30 && buf[1] === 0x26 && buf[2] === 0xB2 && buf[3] === 0x75 && buf[4] === 0x8E && buf[5] === 0x66 && buf[6] === 0xCF && buf[7] === 0x11 && buf[8] === 0xA6 && buf[9] === 0xD9) { - return { - ext: "wmv", - mime: "video/x-ms-wmv" - }; - } - - if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x1 && buf[3].toString(16)[0] === "b") { - return { - ext: "mpg", - mime: "video/mpeg" - }; - } - - if ((buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) || (buf[0] === 0xFF && buf[1] === 0xfb)) { - return { - ext: "mp3", - mime: "audio/mpeg" - }; - } - - if ((buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x4D && buf[9] === 0x34 && buf[10] === 0x41) || (buf[0] === 0x4D && buf[1] === 0x34 && buf[2] === 0x41 && buf[3] === 0x20)) { - return { - ext: "m4a", - mime: "audio/m4a" - }; - } - - if (buf[0] === 0x4F && buf[1] === 0x67 && buf[2] === 0x67 && buf[3] === 0x53) { - return { - ext: "ogg", - mime: "audio/ogg" - }; - } - - if (buf[0] === 0x66 && buf[1] === 0x4C && buf[2] === 0x61 && buf[3] === 0x43) { - return { - ext: "flac", - mime: "audio/x-flac" - }; - } - - if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45) { - return { - ext: "wav", - mime: "audio/x-wav" - }; - } - - if (buf[0] === 0x23 && buf[1] === 0x21 && buf[2] === 0x41 && buf[3] === 0x4D && buf[4] === 0x52 && buf[5] === 0x0A) { - return { - ext: "amr", - mime: "audio/amr" - }; - } - - if (buf[0] === 0x25 && buf[1] === 0x50 && buf[2] === 0x44 && buf[3] === 0x46) { - return { - ext: "pdf", - mime: "application/pdf" - }; - } - - if (buf[0] === 0x4D && buf[1] === 0x5A) { - return { - ext: "exe", - mime: "application/x-msdownload" - }; - } - - if ((buf[0] === 0x43 || buf[0] === 0x46) && buf[1] === 0x57 && buf[2] === 0x53) { - return { - ext: "swf", - mime: "application/x-shockwave-flash" - }; - } - - if (buf[0] === 0x7B && buf[1] === 0x5C && buf[2] === 0x72 && buf[3] === 0x74 && buf[4] === 0x66) { - return { - ext: "rtf", - mime: "application/rtf" - }; - } - - if (buf[0] === 0x77 && buf[1] === 0x4F && buf[2] === 0x46 && buf[3] === 0x46 && buf[4] === 0x00 && buf[5] === 0x01 && buf[6] === 0x00 && buf[7] === 0x00) { - return { - ext: "woff", - mime: "application/font-woff" - }; - } - - if (buf[0] === 0x77 && buf[1] === 0x4F && buf[2] === 0x46 && buf[3] === 0x32 && buf[4] === 0x00 && buf[5] === 0x01 && buf[6] === 0x00 && buf[7] === 0x00) { - return { - ext: "woff2", - mime: "application/font-woff" - }; - } - - if (buf[34] === 0x4C && buf[35] === 0x50 && ((buf[8] === 0x02 && buf[9] === 0x00 && buf[10] === 0x01) || (buf[8] === 0x01 && buf[9] === 0x00 && buf[10] === 0x00) || (buf[8] === 0x02 && buf[9] === 0x00 && buf[10] === 0x02))) { - return { - ext: "eot", - mime: "application/octet-stream" - }; - } - - if (buf[0] === 0x00 && buf[1] === 0x01 && buf[2] === 0x00 && buf[3] === 0x00 && buf[4] === 0x00) { - return { - ext: "ttf", - mime: "application/font-sfnt" - }; - } - - if (buf[0] === 0x4F && buf[1] === 0x54 && buf[2] === 0x54 && buf[3] === 0x4F && buf[4] === 0x00) { - return { - ext: "otf", - mime: "application/font-sfnt" - }; - } - - if (buf[0] === 0x00 && buf[1] === 0x00 && buf[2] === 0x01 && buf[3] === 0x00) { - return { - ext: "ico", - mime: "image/x-icon" - }; - } - - if (buf[0] === 0x46 && buf[1] === 0x4C && buf[2] === 0x56 && buf[3] === 0x01) { - return { - ext: "flv", - mime: "video/x-flv" - }; - } - - if (buf[0] === 0x25 && buf[1] === 0x21) { - return { - ext: "ps", - mime: "application/postscript" - }; - } - - if (buf[0] === 0xFD && buf[1] === 0x37 && buf[2] === 0x7A && buf[3] === 0x58 && buf[4] === 0x5A && buf[5] === 0x00) { - return { - ext: "xz", - mime: "application/x-xz" - }; - } - - if (buf[0] === 0x53 && buf[1] === 0x51 && buf[2] === 0x4C && buf[3] === 0x69) { - return { - ext: "sqlite", - mime: "application/x-sqlite3" - }; - } - - // Added by n1474335 [n1474335@gmail.com] from here on - // ################################################################## // - if ((buf[0] === 0x1F && buf[1] === 0x9D) || (buf[0] === 0x1F && buf[1] === 0xA0)) { - return { - ext: "z, tar.z", - mime: "application/x-gtar" - }; - } - - if (buf[0] === 0x7F && buf[1] === 0x45 && buf[2] === 0x4C && buf[3] === 0x46) { - return { - ext: "none, axf, bin, elf, o, prx, puff, so", - mime: "application/x-executable", - desc: "Executable and Linkable Format file. No standard file extension." - }; - } - - if (buf[0] === 0xCA && buf[1] === 0xFE && buf[2] === 0xBA && buf[3] === 0xBE) { - return { - ext: "class", - mime: "application/java-vm" - }; - } - - if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) { - return { - ext: "txt", - mime: "text/plain", - desc: "UTF-8 encoded Unicode byte order mark detected, commonly but not exclusively seen in text files." - }; - } - - // Must be before Little-endian UTF-16 BOM - if (buf[0] === 0xFF && buf[1] === 0xFE && buf[2] === 0x00 && buf[3] === 0x00) { - return { - ext: "", - mime: "", - desc: "Little-endian UTF-32 encoded Unicode byte order mark detected." - }; - } - - if (buf[0] === 0xFF && buf[1] === 0xFE) { - return { - ext: "", - mime: "", - desc: "Little-endian UTF-16 encoded Unicode byte order mark detected." - }; - } - - if ((buf[0x8001] === 0x43 && buf[0x8002] === 0x44 && buf[0x8003] === 0x30 && buf[0x8004] === 0x30 && buf[0x8005] === 0x31) || - (buf[0x8801] === 0x43 && buf[0x8802] === 0x44 && buf[0x8803] === 0x30 && buf[0x8804] === 0x30 && buf[0x8805] === 0x31) || - (buf[0x9001] === 0x43 && buf[0x9002] === 0x44 && buf[0x9003] === 0x30 && buf[0x9004] === 0x30 && buf[0x9005] === 0x31)) { - return { - ext: "iso", - mime: "application/octet-stream", - desc: "ISO 9660 CD/DVD image file" - }; - } - - if (buf[0] === 0xD0 && buf[1] === 0xCF && buf[2] === 0x11 && buf[3] === 0xE0 && buf[4] === 0xA1 && buf[5] === 0xB1 && buf[6] === 0x1A && buf[7] === 0xE1) { - return { - ext: "doc, xls, ppt", - mime: "application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint", - desc: "Microsoft Office documents" - }; - } - - if (buf[0] === 0x64 && buf[1] === 0x65 && buf[2] === 0x78 && buf[3] === 0x0A && buf[4] === 0x30 && buf[5] === 0x33 && buf[6] === 0x35 && buf[7] === 0x00) { - return { - ext: "dex", - mime: "application/octet-stream", - desc: "Dalvik Executable (Android)" - }; - } - - if (buf[0] === 0x4B && buf[1] === 0x44 && buf[2] === 0x4D) { - return { - ext: "vmdk", - mime: "application/vmdk, application/x-virtualbox-vmdk" - }; - } - - if (buf[0] === 0x43 && buf[1] === 0x72 && buf[2] === 0x32 && buf[3] === 0x34) { - return { - ext: "crx", - mime: "application/crx", - desc: "Google Chrome extension or packaged app" - }; - } - - return null; - }, - -}; - -export default FileType; diff --git a/src/core/operations/Filter.mjs b/src/core/operations/Filter.mjs new file mode 100644 index 00000000..64a97279 --- /dev/null +++ b/src/core/operations/Filter.mjs @@ -0,0 +1,73 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; +import OperationError from "../errors/OperationError"; +import XRegExp from "xregexp"; + +/** + * Filter operation + */ +class Filter extends Operation { + + /** + * Filter constructor + */ + constructor() { + super(); + + this.name = "Filter"; + this.module = "Regex"; + this.description = "Splits up the input using the specified delimiter and then filters each branch based on a regular expression."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "Regex", + "type": "string", + "value": "" + }, + { + "name": "Invert condition", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + reverse = args[2]; + let regex; + + try { + regex = new XRegExp(args[1]); + } catch (err) { + throw new OperationError(`Invalid regex. Details: ${err.message}`); + } + + const regexFilter = function(value) { + return reverse ^ regex.test(value); + }; + + return input.split(delim).filter(regexFilter).join(delim); + } + +} + +export default Filter; diff --git a/src/core/operations/FindReplace.mjs b/src/core/operations/FindReplace.mjs new file mode 100644 index 00000000..af75fcb0 --- /dev/null +++ b/src/core/operations/FindReplace.mjs @@ -0,0 +1,94 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import XRegExp from "xregexp"; + +/** + * Find / Replace operation + */ +class FindReplace extends Operation { + + /** + * FindReplace constructor + */ + constructor() { + super(); + + this.name = "Find / Replace"; + this.module = "Regex"; + this.description = "Replaces all occurrences of the first string with the second.

Includes support for regular expressions (regex), simple strings and extended strings (which support \\n, \\r, \\t, \\b, \\f and escaped hex bytes using \\x notation, e.g. \\x00 for a null byte)."; + this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Find", + "type": "toggleString", + "value": "", + "toggleValues": ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"] + }, + { + "name": "Replace", + "type": "binaryString", + "value": "" + }, + { + "name": "Global match", + "type": "boolean", + "value": true + }, + { + "name": "Case insensitive", + "type": "boolean", + "value": false + }, + { + "name": "Multiline matching", + "type": "boolean", + "value": true + }, + { + "name": "Dot matches all", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [{option: type}, replace, g, i, m, s] = args; + let find = args[0].string, + modifiers = ""; + + if (g) modifiers += "g"; + if (i) modifiers += "i"; + if (m) modifiers += "m"; + if (s) modifiers += "s"; + + if (type === "Regex") { + find = new XRegExp(find, modifiers); + return input.replace(find, replace); + } + + if (type.indexOf("Extended") === 0) { + find = Utils.parseEscapedChars(find); + } + + find = new XRegExp(Utils.escapeRegex(find), modifiers); + + return input.replace(find, replace); + } + +} + +export default FindReplace; diff --git a/src/core/operations/Fletcher16Checksum.mjs b/src/core/operations/Fletcher16Checksum.mjs new file mode 100644 index 00000000..d2331823 --- /dev/null +++ b/src/core/operations/Fletcher16Checksum.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Fletcher-16 Checksum operation + */ +class Fletcher16Checksum extends Operation { + + /** + * Fletcher16Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-16 Checksum"; + 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.

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.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xff; + b = (b + a) % 0xff; + } + + return Utils.hex(((b << 8) | a) >>> 0, 4); + } + +} + +export default Fletcher16Checksum; diff --git a/src/core/operations/Fletcher32Checksum.mjs b/src/core/operations/Fletcher32Checksum.mjs new file mode 100644 index 00000000..30f4bc69 --- /dev/null +++ b/src/core/operations/Fletcher32Checksum.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Fletcher-32 Checksum operation + */ +class Fletcher32Checksum extends Operation { + + /** + * Fletcher32Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-32 Checksum"; + 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.

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.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xffff; + b = (b + a) % 0xffff; + } + + return Utils.hex(((b << 16) | a) >>> 0, 8); + } + +} + +export default Fletcher32Checksum; diff --git a/src/core/operations/Fletcher64Checksum.mjs b/src/core/operations/Fletcher64Checksum.mjs new file mode 100644 index 00000000..56afe42e --- /dev/null +++ b/src/core/operations/Fletcher64Checksum.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Fletcher-64 Checksum operation + */ +class Fletcher64Checksum extends Operation { + + /** + * Fletcher64Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-64 Checksum"; + 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.

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.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xffffffff; + b = (b + a) % 0xffffffff; + } + + return Utils.hex(b >>> 0, 8) + Utils.hex(a >>> 0, 8); + } + +} + +export default Fletcher64Checksum; diff --git a/src/core/operations/Fletcher8Checksum.mjs b/src/core/operations/Fletcher8Checksum.mjs new file mode 100644 index 00000000..a6dc0e4d --- /dev/null +++ b/src/core/operations/Fletcher8Checksum.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Fletcher-8 Checksum operation + */ +class Fletcher8Checksum extends Operation { + + /** + * Fletcher8Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-8 Checksum"; + 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.

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.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xf; + b = (b + a) % 0xf; + } + + return Utils.hex(((b << 4) | a) >>> 0, 2); + } + +} + +export default Fletcher8Checksum; diff --git a/src/core/operations/Fork.mjs b/src/core/operations/Fork.mjs new file mode 100644 index 00000000..27a1af96 --- /dev/null +++ b/src/core/operations/Fork.mjs @@ -0,0 +1,117 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Recipe from "../Recipe"; +import Dish from "../Dish"; + +/** + * Fork operation + */ +class Fork extends Operation { + + /** + * Fork constructor + */ + constructor() { + super(); + + this.name = "Fork"; + this.flowControl = true; + this.module = "Default"; + this.description = "Split the input data up based on the specified delimiter and run all subsequent operations on each branch separately.

For example, to decode multiple Base64 strings, enter them all on separate lines then add the 'Fork' and 'From Base64' operations to the recipe. Each string will be decoded separately."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Split delimiter", + "type": "binaryShortString", + "value": "\\n" + }, + { + "name": "Merge delimiter", + "type": "binaryShortString", + "value": "\\n" + }, + { + "name": "Ignore errors", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + async run(state) { + const opList = state.opList, + inputType = opList[state.progress].inputType, + outputType = opList[state.progress].outputType, + input = await state.dish.get(inputType), + ings = opList[state.progress].ingValues, + [splitDelim, mergeDelim, ignoreErrors] = ings, + subOpList = []; + let inputs = [], + i; + + if (input) + inputs = input.split(splitDelim); + + // Create subOpList for each tranche to operate on + // (all remaining operations unless we encounter a Merge) + for (i = state.progress + 1; i < opList.length; i++) { + if (opList[i].name === "Merge" && !opList[i].disabled) { + break; + } else { + subOpList.push(opList[i]); + } + } + + const recipe = new Recipe(); + let output = "", + progress = 0; + + state.forkOffset += state.progress + 1; + + recipe.addOperations(subOpList); + + // Take a deep(ish) copy of the ingredient values + const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues))); + + // Run recipe over each tranche + for (i = 0; i < inputs.length; i++) { + // Baseline ing values for each tranche so that registers are reset + subOpList.forEach((op, i) => { + op.ingValues = JSON.parse(JSON.stringify(ingValues[i])); + }); + + const dish = new Dish(); + dish.set(inputs[i], inputType); + + try { + progress = await recipe.execute(dish, 0, state); + } catch (err) { + if (!ignoreErrors) { + throw err; + } + progress = err.progress + 1; + } + output += await dish.get(outputType) + mergeDelim; + } + + state.dish.set(output, outputType); + state.progress += progress; + return state; + } + +} + +export default Fork; diff --git a/src/core/operations/FormatMACAddresses.mjs b/src/core/operations/FormatMACAddresses.mjs new file mode 100644 index 00000000..1002877d --- /dev/null +++ b/src/core/operations/FormatMACAddresses.mjs @@ -0,0 +1,122 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Format MAC addresses operation + */ +class FormatMACAddresses extends Operation { + + /** + * FormatMACAddresses constructor + */ + constructor() { + super(); + + this.name = "Format MAC addresses"; + this.module = "Default"; + this.description = "Displays given MAC addresses in multiple different formats.

Expects addresses in a list separated by newlines, spaces or commas.

WARNING: There are no validity checks."; + this.infoURL = "https://wikipedia.org/wiki/MAC_address#Notational_conventions"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Output case", + "type": "option", + "value": ["Both", "Upper only", "Lower only"] + }, + { + "name": "No delimiter", + "type": "boolean", + "value": true + }, + { + "name": "Dash delimiter", + "type": "boolean", + "value": true + }, + { + "name": "Colon delimiter", + "type": "boolean", + "value": true + }, + { + "name": "Cisco style", + "type": "boolean", + "value": false + }, + { + "name": "IPv6 interface ID", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + + const [ + outputCase, + noDelim, + dashDelim, + colonDelim, + ciscoStyle, + ipv6IntID + ] = args, + outputList = [], + macs = input.toLowerCase().split(/[,\s\r\n]+/); + + macs.forEach(function(mac) { + const cleanMac = mac.replace(/[:.-]+/g, ""), + macHyphen = cleanMac.replace(/(.{2}(?=.))/g, "$1-"), + macColon = cleanMac.replace(/(.{2}(?=.))/g, "$1:"), + macCisco = cleanMac.replace(/(.{4}(?=.))/g, "$1."); + let macIPv6 = cleanMac.slice(0, 6) + "fffe" + cleanMac.slice(6); + + macIPv6 = macIPv6.replace(/(.{4}(?=.))/g, "$1:"); + let bite = parseInt(macIPv6.slice(0, 2), 16) ^ 2; + bite = bite.toString(16).padStart(2, "0"); + macIPv6 = bite + macIPv6.slice(2); + + if (outputCase === "Lower only") { + if (noDelim) outputList.push(cleanMac); + if (dashDelim) outputList.push(macHyphen); + if (colonDelim) outputList.push(macColon); + if (ciscoStyle) outputList.push(macCisco); + if (ipv6IntID) outputList.push(macIPv6); + } else if (outputCase === "Upper only") { + if (noDelim) outputList.push(cleanMac.toUpperCase()); + if (dashDelim) outputList.push(macHyphen.toUpperCase()); + if (colonDelim) outputList.push(macColon.toUpperCase()); + if (ciscoStyle) outputList.push(macCisco.toUpperCase()); + if (ipv6IntID) outputList.push(macIPv6.toUpperCase()); + } else { + if (noDelim) outputList.push(cleanMac, cleanMac.toUpperCase()); + if (dashDelim) outputList.push(macHyphen, macHyphen.toUpperCase()); + if (colonDelim) outputList.push(macColon, macColon.toUpperCase()); + if (ciscoStyle) outputList.push(macCisco, macCisco.toUpperCase()); + if (ipv6IntID) outputList.push(macIPv6, macIPv6.toUpperCase()); + } + + outputList.push( + "" // Empty line to delimit groups + ); + }); + + // Return the data as a string + return outputList.join("\n"); + } + +} + +export default FormatMACAddresses; diff --git a/src/core/operations/FrequencyDistribution.mjs b/src/core/operations/FrequencyDistribution.mjs new file mode 100644 index 00000000..13eb53e1 --- /dev/null +++ b/src/core/operations/FrequencyDistribution.mjs @@ -0,0 +1,111 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; + +/** + * Frequency distribution operation + */ +class FrequencyDistribution extends Operation { + + /** + * FrequencyDistribution constructor + */ + constructor() { + super(); + + this.name = "Frequency distribution"; + this.module = "Default"; + this.description = "Displays the distribution of bytes in the data as a graph."; + this.infoURL = "https://wikipedia.org/wiki/Frequency_distribution"; + this.inputType = "ArrayBuffer"; + this.outputType = "json"; + this.presentType = "html"; + this.args = [ + { + "name": "Show 0%s", + "type": "boolean", + "value": "Entropy.FREQ_ZEROS" + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {json} + */ + run(input, args) { + const data = new Uint8Array(input); + if (!data.length) throw new OperationError("No data"); + + const distrib = new Array(256).fill(0), + percentages = new Array(256), + len = data.length; + let i; + + // Count bytes + for (i = 0; i < len; i++) { + distrib[data[i]]++; + } + + // Calculate percentages + let repr = 0; + for (i = 0; i < 256; i++) { + if (distrib[i] > 0) repr++; + percentages[i] = distrib[i] / len * 100; + } + + return { + "dataLength": len, + "percentages": percentages, + "distribution": distrib, + "bytesRepresented": repr + }; + } + + /** + * Displays the frequency distribution as a bar chart for web apps. + * + * @param {json} freq + * @returns {html} + */ + present(freq, args) { + const showZeroes = args[0]; + // Print + let output = `
+Total data length: ${freq.dataLength} +Number of bytes represented: ${freq.bytesRepresented} +Number of bytes not represented: ${256 - freq.bytesRepresented} + +Byte Percentage +`; + + for (let i = 0; i < 256; i++) { + if (freq.distribution[i] || showZeroes) { + output += " " + Utils.hex(i, 2) + " (" + + (freq.percentages[i].toFixed(2).replace(".00", "") + "%)").padEnd(8, " ") + + Array(Math.ceil(freq.percentages[i])+1).join("|") + "\n"; + } + } + + return output; + } + +} + +export default FrequencyDistribution; diff --git a/src/core/operations/FromBCD.mjs b/src/core/operations/FromBCD.mjs new file mode 100644 index 00000000..a87c1101 --- /dev/null +++ b/src/core/operations/FromBCD.mjs @@ -0,0 +1,123 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD"; +import BigNumber from "bignumber.js"; + +/** + * From BCD operation + */ +class FromBCD extends Operation { + + /** + * FromBCD constructor + */ + constructor() { + super(); + + this.name = "From BCD"; + this.module = "Default"; + this.description = "Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign."; + this.infoURL = "https://wikipedia.org/wiki/Binary-coded_decimal"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Scheme", + "type": "option", + "value": ENCODING_SCHEME + }, + { + "name": "Packed", + "type": "boolean", + "value": true + }, + { + "name": "Signed", + "type": "boolean", + "value": false + }, + { + "name": "Input format", + "type": "option", + "value": FORMAT + } + ]; + this.patterns = [ + { + match: "^(?:\\d{4} ){3,}\\d{4}$", + flags: "", + args: ["8 4 2 1", true, false, "Nibbles"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const encoding = ENCODING_LOOKUP[args[0]], + packed = args[1], + signed = args[2], + inputFormat = args[3], + nibbles = []; + + let output = "", + byteArray; + + // Normalise the input + switch (inputFormat) { + case "Nibbles": + case "Bytes": + input = input.replace(/\s/g, ""); + for (let i = 0; i < input.length; i += 4) { + nibbles.push(parseInt(input.substr(i, 4), 2)); + } + break; + case "Raw": + default: + byteArray = Utils.strToByteArray(input); + byteArray.forEach(b => { + nibbles.push(b >>> 4); + nibbles.push(b & 15); + }); + break; + } + + if (!packed) { + // Discard each high nibble + for (let i = 0; i < nibbles.length; i++) { + nibbles.splice(i, 1); + } + } + + if (signed) { + const sign = nibbles.pop(); + if (sign === 13 || + sign === 11) { + // Negative + output += "-"; + } + } + + nibbles.forEach(n => { + if (isNaN(n)) throw new OperationError("Invalid input"); + const val = encoding.indexOf(n); + if (val < 0) throw new OperationError(`Value ${Utils.bin(n, 4)} is not in the encoding scheme`); + output += val.toString(); + }); + + return new BigNumber(output); + } + +} + +export default FromBCD; diff --git a/src/core/operations/FromBase.mjs b/src/core/operations/FromBase.mjs new file mode 100644 index 00000000..a44eaf60 --- /dev/null +++ b/src/core/operations/FromBase.mjs @@ -0,0 +1,64 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError"; + +/** + * From Base operation + */ +class FromBase extends Operation { + + /** + * FromBase constructor + */ + constructor() { + super(); + + this.name = "From Base"; + this.module = "Default"; + this.description = "Converts a number to decimal from a given numerical base."; + this.infoURL = "https://wikipedia.org/wiki/Radix"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Radix", + "type": "number", + "value": 36 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const radix = args[0]; + if (radix < 2 || radix > 36) { + throw new OperationError("Error: Radix argument must be between 2 and 36"); + } + + const number = input.replace(/\s/g, "").split("."); + let result = new BigNumber(number[0], radix) || 0; + + if (number.length === 1) return result; + + // Fractional part + for (let i = 0; i < number[1].length; i++) { + const digit = new BigNumber(number[1][i], radix); + result += digit.div(Math.pow(radix, i+1)); + } + + return result; + } + +} + +export default FromBase; diff --git a/src/core/operations/FromBase32.mjs b/src/core/operations/FromBase32.mjs new file mode 100644 index 00000000..3518ec5f --- /dev/null +++ b/src/core/operations/FromBase32.mjs @@ -0,0 +1,98 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * From Base32 operation + */ +class FromBase32 extends Operation { + + /** + * FromBase32 constructor + */ + constructor() { + super(); + + this.name = "From Base32"; + 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 = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Alphabet", + type: "binaryString", + value: "A-Z2-7=" + }, + { + name: "Remove non-alphabet chars", + type: "boolean", + value: true + } + ]; + this.patterns = [ + { + match: "^(?:[A-Z2-7]{8})+(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}={1})?$", + flags: "", + args: ["A-Z2-7=", false] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (!input) return []; + + const alphabet = args[0] ? + Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=", + removeNonAlphChars = args[1], + output = []; + + let chr1, chr2, chr3, chr4, chr5, + enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8, + i = 0; + + if (removeNonAlphChars) { + const re = new RegExp("[^" + alphabet.replace(/[\]\\\-^]/g, "\\$&") + "]", "g"); + input = input.replace(re, ""); + } + + while (i < input.length) { + enc1 = alphabet.indexOf(input.charAt(i++)); + enc2 = alphabet.indexOf(input.charAt(i++) || "="); + enc3 = alphabet.indexOf(input.charAt(i++) || "="); + enc4 = alphabet.indexOf(input.charAt(i++) || "="); + enc5 = alphabet.indexOf(input.charAt(i++) || "="); + enc6 = alphabet.indexOf(input.charAt(i++) || "="); + enc7 = alphabet.indexOf(input.charAt(i++) || "="); + enc8 = alphabet.indexOf(input.charAt(i++) || "="); + + chr1 = (enc1 << 3) | (enc2 >> 2); + chr2 = ((enc2 & 3) << 6) | (enc3 << 1) | (enc4 >> 4); + chr3 = ((enc4 & 15) << 4) | (enc5 >> 1); + chr4 = ((enc5 & 1) << 7) | (enc6 << 2) | (enc7 >> 3); + chr5 = ((enc7 & 7) << 5) | enc8; + + output.push(chr1); + if (enc2 & 3 !== 0 || enc3 !== 32) output.push(chr2); + if (enc4 & 15 !== 0 || enc5 !== 32) output.push(chr3); + if (enc5 & 1 !== 0 || enc6 !== 32) output.push(chr4); + if (enc7 & 7 !== 0 || enc8 !== 32) output.push(chr5); + } + + return output; + } + +} + +export default FromBase32; diff --git a/src/core/operations/FromBase58.mjs b/src/core/operations/FromBase58.mjs new file mode 100644 index 00000000..8b0db19f --- /dev/null +++ b/src/core/operations/FromBase58.mjs @@ -0,0 +1,115 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {ALPHABET_OPTIONS} from "../lib/Base58"; + +/** + * From Base58 operation + */ +class FromBase58 extends Operation { + + /** + * FromBase58 constructor + */ + constructor() { + super(); + + this.name = "From Base58"; + 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.

This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included) back into its raw form.

e.g. StV1DL6CwTryKyV becomes hello world

Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc)."; + this.infoURL = "https://wikipedia.org/wiki/Base58"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Alphabet", + "type": "editableOption", + "value": ALPHABET_OPTIONS + }, + { + "name": "Remove non-alphabet chars", + "type": "boolean", + "value": true + } + ]; + this.patterns = [ + { + match: "^[1-9A-HJ-NP-Za-km-z]{20,}$", + flags: "", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", false] + }, + { + match: "^[1-9A-HJ-NP-Za-km-z]{20,}$", + flags: "", + args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", false] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + let alphabet = args[0] || ALPHABET_OPTIONS[0].value; + const removeNonAlphaChars = args[1] === undefined ? true : args[1], + result = [0]; + + alphabet = Utils.expandAlphRange(alphabet).join(""); + + if (alphabet.length !== 58 || + [].unique.call(alphabet).length !== 58) { + throw new OperationError("Alphabet must be of length 58"); + } + + if (input.length === 0) return []; + + let zeroPrefix = 0; + for (let i = 0; i < input.length && input[i] === alphabet[0]; i++) { + zeroPrefix++; + } + + [].forEach.call(input, function(c, charIndex) { + const index = alphabet.indexOf(c); + + if (index === -1) { + if (removeNonAlphaChars) { + return; + } else { + throw new OperationError(`Char '${c}' at position ${charIndex} not in alphabet`); + } + } + + let carry = result[0] * 58 + index; + result[0] = carry & 0xFF; + carry = carry >> 8; + + for (let i = 1; i < result.length; i++) { + carry += result[i] * 58; + result[i] = carry & 0xFF; + carry = carry >> 8; + } + + while (carry > 0) { + result.push(carry & 0xFF); + carry = carry >> 8; + } + }); + + while (zeroPrefix--) { + result.push(0); + } + + return result.reverse(); + } + +} + +export default FromBase58; diff --git a/src/core/operations/FromBase62.mjs b/src/core/operations/FromBase62.mjs new file mode 100644 index 00000000..525f2e2f --- /dev/null +++ b/src/core/operations/FromBase62.mjs @@ -0,0 +1,58 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import BigNumber from "bignumber.js"; +import Utils from "../Utils"; + + +/** + * From Base62 operation + */ +class FromBase62 extends Operation { + + /** + * FromBase62 constructor + */ + constructor() { + super(); + + this.name = "From Base62"; + 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 = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Alphabet", + type: "string", + value: "0-9A-Za-z" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (input.length < 1) return []; + const ALPHABET = Utils.expandAlphRange(args[0]).join(""); + const BN = BigNumber.clone({ ALPHABET }); + + const re = new RegExp("[^" + ALPHABET.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); + input = input.replace(re, ""); + + const number = new BN(input, 62); + + return Utils.convertToByteArray(number.toString(16), "Hex"); + } + +} + +export default FromBase62; diff --git a/src/core/operations/FromBase64.mjs b/src/core/operations/FromBase64.mjs new file mode 100644 index 00000000..be049802 --- /dev/null +++ b/src/core/operations/FromBase64.mjs @@ -0,0 +1,150 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {fromBase64, ALPHABET_OPTIONS} from "../lib/Base64"; + +/** + * From Base64 operation + */ +class FromBase64 extends Operation { + + /** + * FromBase64 constructor + */ + constructor() { + super(); + + this.name = "From Base64"; + this.module = "Default"; + this.description = "Base64 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.

This operation decodes data from an ASCII Base64 string back into its raw format.

e.g. aGVsbG8= becomes hello"; + this.infoURL = "https://wikipedia.org/wiki/Base64"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Alphabet", + type: "editableOption", + value: ALPHABET_OPTIONS + }, + { + name: "Remove non-alphabet chars", + type: "boolean", + value: true + } + ]; + this.patterns = [ + { + match: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["A-Za-z0-9+/=", true] + }, + { + match: "^\\s*[A-Z\\d\\-_]{20,}\\s*$", + flags: "i", + args: ["A-Za-z0-9-_", true] + }, + { + match: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$", + flags: "i", + args: ["A-Za-z0-9+\\-=", true] + }, + { + match: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$", + flags: "i", + args: ["./0-9A-Za-z=", true] + }, + { + match: "^\\s*[A-Z\\d_.]{20,}\\s*$", + flags: "i", + args: ["A-Za-z0-9_.", true] + }, + { + match: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$", + flags: "i", + args: ["A-Za-z0-9._-", true] + }, + { + match: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["0-9a-zA-Z+/=", true] + }, + { + match: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["0-9A-Za-z+/=", true] + }, + { + match: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$", + flags: "", + args: [" -_", false] + }, + { + match: "^\\s*[A-Z\\d+\\-]{20,}\\s*$", + flags: "i", + args: ["+\\-0-9A-Za-z", true] + }, + { + match: "^\\s*[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\s*$", + flags: "", + args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true] + }, + { + match: "^\\s*(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["N-ZA-Mn-za-m0-9+/=", true] + }, + { + match: "^\\s*[A-Z\\d./]{20,}\\s*$", + flags: "i", + args: ["./0-9A-Za-z", true] + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [alphabet, removeNonAlphChars] = args; + + return fromBase64(input, alphabet, "byteArray", removeNonAlphChars); + } + + /** + * Highlight to Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + pos[0].start = Math.ceil(pos[0].start / 4 * 3); + pos[0].end = Math.floor(pos[0].end / 4 * 3); + return pos; + } + + /** + * Highlight from Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + pos[0].start = Math.floor(pos[0].start / 3 * 4); + pos[0].end = Math.ceil(pos[0].end / 3 * 4); + return pos; + } +} + +export default FromBase64; diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs new file mode 100644 index 00000000..2fec8e2e --- /dev/null +++ b/src/core/operations/FromBase85.mjs @@ -0,0 +1,105 @@ +/** + * @author PenguinGeorge [george@penguingeorge.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; +import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85"; + +/** + * From Base85 operation + */ +class FromBase85 extends Operation { + + /** + * From Base85 constructor + */ + constructor() { + super(); + + this.name = "From Base85"; + this.module = "Default"; + this.description = "Base85 (also called Ascii85) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.

This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included).

e.g. BOu!rD]j7BEbo7 becomes hello world

Base85 is commonly used in Adobe's PostScript and PDF file formats."; + this.infoURL = "https://wikipedia.org/wiki/Ascii85"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Alphabet", + type: "editableOption", + value: ALPHABET_OPTIONS + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const alphabet = Utils.expandAlphRange(args[0]).join(""), + encoding = alphabetName(alphabet), + result = []; + + if (alphabet.length !== 85 || + [].unique.call(alphabet).length !== 85) { + throw new OperationError("Alphabet must be of length 85"); + } + + if (input.length === 0) return []; + + const matches = input.match(/<~(.+?)~>/); + if (matches !== null) input = matches[1]; + + let i = 0; + let block, blockBytes; + while (i < input.length) { + if (encoding === "Standard" && input[i] === "z") { + result.push(0, 0, 0, 0); + i++; + } else { + let digits = []; + digits = input + .substr(i, 5) + .split("") + .map((chr, idx) => { + const digit = alphabet.indexOf(chr); + if (digit < 0 || digit > 84) { + throw `Invalid character '${chr}' at index ${idx}`; + } + return digit; + }); + + block = + digits[0] * 52200625 + + digits[1] * 614125 + + (i + 2 < input.length ? digits[2] : 84) * 7225 + + (i + 3 < input.length ? digits[3] : 84) * 85 + + (i + 4 < input.length ? digits[4] : 84); + + blockBytes = [ + (block >> 24) & 0xff, + (block >> 16) & 0xff, + (block >> 8) & 0xff, + block & 0xff + ]; + + if (input.length < i + 5) { + blockBytes.splice(input.length - (i + 5), 5); + } + + result.push.apply(result, blockBytes); + i += 5; + } + } + + return result; + } + +} + +export default FromBase85; diff --git a/src/core/operations/FromBinary.mjs b/src/core/operations/FromBinary.mjs new file mode 100644 index 00000000..a7ced7e3 --- /dev/null +++ b/src/core/operations/FromBinary.mjs @@ -0,0 +1,118 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {BIN_DELIM_OPTIONS} from "../lib/Delim"; +import {fromBinary} from "../lib/Binary"; + +/** + * From Binary operation + */ +class FromBinary extends Operation { + + /** + * FromBinary constructor + */ + constructor() { + super(); + + this.name = "From Binary"; + this.module = "Default"; + this.description = "Converts a binary string back into its raw form.

e.g. 01001000 01101001 becomes Hi"; + this.infoURL = "https://wikipedia.org/wiki/Binary_code"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": BIN_DELIM_OPTIONS + } + ]; + this.patterns = [ + { + match: "^(?:[01]{8})+$", + flags: "", + args: ["None"] + }, + { + match: "^(?:[01]{8})(?: [01]{8})*$", + flags: "", + args: ["Space"] + }, + { + match: "^(?:[01]{8})(?:,[01]{8})*$", + flags: "", + args: ["Comma"] + }, + { + match: "^(?:[01]{8})(?:;[01]{8})*$", + flags: "", + args: ["Semi-colon"] + }, + { + match: "^(?:[01]{8})(?::[01]{8})*$", + flags: "", + args: ["Colon"] + }, + { + match: "^(?:[01]{8})(?:\\n[01]{8})*$", + flags: "", + args: ["Line feed"] + }, + { + match: "^(?:[01]{8})(?:\\r\\n[01]{8})*$", + flags: "", + args: ["CRLF"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + return fromBinary(input, args[0]); + } + + /** + * Highlight From Binary + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const delim = Utils.charRep(args[0] || "Space"); + pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length)); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length)); + return pos; + } + + /** + * Highlight From Binary in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const delim = Utils.charRep(args[0] || "Space"); + pos[0].start = pos[0].start * (8 + delim.length); + pos[0].end = pos[0].end * (8 + delim.length) - delim.length; + return pos; + } + +} + +export default FromBinary; diff --git a/src/core/operations/FromBraille.mjs b/src/core/operations/FromBraille.mjs new file mode 100644 index 00000000..61fa1452 --- /dev/null +++ b/src/core/operations/FromBraille.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {BRAILLE_LOOKUP} from "../lib/Braille"; + +/** + * From Braille operation + */ +class FromBraille extends Operation { + + /** + * FromBraille constructor + */ + constructor() { + super(); + + this.name = "From Braille"; + this.module = "Default"; + this.description = "Converts six-dot braille symbols to text."; + this.infoURL = "https://wikipedia.org/wiki/Braille"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.split("").map(b => { + const idx = BRAILLE_LOOKUP.dot6.indexOf(b); + return idx < 0 ? b : BRAILLE_LOOKUP.ascii[idx]; + }).join(""); + } + + /** + * Highlight From Braille + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight From Braille in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default FromBraille; diff --git a/src/core/operations/FromCaseInsensitiveRegex.mjs b/src/core/operations/FromCaseInsensitiveRegex.mjs new file mode 100644 index 00000000..36cd9b14 --- /dev/null +++ b/src/core/operations/FromCaseInsensitiveRegex.mjs @@ -0,0 +1,39 @@ +/** + * @author masq [github.cyberchef@masq.cc] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * From Case Insensitive Regex operation + */ +class FromCaseInsensitiveRegex extends Operation { + + /** + * FromCaseInsensitiveRegex constructor + */ + constructor() { + super(); + + this.name = "From Case Insensitive Regex"; + this.module = "Default"; + this.description = "Converts a case-insensitive regex string to a case sensitive regex string (no guarantee on it being the proper original casing) in case the i flag wasn't available at the time but now is, or you need it to be case-sensitive again.

e.g. [mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .* becomes Mozilla/[0-9].[0-9] .*"; + this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.replace(/\[[a-z]{2}\]/ig, m => m[1].toUpperCase() === m[2].toUpperCase() ? m[1] : m); + } +} + +export default FromCaseInsensitiveRegex; diff --git a/src/core/operations/FromCharcode.mjs b/src/core/operations/FromCharcode.mjs new file mode 100644 index 00000000..3ae54b6c --- /dev/null +++ b/src/core/operations/FromCharcode.mjs @@ -0,0 +1,84 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {DELIM_OPTIONS} from "../lib/Delim"; +import OperationError from "../errors/OperationError"; + +/** + * From Charcode operation + */ +class FromCharcode extends Operation { + + /** + * FromCharcode constructor + */ + constructor() { + super(); + + this.name = "From Charcode"; + this.module = "Default"; + this.description = "Converts unicode character codes back into text.

e.g. 0393 03b5 03b9 03ac 20 03c3 03bf 03c5 becomes Γειά σου"; + this.infoURL = "https://wikipedia.org/wiki/Plane_(Unicode)"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "Base", + "type": "number", + "value": 16 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + * + * @throws {OperationError} if base out of range + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"), + base = args[1]; + let bites = input.split(delim), + i = 0; + + if (base < 2 || base > 36) { + throw new OperationError("Error: Base argument must be between 2 and 36"); + } + + if (input.length === 0) { + return []; + } + + if (base !== 16 && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); + + // Split into groups of 2 if the whole string is concatenated and + // too long to be a single character + if (bites.length === 1 && input.length > 17) { + bites = []; + for (i = 0; i < input.length; i += 2) { + bites.push(input.slice(i, i+2)); + } + } + + let latin1 = ""; + for (i = 0; i < bites.length; i++) { + latin1 += Utils.chr(parseInt(bites[i], base)); + } + return Utils.strToByteArray(latin1); + } + +} + +export default FromCharcode; diff --git a/src/core/operations/FromDecimal.mjs b/src/core/operations/FromDecimal.mjs new file mode 100644 index 00000000..1d0f272c --- /dev/null +++ b/src/core/operations/FromDecimal.mjs @@ -0,0 +1,88 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {DELIM_OPTIONS} from "../lib/Delim"; +import {fromDecimal} from "../lib/Decimal"; + +/** + * From Decimal operation + */ +class FromDecimal extends Operation { + + /** + * FromDecimal constructor + */ + constructor() { + super(); + + this.name = "From Decimal"; + this.module = "Default"; + this.description = "Converts the data from an ordinal integer array back into its raw form.

e.g. 72 101 108 108 111 becomes Hello"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "Support signed values", + "type": "boolean", + "value": false + } + ]; + this.patterns = [ + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?: (?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["Space", false] + }, + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:,(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["Comma", false] + }, + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:;(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["Semi-colon", false] + }, + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?::(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["Colon", false] + }, + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:\\n(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["Line feed", false] + }, + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:\\r\\n(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["CRLF", false] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + let data = fromDecimal(input, args[0]); + if (args[1]) { // Convert negatives + data = data.map(v => v < 0 ? 0xFF + v + 1 : v); + } + return data; + } + +} + +export default FromDecimal; diff --git a/src/core/operations/FromHTMLEntity.mjs b/src/core/operations/FromHTMLEntity.mjs new file mode 100644 index 00000000..0a07962b --- /dev/null +++ b/src/core/operations/FromHTMLEntity.mjs @@ -0,0 +1,343 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * From HTML Entity operation + */ +class FromHTMLEntity extends Operation { + + /** + * FromHTMLEntity constructor + */ + constructor() { + super(); + + this.name = "From HTML Entity"; + this.module = "Default"; + this.description = "Converts HTML entities back to characters

e.g. &amp; becomes &"; + this.infoURL = "https://wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.patterns = [ + { + match: "&(?:#\\d{2,3}|#x[\\da-f]{2}|[a-z]{2,6});", + flags: "i", + args: [] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const regex = /&(#?x?[a-zA-Z0-9]{1,8});/g; + let output = "", + m, + i = 0; + + while ((m = regex.exec(input))) { + // Add up to match + for (; i < m.index;) + output += input[i++]; + + // Add match + const bite = entityToByte[m[1]]; + if (bite) { + output += Utils.chr(bite); + } else if (!bite && m[1][0] === "#" && m[1].length > 1 && /^#\d{1,6}$/.test(m[1])) { + // Numeric entity (e.g. ) + const num = m[1].slice(1, m[1].length); + output += Utils.chr(parseInt(num, 10)); + } else if (!bite && m[1][0] === "#" && m[1].length > 3 && /^#x[\dA-F]{2,8}$/i.test(m[1])) { + // Hex entity (e.g. :) + const hex = m[1].slice(2, m[1].length); + output += Utils.chr(parseInt(hex, 16)); + } else { + // Not a valid entity, print as normal + for (; i < regex.lastIndex;) + output += input[i++]; + } + + i = regex.lastIndex; + } + // Add all after final match + for (; i < input.length;) + output += input[i++]; + + return output; + } + +} + + +/** + * Lookup table to translate HTML entity codes to their byte values. + */ +const entityToByte = { + "quot": 34, + "amp": 38, + "apos": 39, + "lt": 60, + "gt": 62, + "nbsp": 160, + "iexcl": 161, + "cent": 162, + "pound": 163, + "curren": 164, + "yen": 165, + "brvbar": 166, + "sect": 167, + "uml": 168, + "copy": 169, + "ordf": 170, + "laquo": 171, + "not": 172, + "shy": 173, + "reg": 174, + "macr": 175, + "deg": 176, + "plusmn": 177, + "sup2": 178, + "sup3": 179, + "acute": 180, + "micro": 181, + "para": 182, + "middot": 183, + "cedil": 184, + "sup1": 185, + "ordm": 186, + "raquo": 187, + "frac14": 188, + "frac12": 189, + "frac34": 190, + "iquest": 191, + "Agrave": 192, + "Aacute": 193, + "Acirc": 194, + "Atilde": 195, + "Auml": 196, + "Aring": 197, + "AElig": 198, + "Ccedil": 199, + "Egrave": 200, + "Eacute": 201, + "Ecirc": 202, + "Euml": 203, + "Igrave": 204, + "Iacute": 205, + "Icirc": 206, + "Iuml": 207, + "ETH": 208, + "Ntilde": 209, + "Ograve": 210, + "Oacute": 211, + "Ocirc": 212, + "Otilde": 213, + "Ouml": 214, + "times": 215, + "Oslash": 216, + "Ugrave": 217, + "Uacute": 218, + "Ucirc": 219, + "Uuml": 220, + "Yacute": 221, + "THORN": 222, + "szlig": 223, + "agrave": 224, + "aacute": 225, + "acirc": 226, + "atilde": 227, + "auml": 228, + "aring": 229, + "aelig": 230, + "ccedil": 231, + "egrave": 232, + "eacute": 233, + "ecirc": 234, + "euml": 235, + "igrave": 236, + "iacute": 237, + "icirc": 238, + "iuml": 239, + "eth": 240, + "ntilde": 241, + "ograve": 242, + "oacute": 243, + "ocirc": 244, + "otilde": 245, + "ouml": 246, + "divide": 247, + "oslash": 248, + "ugrave": 249, + "uacute": 250, + "ucirc": 251, + "uuml": 252, + "yacute": 253, + "thorn": 254, + "yuml": 255, + "OElig": 338, + "oelig": 339, + "Scaron": 352, + "scaron": 353, + "Yuml": 376, + "fnof": 402, + "circ": 710, + "tilde": 732, + "Alpha": 913, + "Beta": 914, + "Gamma": 915, + "Delta": 916, + "Epsilon": 917, + "Zeta": 918, + "Eta": 919, + "Theta": 920, + "Iota": 921, + "Kappa": 922, + "Lambda": 923, + "Mu": 924, + "Nu": 925, + "Xi": 926, + "Omicron": 927, + "Pi": 928, + "Rho": 929, + "Sigma": 931, + "Tau": 932, + "Upsilon": 933, + "Phi": 934, + "Chi": 935, + "Psi": 936, + "Omega": 937, + "alpha": 945, + "beta": 946, + "gamma": 947, + "delta": 948, + "epsilon": 949, + "zeta": 950, + "eta": 951, + "theta": 952, + "iota": 953, + "kappa": 954, + "lambda": 955, + "mu": 956, + "nu": 957, + "xi": 958, + "omicron": 959, + "pi": 960, + "rho": 961, + "sigmaf": 962, + "sigma": 963, + "tau": 964, + "upsilon": 965, + "phi": 966, + "chi": 967, + "psi": 968, + "omega": 969, + "thetasym": 977, + "upsih": 978, + "piv": 982, + "ensp": 8194, + "emsp": 8195, + "thinsp": 8201, + "zwnj": 8204, + "zwj": 8205, + "lrm": 8206, + "rlm": 8207, + "ndash": 8211, + "mdash": 8212, + "lsquo": 8216, + "rsquo": 8217, + "sbquo": 8218, + "ldquo": 8220, + "rdquo": 8221, + "bdquo": 8222, + "dagger": 8224, + "Dagger": 8225, + "bull": 8226, + "hellip": 8230, + "permil": 8240, + "prime": 8242, + "Prime": 8243, + "lsaquo": 8249, + "rsaquo": 8250, + "oline": 8254, + "frasl": 8260, + "euro": 8364, + "image": 8465, + "weierp": 8472, + "real": 8476, + "trade": 8482, + "alefsym": 8501, + "larr": 8592, + "uarr": 8593, + "rarr": 8594, + "darr": 8595, + "harr": 8596, + "crarr": 8629, + "lArr": 8656, + "uArr": 8657, + "rArr": 8658, + "dArr": 8659, + "hArr": 8660, + "forall": 8704, + "part": 8706, + "exist": 8707, + "empty": 8709, + "nabla": 8711, + "isin": 8712, + "notin": 8713, + "ni": 8715, + "prod": 8719, + "sum": 8721, + "minus": 8722, + "lowast": 8727, + "radic": 8730, + "prop": 8733, + "infin": 8734, + "ang": 8736, + "and": 8743, + "or": 8744, + "cap": 8745, + "cup": 8746, + "int": 8747, + "there4": 8756, + "sim": 8764, + "cong": 8773, + "asymp": 8776, + "ne": 8800, + "equiv": 8801, + "le": 8804, + "ge": 8805, + "sub": 8834, + "sup": 8835, + "nsub": 8836, + "sube": 8838, + "supe": 8839, + "oplus": 8853, + "otimes": 8855, + "perp": 8869, + "sdot": 8901, + "vellip": 8942, + "lceil": 8968, + "rceil": 8969, + "lfloor": 8970, + "rfloor": 8971, + "lang": 9001, + "rang": 9002, + "loz": 9674, + "spades": 9824, + "clubs": 9827, + "hearts": 9829, + "diams": 9830, +}; + +export default FromHTMLEntity; diff --git a/src/core/operations/FromHex.mjs b/src/core/operations/FromHex.mjs new file mode 100644 index 00000000..658cf341 --- /dev/null +++ b/src/core/operations/FromHex.mjs @@ -0,0 +1,147 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {fromHex, FROM_HEX_DELIM_OPTIONS} from "../lib/Hex"; +import Utils from "../Utils"; + +/** + * From Hex operation + */ +class FromHex extends Operation { + + /** + * FromHex constructor + */ + constructor() { + super(); + + this.name = "From Hex"; + this.module = "Default"; + this.description = "Converts a hexadecimal byte string back into its raw value.

e.g. ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a becomes the UTF-8 encoded string Γειά σου"; + this.infoURL = "https://wikipedia.org/wiki/Hexadecimal"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: FROM_HEX_DELIM_OPTIONS + } + ]; + this.patterns = [ + { + match: "^(?:[\\dA-F]{2})+$", + flags: "i", + args: ["None"] + }, + { + match: "^[\\dA-F]{2}(?: [\\dA-F]{2})*$", + flags: "i", + args: ["Space"] + }, + { + match: "^[\\dA-F]{2}(?:,[\\dA-F]{2})*$", + flags: "i", + args: ["Comma"] + }, + { + match: "^[\\dA-F]{2}(?:;[\\dA-F]{2})*$", + flags: "i", + args: ["Semi-colon"] + }, + { + match: "^[\\dA-F]{2}(?::[\\dA-F]{2})*$", + flags: "i", + args: ["Colon"] + }, + { + match: "^[\\dA-F]{2}(?:\\n[\\dA-F]{2})*$", + flags: "i", + args: ["Line feed"] + }, + { + match: "^[\\dA-F]{2}(?:\\r\\n[\\dA-F]{2})*$", + flags: "i", + args: ["CRLF"] + }, + { + match: "^[\\dA-F]{2}(?:0x[\\dA-F]{2})*$", + flags: "i", + args: ["0x"] + }, + { + match: "^[\\dA-F]{2}(?:\\\\x[\\dA-F]{2})*$", + flags: "i", + args: ["\\x"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = args[0] || "Auto"; + return fromHex(input, delim, 2); + } + + /** + * Highlight to Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + if (args[0] === "Auto") return false; + const delim = Utils.charRep(args[0] || "Space"), + len = delim === "\r\n" ? 1 : delim.length, + width = len + 2; + + // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly + if (delim === "0x" || delim === "\\x") { + if (pos[0].start > 1) pos[0].start -= 2; + else pos[0].start = 0; + if (pos[0].end > 1) pos[0].end -= 2; + else pos[0].end = 0; + } + + pos[0].start = pos[0].start === 0 ? 0 : Math.round(pos[0].start / width); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / width); + return pos; + } + + /** + * Highlight from Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const delim = Utils.charRep(args[0] || "Space"), + len = delim === "\r\n" ? 1 : delim.length; + + pos[0].start = pos[0].start * (2 + len); + pos[0].end = pos[0].end * (2 + len) - len; + + // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly + if (delim === "0x" || delim === "\\x") { + pos[0].start += 2; + pos[0].end += 2; + } + return pos; + } +} + +export default FromHex; diff --git a/src/core/operations/FromHexContent.mjs b/src/core/operations/FromHexContent.mjs new file mode 100644 index 00000000..22e858e2 --- /dev/null +++ b/src/core/operations/FromHexContent.mjs @@ -0,0 +1,67 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {fromHex} from "../lib/Hex"; + +/** + * From Hex Content operation + */ +class FromHexContent extends Operation { + + /** + * FromHexContent constructor + */ + constructor() { + super(); + + this.name = "From Hex Content"; + this.module = "Default"; + this.description = "Translates hexadecimal bytes in text back to raw bytes. This format is used by SNORT for representing hex within ASCII text.

e.g. foo|3d|bar becomes foo=bar."; + this.infoURL = "http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node32.html#SECTION00451000000000000000"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const regex = /\|([a-f\d ]{2,})\|/gi, + output = []; + let m, i = 0; + while ((m = regex.exec(input))) { + // Add up to match + for (; i < m.index;) + output.push(Utils.ord(input[i++])); + + // Add match + const bytes = fromHex(m[1]); + if (bytes) { + for (let a = 0; a < bytes.length;) + output.push(bytes[a++]); + } else { + // Not valid hex, print as normal + for (; i < regex.lastIndex;) + output.push(Utils.ord(input[i++])); + } + + i = regex.lastIndex; + } + // Add all after final match + for (; i < input.length;) + output.push(Utils.ord(input[i++])); + + return output; + } + +} + +export default FromHexContent; diff --git a/src/core/operations/Hexdump.js b/src/core/operations/FromHexdump.mjs old mode 100755 new mode 100644 similarity index 61% rename from src/core/operations/Hexdump.js rename to src/core/operations/FromHexdump.mjs index 94014fed..7f4d9119 --- a/src/core/operations/Hexdump.js +++ b/src/core/operations/FromHexdump.mjs @@ -1,88 +1,51 @@ -/* globals app */ -import Utils from "../Utils.js"; - - /** - * Hexdump operations. - * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 - * - * @namespace */ -const Hexdump = { + +import Operation from "../Operation"; +import {fromHex} from "../lib/Hex"; + +/** + * From Hexdump operation + */ +class FromHexdump extends Operation { /** - * @constant - * @default + * FromHexdump constructor */ - WIDTH: 16, - /** - * @constant - * @default - */ - UPPER_CASE: false, - /** - * @constant - * @default - */ - INCLUDE_FINAL_LENGTH: false, + constructor() { + super(); + + this.name = "From Hexdump"; + this.module = "Default"; + this.description = "Attempts to convert a hexdump back into raw data. This operation supports many different hexdump variations, but probably not all. Make sure you verify that the data it gives you is correct before continuing analysis."; + this.infoURL = "https://wikipedia.org/wiki/Hex_dump"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + this.patterns = [ + { + match: "^(?:(?:[\\dA-F]{4,16}h?:?)?[ \\t]*((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )*[\\dA-F]{4}|(?:[\\dA-F]{2} )*[\\dA-F]{2})[^\\n]*\\n?){2,}$", + flags: "i", + args: [] + }, + ]; + } /** - * To Hexdump operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runTo: function(input, args) { - const length = args[0] || Hexdump.WIDTH; - const upperCase = args[1]; - const includeFinalLength = args[2]; - - let output = "", padding = 2; - for (let i = 0; i < input.length; i += length) { - const buff = input.slice(i, i+length); - let hexa = ""; - for (let j = 0; j < buff.length; j++) { - hexa += Utils.hex(buff[j], padding) + " "; - } - - let lineNo = Utils.hex(i, 8); - - if (upperCase) { - hexa = hexa.toUpperCase(); - lineNo = lineNo.toUpperCase(); - } - - output += lineNo + " " + - Utils.padRight(hexa, (length*(padding+1))) + - " |" + Utils.padRight(Utils.printable(Utils.byteArrayToChars(buff)), buff.length) + "|\n"; - - if (includeFinalLength && i+buff.length === input.length) { - output += Utils.hex(i+buff.length, 8) + "\n"; - } - } - - return output.slice(0, -1); - }, - - - /** - * From Hexdump operation. - * * @param {string} input * @param {Object[]} args * @returns {byteArray} */ - runFrom: function(input, args) { - let output = [], - regex = /^\s*(?:[\dA-F]{4,16}:?)?\s*((?:[\dA-F]{2}\s){1,8}(?:\s|[\dA-F]{2}-)(?:[\dA-F]{2}\s){1,8}|(?:[\dA-F]{2}\s|[\dA-F]{4}\s)+)/igm, - block, line; + run(input, args) { + const output = [], + regex = /^\s*(?:[\dA-F]{4,16}h?:?)?[ \t]+((?:[\dA-F]{2} ){1,8}(?:[ \t]|[\dA-F]{2}-)(?:[\dA-F]{2} ){1,8}|(?:[\dA-F]{4} )*[\dA-F]{4}|(?:[\dA-F]{2} )*[\dA-F]{2})/igm; + let block, line; while ((block = regex.exec(input))) { - line = Utils.fromHex(block[1].replace(/-/g, " ")); + line = fromHex(block[1].replace(/-/g, " ")); for (let i = 0; i < line.length; i++) { output.push(line[i]); } @@ -92,14 +55,13 @@ const Hexdump = { const w = (width - 13) / 4; // w should be the specified width of the hexdump and therefore a round number if (Math.floor(w) !== w || input.indexOf("\r") !== -1 || output.indexOf(13) !== -1) { - if (app) app.options.attemptHighlight = false; + if (ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); } return output; - }, - + } /** - * Highlight to hexdump + * Highlight From Hexdump * * @param {Object[]} pos * @param {number} pos[].start @@ -107,11 +69,49 @@ const Hexdump = { * @param {Object[]} args * @returns {Object[]} pos */ - highlightTo: function(pos, args) { + highlight(pos, args) { + const w = args[0] || 16; + const width = 14 + (w*4); + + let line = Math.floor(pos[0].start / width); + let offset = pos[0].start % width; + + if (offset < 10) { // In line number section + pos[0].start = line*w; + } else if (offset > 10+(w*3)) { // In ASCII section + pos[0].start = (line+1)*w; + } else { // In byte section + pos[0].start = line*w + Math.floor((offset-10)/3); + } + + line = Math.floor(pos[0].end / width); + offset = pos[0].end % width; + + if (offset < 10) { // In line number section + pos[0].end = line*w; + } else if (offset > 10+(w*3)) { // In ASCII section + pos[0].end = (line+1)*w; + } else { // In byte section + pos[0].end = line*w + Math.ceil((offset-10)/3); + } + + return pos; + } + + /** + * Highlight From Hexdump in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { // Calculate overall selection - let w = args[0] || 16, - width = 14 + (w*4), - line = Math.floor(pos[0].start / w), + const w = args[0] || 16, + width = 14 + (w*4); + let line = Math.floor(pos[0].start / w), offset = pos[0].start % w, start = 0, end = 0; @@ -146,7 +146,8 @@ const Hexdump = { } // Set up multiple selections for ASCII - let len = pos.length, lineNum = 0; + const len = pos.length; + let lineNum = 0; start = 0; end = 0; for (let i = 1; i < len; i++) { @@ -156,47 +157,8 @@ const Hexdump = { pos.push({ start: start, end: end }); } return pos; - }, + } +} - /** - * Highlight from hexdump - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightFrom: function(pos, args) { - const w = args[0] || 16; - const width = 14 + (w*4); - - let line = Math.floor(pos[0].start / width); - let offset = pos[0].start % width; - - if (offset < 10) { // In line number section - pos[0].start = line*w; - } else if (offset > 10+(w*3)) { // In ASCII section - pos[0].start = (line+1)*w; - } else { // In byte section - pos[0].start = line*w + Math.floor((offset-10)/3); - } - - line = Math.floor(pos[0].end / width); - offset = pos[0].end % width; - - if (offset < 10) { // In line number section - pos[0].end = line*w; - } else if (offset > 10+(w*3)) { // In ASCII section - pos[0].end = (line+1)*w; - } else { // In byte section - pos[0].end = line*w + Math.ceil((offset-10)/3); - } - - return pos; - }, - -}; - -export default Hexdump; +export default FromHexdump; diff --git a/src/core/operations/FromMessagePack.mjs b/src/core/operations/FromMessagePack.mjs new file mode 100644 index 00000000..cea7c498 --- /dev/null +++ b/src/core/operations/FromMessagePack.mjs @@ -0,0 +1,47 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import notepack from "notepack.io"; + +/** + * From MessagePack operation + */ +class FromMessagePack extends Operation { + + /** + * FromMessagePack constructor + */ + constructor() { + super(); + + this.name = "From MessagePack"; + this.module = "Code"; + this.description = "Converts MessagePack encoded data to JSON. MessagePack is a computer data interchange format. It is a binary form for representing simple data structures like arrays and associative arrays."; + this.infoURL = "https://wikipedia.org/wiki/MessagePack"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + try { + const buf = Buffer.from(new Uint8Array(input)); + return notepack.decode(buf); + } catch (err) { + throw new OperationError(`Could not decode MessagePack to JSON: ${err}`); + } + } + +} + +export default FromMessagePack; diff --git a/src/core/operations/FromMorseCode.mjs b/src/core/operations/FromMorseCode.mjs new file mode 100644 index 00000000..71613ad1 --- /dev/null +++ b/src/core/operations/FromMorseCode.mjs @@ -0,0 +1,153 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * From Morse Code operation + */ +class FromMorseCode extends Operation { + + /** + * FromMorseCode constructor + */ + constructor() { + super(); + + this.name = "From Morse Code"; + this.module = "Default"; + this.description = "Translates Morse Code into (upper case) alphanumeric characters."; + this.infoURL = "https://wikipedia.org/wiki/Morse_code"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Letter delimiter", + "type": "option", + "value": LETTER_DELIM_OPTIONS + }, + { + "name": "Word delimiter", + "type": "option", + "value": WORD_DELIM_OPTIONS + } + ]; + this.patterns = [ + { + match: "(?:^[-. \\n]{5,}$|^[_. \\n]{5,}$|^(?:dash|dot| |\\n){5,}$)", + flags: "i", + args: ["Space", "Line feed"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!this.reversedTable) { + this.reverseTable(); + } + + const letterDelim = Utils.charRep(args[0]); + const wordDelim = Utils.charRep(args[1]); + + input = input.replace(/-|‐|−|_|–|—|dash/ig, ""); //hyphen-minus|hyphen|minus-sign|undersore|en-dash|em-dash + input = input.replace(/\.|·|dot/ig, ""); + + let words = input.split(wordDelim); + const self = this; + words = Array.prototype.map.call(words, function(word) { + const signals = word.split(letterDelim); + + const letters = signals.map(function(signal) { + return self.reversedTable[signal]; + }); + + return letters.join(""); + }); + words = words.join(" "); + + return words; + } + + + /** + * Reverses the Morse Code lookup table + */ + reverseTable() { + this.reversedTable = {}; + + for (const letter in MORSE_TABLE) { + const signal = MORSE_TABLE[letter]; + this.reversedTable[signal] = letter; + } + } + +} + +const MORSE_TABLE = { + "A": "", + "B": "", + "C": "", + "D": "", + "E": "", + "F": "", + "G": "", + "H": "", + "I": "", + "J": "", + "K": "", + "L": "", + "M": "", + "N": "", + "O": "", + "P": "", + "Q": "", + "R": "", + "S": "", + "T": "", + "U": "", + "V": "", + "W": "", + "X": "", + "Y": "", + "Z": "", + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + "6": "", + "7": "", + "8": "", + "9": "", + "0": "", + ".": "", + ",": "", + ":": "", + ";": "", + "!": "", + "?": "", + "'": "", + "\"": "", + "/": "", + "-": "", + "+": "", + "(": "", + ")": "", + "@": "", + "=": "", + "&": "", + "_": "", + "$": "" +}; + +export default FromMorseCode; diff --git a/src/core/operations/FromOctal.mjs b/src/core/operations/FromOctal.mjs new file mode 100644 index 00000000..41060807 --- /dev/null +++ b/src/core/operations/FromOctal.mjs @@ -0,0 +1,82 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {DELIM_OPTIONS} from "../lib/Delim"; + +/** + * From Octal operation + */ +class FromOctal extends Operation { + + /** + * FromOctal constructor + */ + constructor() { + super(); + + this.name = "From Octal"; + this.module = "Default"; + this.description = "Converts an octal byte string back into its raw value.

e.g. 316 223 316 265 316 271 316 254 40 317 203 316 277 317 205 becomes the UTF-8 encoded string Γειά σου"; + this.infoURL = "https://wikipedia.org/wiki/Octal"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + this.patterns = [ + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?: (?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Space"] + }, + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:,(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Comma"] + }, + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:;(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Semi-colon"] + }, + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?::(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Colon"] + }, + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Line feed"] + }, + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\r\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["CRLF"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"); + if (input.length === 0) return []; + return input.split(delim).map(val => parseInt(val, 8)); + } + +} + +export default FromOctal; diff --git a/src/core/operations/FromPunycode.mjs b/src/core/operations/FromPunycode.mjs new file mode 100644 index 00000000..96c0bcf9 --- /dev/null +++ b/src/core/operations/FromPunycode.mjs @@ -0,0 +1,53 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import punycode from "punycode"; + +/** + * From Punycode operation + */ +class FromPunycode extends Operation { + + /** + * FromPunycode constructor + */ + constructor() { + super(); + + this.name = "From Punycode"; + this.module = "Encodings"; + this.description = "Punycode is a way to represent Unicode with the limited character subset of ASCII supported by the Domain Name System.

e.g. mnchen-3ya decodes to m\xfcnchen"; + this.infoURL = "https://wikipedia.org/wiki/Punycode"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Internationalised domain name", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const idn = args[0]; + + if (idn) { + return punycode.toUnicode(input); + } else { + return punycode.decode(input); + } + } + +} + +export default FromPunycode; diff --git a/src/core/operations/FromQuotedPrintable.mjs b/src/core/operations/FromQuotedPrintable.mjs new file mode 100644 index 00000000..61466e4e --- /dev/null +++ b/src/core/operations/FromQuotedPrintable.mjs @@ -0,0 +1,69 @@ +/** + * Some parts taken from mimelib (http://github.com/andris9/mimelib) + * @author Andris Reinman + * @license MIT + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * From Quoted Printable operation + */ +class FromQuotedPrintable extends Operation { + + /** + * FromQuotedPrintable constructor + */ + constructor() { + super(); + + this.name = "From Quoted Printable"; + this.module = "Default"; + this.description = "Converts QP-encoded text back to standard text."; + this.infoURL = "https://wikipedia.org/wiki/Quoted-printable"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + this.patterns = [ + { + match: "^[\\x21-\\x3d\\x3f-\\x7e \\t]{0,76}(?:=[\\da-f]{2}|=\\r?\\n)(?:[\\x21-\\x3d\\x3f-\\x7e \\t]|=[\\da-f]{2}|=\\r?\\n)*$", + flags: "i", + args: [] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const str = input.replace(/=(?:\r?\n|$)/g, ""); + + const encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length, + bufferLength = str.length - encodedBytesCount * 2, + buffer = new Array(bufferLength); + let chr, hex, + bufferPos = 0; + + for (let i = 0, len = str.length; i < len; i++) { + chr = str.charAt(i); + if (chr === "=" && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) { + buffer[bufferPos++] = parseInt(hex, 16); + i += 2; + continue; + } + buffer[bufferPos++] = chr.charCodeAt(0); + } + + return buffer; + } + +} + +export default FromQuotedPrintable; diff --git a/src/core/operations/FromUNIXTimestamp.mjs b/src/core/operations/FromUNIXTimestamp.mjs new file mode 100644 index 00000000..ee074ec0 --- /dev/null +++ b/src/core/operations/FromUNIXTimestamp.mjs @@ -0,0 +1,92 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import moment from "moment-timezone"; +import {UNITS} from "../lib/DateTime"; +import OperationError from "../errors/OperationError"; + +/** + * From UNIX Timestamp operation + */ +class FromUNIXTimestamp extends Operation { + + /** + * FromUNIXTimestamp constructor + */ + constructor() { + super(); + + this.name = "From UNIX Timestamp"; + this.module = "Default"; + this.description = "Converts a UNIX timestamp to a datetime string.

e.g. 978346800 becomes Mon 1 January 2001 11:00:00 UTC

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch)."; + this.infoURL = "https://wikipedia.org/wiki/Unix_time"; + this.inputType = "number"; + this.outputType = "string"; + this.args = [ + { + "name": "Units", + "type": "option", + "value": UNITS + } + ]; + this.patterns = [ + { + match: "^1?\\d{9}$", + flags: "", + args: ["Seconds (s)"] + }, + { + match: "^1?\\d{12}$", + flags: "", + args: ["Milliseconds (ms)"] + }, + { + match: "^1?\\d{15}$", + flags: "", + args: ["Microseconds (μs)"] + }, + { + match: "^1?\\d{18}$", + flags: "", + args: ["Nanoseconds (ns)"] + }, + ]; + } + + /** + * @param {number} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid unit + */ + run(input, args) { + const units = args[0]; + let d; + + input = parseFloat(input); + + if (units === "Seconds (s)") { + d = moment.unix(input); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss") + " UTC"; + } else if (units === "Milliseconds (ms)") { + d = moment(input); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; + } else if (units === "Microseconds (μs)") { + d = moment(input / 1000); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; + } else if (units === "Nanoseconds (ns)") { + d = moment(input / 1000000); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; + } else { + throw new OperationError("Unrecognised unit"); + } + } + +} + +export default FromUNIXTimestamp; diff --git a/src/core/operations/GenerateAllHashes.mjs b/src/core/operations/GenerateAllHashes.mjs new file mode 100644 index 00000000..633337f0 --- /dev/null +++ b/src/core/operations/GenerateAllHashes.mjs @@ -0,0 +1,105 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import MD2 from "./MD2"; +import MD4 from "./MD4"; +import MD5 from "./MD5"; +import MD6 from "./MD6"; +import SHA0 from "./SHA0"; +import SHA1 from "./SHA1"; +import SHA2 from "./SHA2"; +import SHA3 from "./SHA3"; +import Keccak from "./Keccak"; +import Shake from "./Shake"; +import RIPEMD from "./RIPEMD"; +import HAS160 from "./HAS160"; +import Whirlpool from "./Whirlpool"; +import SSDEEP from "./SSDEEP"; +import CTPH from "./CTPH"; +import Fletcher8Checksum from "./Fletcher8Checksum"; +import Fletcher16Checksum from "./Fletcher16Checksum"; +import Fletcher32Checksum from "./Fletcher32Checksum"; +import Fletcher64Checksum from "./Fletcher64Checksum"; +import Adler32Checksum from "./Adler32Checksum"; +import CRC16Checksum from "./CRC16Checksum"; +import CRC32Checksum from "./CRC32Checksum"; + +/** + * Generate all hashes operation + */ +class GenerateAllHashes extends Operation { + + /** + * GenerateAllHashes constructor + */ + constructor() { + super(); + + this.name = "Generate all hashes"; + this.module = "Crypto"; + this.description = "Generates all available hashes and checksums for the input."; + this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const arrayBuffer = input, + str = Utils.arrayBufferToStr(arrayBuffer, false), + byteArray = new Uint8Array(arrayBuffer), + output = "MD2: " + (new MD2()).run(arrayBuffer, []) + + "\nMD4: " + (new MD4()).run(arrayBuffer, []) + + "\nMD5: " + (new MD5()).run(arrayBuffer, []) + + "\nMD6: " + (new MD6()).run(str, []) + + "\nSHA0: " + (new SHA0()).run(arrayBuffer, []) + + "\nSHA1: " + (new SHA1()).run(arrayBuffer, []) + + "\nSHA2 224: " + (new SHA2()).run(arrayBuffer, ["224"]) + + "\nSHA2 256: " + (new SHA2()).run(arrayBuffer, ["256"]) + + "\nSHA2 384: " + (new SHA2()).run(arrayBuffer, ["384"]) + + "\nSHA2 512: " + (new SHA2()).run(arrayBuffer, ["512"]) + + "\nSHA3 224: " + (new SHA3()).run(arrayBuffer, ["224"]) + + "\nSHA3 256: " + (new SHA3()).run(arrayBuffer, ["256"]) + + "\nSHA3 384: " + (new SHA3()).run(arrayBuffer, ["384"]) + + "\nSHA3 512: " + (new SHA3()).run(arrayBuffer, ["512"]) + + "\nKeccak 224: " + (new Keccak()).run(arrayBuffer, ["224"]) + + "\nKeccak 256: " + (new Keccak()).run(arrayBuffer, ["256"]) + + "\nKeccak 384: " + (new Keccak()).run(arrayBuffer, ["384"]) + + "\nKeccak 512: " + (new Keccak()).run(arrayBuffer, ["512"]) + + "\nShake 128: " + (new Shake()).run(arrayBuffer, ["128", 256]) + + "\nShake 256: " + (new Shake()).run(arrayBuffer, ["256", 512]) + + "\nRIPEMD-128: " + (new RIPEMD()).run(arrayBuffer, ["128"]) + + "\nRIPEMD-160: " + (new RIPEMD()).run(arrayBuffer, ["160"]) + + "\nRIPEMD-256: " + (new RIPEMD()).run(arrayBuffer, ["256"]) + + "\nRIPEMD-320: " + (new RIPEMD()).run(arrayBuffer, ["320"]) + + "\nHAS-160: " + (new HAS160()).run(arrayBuffer, []) + + "\nWhirlpool-0: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-0"]) + + "\nWhirlpool-T: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-T"]) + + "\nWhirlpool: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool"]) + + "\nSSDEEP: " + (new SSDEEP()).run(str) + + "\nCTPH: " + (new CTPH()).run(str) + + "\n\nChecksums:" + + "\nFletcher-8: " + (new Fletcher8Checksum).run(byteArray, []) + + "\nFletcher-16: " + (new Fletcher16Checksum).run(byteArray, []) + + "\nFletcher-32: " + (new Fletcher32Checksum).run(byteArray, []) + + "\nFletcher-64: " + (new Fletcher64Checksum).run(byteArray, []) + + "\nAdler-32: " + (new Adler32Checksum).run(byteArray, []) + + "\nCRC-16: " + (new CRC16Checksum).run(str, []) + + "\nCRC-32: " + (new CRC32Checksum).run(str, []); + + return output; + } + +} + +export default GenerateAllHashes; diff --git a/src/core/operations/GenerateHOTP.mjs b/src/core/operations/GenerateHOTP.mjs new file mode 100644 index 00000000..a1a80012 --- /dev/null +++ b/src/core/operations/GenerateHOTP.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import otp from "otp"; +import ToBase32 from "./ToBase32"; + +/** + * Generate HOTP operation + */ +class GenerateHOTP extends Operation { + + /** + * GenerateHOTP constructor + */ + constructor() { + super(); + + this.name = "Generate HOTP"; + 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.

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.outputType = "string"; + this.args = [ + { + "name": "Name", + "type": "string", + "value": "" + }, + { + "name": "Key size", + "type": "number", + "value": 32 + }, + { + "name": "Code length", + "type": "number", + "value": 6 + }, + { + "name": "Counter", + "type": "number", + "value": 0 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const otpObj = otp({ + name: args[0], + keySize: args[1], + codeLength: args[2], + secret: (new ToBase32).run(input, []), + }); + const counter = args[3]; + return `URI: ${otpObj.hotpURL}\n\nPassword: ${otpObj.hotp(counter)}`; + } + +} + +export default GenerateHOTP; diff --git a/src/core/operations/GenerateLoremIpsum.mjs b/src/core/operations/GenerateLoremIpsum.mjs new file mode 100644 index 00000000..fb5ecd17 --- /dev/null +++ b/src/core/operations/GenerateLoremIpsum.mjs @@ -0,0 +1,70 @@ +/** + * @author klaxon [klaxon@veyr.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum"; + +/** + * Generate Lorem Ipsum operation + */ +class GenerateLoremIpsum extends Operation { + + /** + * GenerateLoremIpsum constructor + */ + constructor() { + super(); + + this.name = "Generate Lorem Ipsum"; + this.module = "Default"; + this.description = "Generate varying length lorem ipsum placeholder text."; + this.infoURL = "https://wikipedia.org/wiki/Lorem_ipsum"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Length", + "type": "number", + "value": "3" + }, + { + "name": "Length in", + "type": "option", + "value": ["Paragraphs", "Sentences", "Words", "Bytes"] + } + + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [length, lengthType] = args; + if (length < 1){ + throw new OperationError("Length must be greater than 0"); + } + switch (lengthType) { + case "Paragraphs": + return GenerateParagraphs(length); + case "Sentences": + return GenerateSentences(length); + case "Words": + return GenerateWords(length); + case "Bytes": + return GenerateBytes(length); + default: + throw new OperationError("Invalid length type"); + + } + } + +} + +export default GenerateLoremIpsum; diff --git a/src/core/operations/GeneratePGPKeyPair.mjs b/src/core/operations/GeneratePGPKeyPair.mjs new file mode 100644 index 00000000..74f8a404 --- /dev/null +++ b/src/core/operations/GeneratePGPKeyPair.mjs @@ -0,0 +1,117 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [matt@artemisbot.uk] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { getSubkeySize, ASP } from "../lib/PGP"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * Generate PGP Key Pair operation + */ +class GeneratePGPKeyPair extends Operation { + + /** + * GeneratePGPKeyPair constructor + */ + constructor() { + super(); + + this.name = "Generate PGP Key Pair"; + this.module = "PGP"; + this.description = "Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys."; + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key type", + "type": "option", + "value": ["RSA-1024", "RSA-2048", "RSA-4096", "ECC-256", "ECC-384"] + }, + { + "name": "Password (optional)", + "type": "string", + "value": "" + }, + { + "name": "Name (optional)", + "type": "string", + "value": "" + }, + { + "name": "Email (optional)", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [keyType, keySize] = args[0].split("-"), + password = args[1], + name = args[2], + email = args[3]; + let userIdentifier = ""; + + if (name) userIdentifier += name; + if (email) userIdentifier += ` <${email}>`; + + let flags = kbpgp.const.openpgp.certify_keys; + flags |= kbpgp.const.openpgp.sign_data; + flags |= kbpgp.const.openpgp.auth; + flags |= kbpgp.const.openpgp.encrypt_comm; + flags |= kbpgp.const.openpgp.encrypt_storage; + + const keyGenerationOptions = { + userid: userIdentifier, + ecc: keyType === "ecc", + primary: { + "nbits": keySize, + "flags": flags, + "expire_in": 0 + }, + subkeys: [{ + "nbits": getSubkeySize(keySize), + "flags": kbpgp.const.openpgp.sign_data, + "expire_in": 86400 * 365 * 8 + }, { + "nbits": getSubkeySize(keySize), + "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage, + "expire_in": 86400 * 365 * 2 + }], + asp: ASP + }; + + return new Promise(async (resolve, reject) => { + try { + const unsignedKey = await promisify(kbpgp.KeyManager.generate)(keyGenerationOptions); + await promisify(unsignedKey.sign.bind(unsignedKey))({}); + + const signedKey = unsignedKey, + privateKeyExportOptions = {}; + + if (password) privateKeyExportOptions.passphrase = password; + const privateKey = await promisify(signedKey.export_pgp_private.bind(signedKey))(privateKeyExportOptions); + const publicKey = await promisify(signedKey.export_pgp_public.bind(signedKey))({}); + resolve(privateKey + "\n" + publicKey.trim()); + } catch (err) { + reject(`Error whilst generating key pair: ${err}`); + } + }); + } + +} + +export default GeneratePGPKeyPair; diff --git a/src/core/operations/GenerateQRCode.mjs b/src/core/operations/GenerateQRCode.mjs new file mode 100644 index 00000000..edab6d40 --- /dev/null +++ b/src/core/operations/GenerateQRCode.mjs @@ -0,0 +1,119 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import qr from "qr-image"; +import { toBase64 } from "../lib/Base64"; +import Magic from "../lib/Magic"; +import Utils from "../Utils"; + +/** + * Generate QR Code operation + */ +class GenerateQRCode extends Operation { + + /** + * GenerateQRCode constructor + */ + constructor() { + super(); + + this.name = "Generate QR Code"; + this.module = "Image"; + this.description = "Generates a Quick Response (QR) code from the input text.

A QR code is a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan. A barcode is a machine-readable optical label that contains information about the item to which it is attached."; + this.infoURL = "https://wikipedia.org/wiki/QR_code"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.presentType = "html"; + this.args = [ + { + "name": "Image Format", + "type": "option", + "value": ["PNG", "SVG", "EPS", "PDF"] + }, + { + "name": "Module size (px)", + "type": "number", + "value": 5 + }, + { + "name": "Margin (num modules)", + "type": "number", + "value": 2 + }, + { + "name": "Error correction", + "type": "option", + "value": ["Low", "Medium", "Quartile", "High"], + "defaultIndex": 1 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const [format, size, margin, errorCorrection] = args; + + // Create new QR image from the input data, and convert it to a buffer + const qrImage = qr.imageSync(input, { + type: format, + size: size, + margin: margin, + "ec_level": errorCorrection.charAt(0).toUpperCase() + }); + + if (qrImage == null) { + throw new OperationError("Error generating QR code."); + } + + switch (format) { + case "SVG": + case "EPS": + case "PDF": + return [...Buffer.from(qrImage)]; + case "PNG": + // Return the QR image buffer as a byte array + return [...qrImage]; + default: + throw new OperationError("Unsupported QR code format."); + } + } + + /** + * Displays the QR image using HTML for web apps + * + * @param {byteArray} data + * @returns {html} + */ + present(data, args) { + if (!data.length) return ""; + + const [format] = args; + + if (format === "PNG") { + let dataURI = "data:"; + const type = Magic.magicFileType(data); + if (type && type.mime.indexOf("image") === 0){ + dataURI += type.mime + ";"; + } else { + throw new OperationError("Invalid PNG file generated by QR image"); + } + dataURI += "base64," + toBase64(data); + + return ``; + } + + return Utils.byteArrayToChars(data); + } + +} + +export default GenerateQRCode; diff --git a/src/core/operations/GenerateTOTP.mjs b/src/core/operations/GenerateTOTP.mjs new file mode 100644 index 00000000..a71708a2 --- /dev/null +++ b/src/core/operations/GenerateTOTP.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import otp from "otp"; +import ToBase32 from "./ToBase32"; + +/** + * Generate TOTP operation + */ +class GenerateTOTP extends Operation { + + /** + * GenerateTOTP constructor + */ + constructor() { + super(); + + this.name = "Generate TOTP"; + 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.

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.outputType = "string"; + this.args = [ + { + "name": "Name", + "type": "string", + "value": "" + }, + { + "name": "Key size", + "type": "number", + "value": 32 + }, + { + "name": "Code length", + "type": "number", + "value": 6 + }, + { + "name": "Epoch offset (T0)", + "type": "number", + "value": 0 + }, + { + "name": "Interval (T1)", + "type": "number", + "value": 30 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const otpObj = otp({ + name: args[0], + keySize: args[1], + codeLength: args[2], + secret: (new ToBase32).run(input, []), + epoch: args[3], + timeSlice: args[4] + }); + return `URI: ${otpObj.totpURL}\n\nPassword: ${otpObj.totp()}`; + } + +} + +export default GenerateTOTP; diff --git a/src/core/operations/GenerateUUID.mjs b/src/core/operations/GenerateUUID.mjs new file mode 100644 index 00000000..eef32c73 --- /dev/null +++ b/src/core/operations/GenerateUUID.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import crypto from "crypto"; + +/** + * Generate UUID operation + */ +class GenerateUUID extends Operation { + + /** + * GenerateUUID constructor + */ + constructor() { + super(); + + this.name = "Generate UUID"; + this.module = "Crypto"; + this.description = "Generates an RFC 4122 version 4 compliant Universally Unique Identifier (UUID), also known as a Globally Unique Identifier (GUID).

A version 4 UUID relies on random numbers, in this case generated using window.crypto if available and falling back to Math.random if not."; + this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const buf = new Uint32Array(4).map(() => { + return crypto.randomBytes(4).readUInt32BE(0, true); + }); + let i = 0; + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { + const r = (buf[i >> 3] >> ((i % 8) * 4)) & 0xf, + v = c === "x" ? r : (r & 0x3 | 0x8); + i++; + return v.toString(16); + }); + } + +} + +export default GenerateUUID; diff --git a/src/core/operations/GenericCodeBeautify.mjs b/src/core/operations/GenericCodeBeautify.mjs new file mode 100644 index 00000000..5078bebc --- /dev/null +++ b/src/core/operations/GenericCodeBeautify.mjs @@ -0,0 +1,161 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Generic Code Beautify operation + */ +class GenericCodeBeautify extends Operation { + + /** + * GenericCodeBeautify constructor + */ + constructor() { + super(); + + this.name = "Generic Code Beautify"; + this.module = "Code"; + this.description = "Attempts to pretty print C-style languages such as C, C++, C#, Java, PHP, JavaScript etc.

This will not do a perfect job, and the resulting code may not work any more. This operation is designed purely to make obfuscated or minified code more easy to read and understand.

Things which will not work properly:
  • For loop formatting
  • Do-While loop formatting
  • Switch/Case indentation
  • Certain bit shift operators
"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const preservedTokens = []; + let code = input, + t = 0, + m; + + // Remove strings + const sstrings = /'([^'\\]|\\.)*'/g; + while ((m = sstrings.exec(code))) { + code = preserveToken(code, m, t++); + sstrings.lastIndex = m.index; + } + + const dstrings = /"([^"\\]|\\.)*"/g; + while ((m = dstrings.exec(code))) { + code = preserveToken(code, m, t++); + dstrings.lastIndex = m.index; + } + + // Remove comments + const scomments = /\/\/[^\n\r]*/g; + while ((m = scomments.exec(code))) { + code = preserveToken(code, m, t++); + scomments.lastIndex = m.index; + } + + const mcomments = /\/\*[\s\S]*?\*\//gm; + while ((m = mcomments.exec(code))) { + code = preserveToken(code, m, t++); + mcomments.lastIndex = m.index; + } + + const hcomments = /(^|\n)#[^\n\r#]+/g; + while ((m = hcomments.exec(code))) { + code = preserveToken(code, m, t++); + hcomments.lastIndex = m.index; + } + + // Remove regexes + const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi; + while ((m = regexes.exec(code))) { + code = preserveToken(code, m, t++); + regexes.lastIndex = m.index; + } + + code = code + // Create newlines after ; + .replace(/;/g, ";\n") + // Create newlines after { and around } + .replace(/{/g, "{\n") + .replace(/}/g, "\n}\n") + // Remove carriage returns + .replace(/\r/g, "") + // Remove all indentation + .replace(/^\s+/g, "") + .replace(/\n\s+/g, "\n") + // Remove trailing spaces + .replace(/\s*$/g, "") + .replace(/\n{/g, "{"); + + // Indent + let i = 0, + level = 0, + indent; + while (i < code.length) { + switch (code[i]) { + case "{": + level++; + break; + case "\n": + if (i+1 >= code.length) break; + + if (code[i+1] === "}") level--; + indent = (level >= 0) ? Array(level*4+1).join(" ") : ""; + + code = code.substring(0, i+1) + indent + code.substring(i+1); + if (level > 0) i += level*4; + break; + } + i++; + } + + code = code + // Add strategic spaces + .replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ") + .replace(/\s*<([=]?)\s*/g, " <$1 ") + .replace(/\s*>([=]?)\s*/g, " >$1 ") + .replace(/([^+])\+([^+=])/g, "$1 + $2") + .replace(/([^-])-([^-=])/g, "$1 - $2") + .replace(/([^*])\*([^*=])/g, "$1 * $2") + .replace(/([^/])\/([^/=])/g, "$1 / $2") + .replace(/\s*,\s*/g, ", ") + .replace(/\s*{/g, " {") + .replace(/}\n/g, "}\n\n") + // Hacky horribleness + .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3") + .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3") + .replace(/else\s*\n([^{])/gim, "else\n $1") + .replace(/else\s+([^{])/gim, "else $1") + // Remove strategic spaces + .replace(/\s+;/g, ";") + .replace(/\{\s+\}/g, "{}") + .replace(/\[\s+\]/g, "[]") + .replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1"); + + // Replace preserved tokens + const ptokens = /###preservedToken(\d+)###/g; + while ((m = ptokens.exec(code))) { + const ti = parseInt(m[1], 10); + code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length); + ptokens.lastIndex = m.index; + } + + return code; + + /** + * Replaces a matched token with a placeholder value. + */ + function preserveToken(str, match, t) { + preservedTokens[t] = match[0]; + return str.substring(0, match.index) + + "###preservedToken" + t + "###" + + str.substring(match.index + match[0].length); + } + } + +} + +export default GenericCodeBeautify; diff --git a/src/core/operations/GroupIPAddresses.mjs b/src/core/operations/GroupIPAddresses.mjs new file mode 100644 index 00000000..5e5d97c8 --- /dev/null +++ b/src/core/operations/GroupIPAddresses.mjs @@ -0,0 +1,137 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {IP_DELIM_OPTIONS} from "../lib/Delim"; +import {ipv6ToStr, genIpv6Mask, IPV4_REGEX, strToIpv6, ipv4ToStr, IPV6_REGEX, strToIpv4} from "../lib/IP"; + +/** + * Group IP addresses operation + */ +class GroupIPAddresses extends Operation { + + /** + * GroupIPAddresses constructor + */ + constructor() { + super(); + + this.name = "Group IP addresses"; + this.module = "Default"; + this.description = "Groups a list of IP addresses into subnets. Supports both IPv4 and IPv6 addresses."; + this.infoURL = "https://wikipedia.org/wiki/Subnetwork"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": IP_DELIM_OPTIONS + }, + { + "name": "Subnet (CIDR)", + "type": "number", + "value": 24 + }, + { + "name": "Only show the subnets", + "type": "boolean", + "value": false, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + cidr = args[1], + onlySubnets = args[2], + ipv4Mask = cidr < 32 ? ~(0xFFFFFFFF >>> cidr) : 0xFFFFFFFF, + ipv6Mask = genIpv6Mask(cidr), + ips = input.split(delim), + ipv4Networks = {}, + ipv6Networks = {}; + let match = null, + output = "", + ip = null, + network = null, + networkStr = "", + i; + + if (cidr < 0 || cidr > 127) { + throw new OperationError("CIDR must be less than 32 for IPv4 or 128 for IPv6"); + } + + // Parse all IPs and add to network dictionary + for (i = 0; i < ips.length; i++) { + if ((match = IPV4_REGEX.exec(ips[i]))) { + ip = strToIpv4(match[1]) >>> 0; + network = ip & ipv4Mask; + + if (ipv4Networks.hasOwnProperty(network)) { + ipv4Networks[network].push(ip); + } else { + ipv4Networks[network] = [ip]; + } + } else if ((match = IPV6_REGEX.exec(ips[i]))) { + ip = strToIpv6(match[1]); + network = []; + networkStr = ""; + + for (let j = 0; j < 8; j++) { + network.push(ip[j] & ipv6Mask[j]); + } + + networkStr = ipv6ToStr(network, true); + + if (ipv6Networks.hasOwnProperty(networkStr)) { + ipv6Networks[networkStr].push(ip); + } else { + ipv6Networks[networkStr] = [ip]; + } + } + } + + // Sort IPv4 network dictionaries and print + for (network in ipv4Networks) { + ipv4Networks[network] = ipv4Networks[network].sort(); + + output += ipv4ToStr(network) + "/" + cidr + "\n"; + + if (!onlySubnets) { + for (i = 0; i < ipv4Networks[network].length; i++) { + output += " " + ipv4ToStr(ipv4Networks[network][i]) + "\n"; + } + output += "\n"; + } + } + + // Sort IPv6 network dictionaries and print + for (networkStr in ipv6Networks) { + //ipv6Networks[networkStr] = ipv6Networks[networkStr].sort(); TODO + + output += networkStr + "/" + cidr + "\n"; + + if (!onlySubnets) { + for (i = 0; i < ipv6Networks[networkStr].length; i++) { + output += " " + ipv6ToStr(ipv6Networks[networkStr][i], true) + "\n"; + } + output += "\n"; + } + } + + return output; + } + +} + +export default GroupIPAddresses; diff --git a/src/core/operations/Gunzip.mjs b/src/core/operations/Gunzip.mjs new file mode 100644 index 00000000..fe514186 --- /dev/null +++ b/src/core/operations/Gunzip.mjs @@ -0,0 +1,51 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; + +const Zlib = zlibAndGzip.Zlib; + +/** + * Gunzip operation + */ +class Gunzip extends Operation { + + /** + * Gunzip constructor + */ + constructor() { + super(); + + this.name = "Gunzip"; + this.module = "Compression"; + this.description = "Decompresses data which has been compressed using the deflate algorithm with gzip headers."; + this.infoURL = "https://wikipedia.org/wiki/Gzip"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + this.patterns = [ + { + match: "^\\x1f\\x8b\\x08", + flags: "", + args: [] + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {File} + */ + run(input, args) { + const gunzip = new Zlib.Gunzip(new Uint8Array(input)); + return new Uint8Array(gunzip.decompress()).buffer; + } + +} + +export default Gunzip; diff --git a/src/core/operations/Gzip.mjs b/src/core/operations/Gzip.mjs new file mode 100644 index 00000000..e4a6c15f --- /dev/null +++ b/src/core/operations/Gzip.mjs @@ -0,0 +1,86 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; + +const Zlib = zlibAndGzip.Zlib; + +/** + * Gzip operation + */ +class Gzip extends Operation { + + /** + * Gzip constructor + */ + constructor() { + super(); + + this.name = "Gzip"; + this.module = "Compression"; + this.description = "Compresses data using the deflate algorithm with gzip headers."; + this.infoURL = "https://wikipedia.org/wiki/Gzip"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Compression type", + type: "option", + value: COMPRESSION_TYPE + }, + { + name: "Filename (optional)", + type: "string", + value: "" + }, + { + name: "Comment (optional)", + type: "string", + value: "" + }, + { + name: "Include file checksum", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const filename = args[1], + comment = args[2], + options = { + deflateOptions: { + compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] + }, + flags: { + fhcrc: args[3] + } + }; + + if (filename.length) { + options.flags.fname = true; + options.filename = filename; + } + if (comment.length) { + options.flags.fcommenct = true; + options.comment = comment; + } + + const gzip = new Zlib.Gzip(new Uint8Array(input), options); + return new Uint8Array(gzip.compress()).buffer; + } + +} + +export default Gzip; diff --git a/src/core/operations/HAS160.mjs b/src/core/operations/HAS160.mjs new file mode 100644 index 00000000..ab3db6a2 --- /dev/null +++ b/src/core/operations/HAS160.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * HAS-160 operation + */ +class HAS160 extends Operation { + + /** + * HAS-160 constructor + */ + constructor() { + super(); + + this.name = "HAS-160"; + this.module = "Crypto"; + this.description = "HAS-160 is a cryptographic hash function designed for use with the Korean KCDSA digital signature algorithm. It is derived from SHA-1, with assorted changes intended to increase its security. It produces a 160-bit output.

HAS-160 is used in the same way as SHA-1. First it divides input in blocks of 512 bits each and pads the final block. A digest function updates the intermediate hash value by processing the input blocks in turn.

The message digest algorithm consists of 80 rounds."; + this.infoURL = "https://wikipedia.org/wiki/HAS-160"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("has160", input); + } + +} + +export default HAS160; diff --git a/src/core/operations/HMAC.mjs b/src/core/operations/HMAC.mjs new file mode 100644 index 00000000..d511febb --- /dev/null +++ b/src/core/operations/HMAC.mjs @@ -0,0 +1,82 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import CryptoApi from "crypto-api/src/crypto-api"; + +/** + * HMAC operation + */ +class HMAC extends Operation { + + /** + * HMAC constructor + */ + constructor() { + super(); + + this.name = "HMAC"; + this.module = "Crypto"; + this.description = "Keyed-Hash Message Authentication Codes (HMAC) are a mechanism for message authentication using cryptographic hash functions."; + this.infoURL = "https://wikipedia.org/wiki/HMAC"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"] + }, + { + "name": "Hashing function", + "type": "option", + "value": [ + "MD2", + "MD4", + "MD5", + "SHA0", + "SHA1", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "SHA512/224", + "SHA512/256", + "RIPEMD128", + "RIPEMD160", + "RIPEMD256", + "RIPEMD320", + "HAS160", + "Whirlpool", + "Whirlpool-0", + "Whirlpool-T", + "Snefru" + ] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string || "", args[0].option), + hashFunc = args[1].toLowerCase(), + msg = Utils.arrayBufferToStr(input, false), + hasher = CryptoApi.getHasher(hashFunc); + + const mac = CryptoApi.getHmac(key, hasher); + mac.update(msg); + return CryptoApi.encoder.toHex(mac.finalize()); + } + +} + +export default HMAC; diff --git a/src/core/operations/HTML.js b/src/core/operations/HTML.js deleted file mode 100755 index 457124be..00000000 --- a/src/core/operations/HTML.js +++ /dev/null @@ -1,866 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * HTML operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const HTML = { - - /** - * @constant - * @default - */ - CONVERT_ALL: false, - /** - * @constant - * @default - */ - CONVERT_OPTIONS: ["Named entities where possible", "Numeric entities", "Hex entities"], - - /** - * To HTML Entity operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToEntity: function(input, args) { - let convertAll = args[0], - numeric = args[1] === "Numeric entities", - hexa = args[1] === "Hex entities"; - - const charcodes = Utils.strToCharcode(input); - let output = ""; - - for (let i = 0; i < charcodes.length; i++) { - if (convertAll && numeric) { - output += "&#" + charcodes[i] + ";"; - } else if (convertAll && hexa) { - output += "&#x" + Utils.hex(charcodes[i]) + ";"; - } else if (convertAll) { - output += HTML._byteToEntity[charcodes[i]] || "&#" + charcodes[i] + ";"; - } else if (numeric) { - if (charcodes[i] > 255 || HTML._byteToEntity.hasOwnProperty(charcodes[i])) { - output += "&#" + charcodes[i] + ";"; - } else { - output += Utils.chr(charcodes[i]); - } - } else if (hexa) { - if (charcodes[i] > 255 || HTML._byteToEntity.hasOwnProperty(charcodes[i])) { - output += "&#x" + Utils.hex(charcodes[i]) + ";"; - } else { - output += Utils.chr(charcodes[i]); - } - } else { - output += HTML._byteToEntity[charcodes[i]] || ( - charcodes[i] > 255 ? - "&#" + charcodes[i] + ";" : - Utils.chr(charcodes[i]) - ); - } - } - return output; - }, - - - /** - * From HTML Entity operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFromEntity: function(input, args) { - let regex = /&(#?x?[a-zA-Z0-9]{1,8});/g, - output = "", - m, - i = 0; - - while ((m = regex.exec(input))) { - // Add up to match - for (; i < m.index;) - output += input[i++]; - - // Add match - const bite = HTML._entityToByte[m[1]]; - if (bite) { - output += Utils.chr(bite); - } else if (!bite && m[1][0] === "#" && m[1].length > 1 && /^#\d{1,5}$/.test(m[1])) { - // Numeric entity (e.g. ) - const num = m[1].slice(1, m[1].length); - output += Utils.chr(parseInt(num, 10)); - } else if (!bite && m[1][0] === "#" && m[1].length > 3 && /^#x[\dA-F]{2,8}$/i.test(m[1])) { - // Hex entity (e.g. :) - const hex = m[1].slice(2, m[1].length); - output += Utils.chr(parseInt(hex, 16)); - } else { - // Not a valid entity, print as normal - for (; i < regex.lastIndex;) - output += input[i++]; - } - - i = regex.lastIndex; - } - // Add all after final match - for (; i < input.length;) - output += input[i++]; - - return output; - }, - - - /** - * @constant - * @default - */ - REMOVE_INDENTATION: true, - /** - * @constant - * @default - */ - REMOVE_LINE_BREAKS: true, - - /** - * Strip HTML tags operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runStripTags: function(input, args) { - let removeIndentation = args[0], - removeLineBreaks = args[1]; - - input = Utils.stripHtmlTags(input); - - if (removeIndentation) { - input = input.replace(/\n[ \f\t]+/g, "\n"); - } - - if (removeLineBreaks) { - input = input.replace(/^\s*\n/, "") // first line - .replace(/(\n\s*){2,}/g, "\n"); // all others - } - - return input; - }, - - - /** - * Parse colour code operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runParseColourCode: function(input, args) { - let m = null, - r = 0, g = 0, b = 0, a = 1; - - // Read in the input - if ((m = input.match(/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i))) { - // Hex - #d9edf7 - r = parseInt(m[1], 16); - g = parseInt(m[2], 16); - b = parseInt(m[3], 16); - } else if ((m = input.match(/rgba?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)(?:,\s?(\d(?:\.\d+)?))?\)/i))) { - // RGB or RGBA - rgb(217,237,247) or rgba(217,237,247,1) - r = parseFloat(m[1]); - g = parseFloat(m[2]); - b = parseFloat(m[3]); - a = m[4] ? parseFloat(m[4]) : 1; - } else if ((m = input.match(/hsla?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)%,\s?(\d{1,3}(?:\.\d+)?)%(?:,\s?(\d(?:\.\d+)?))?\)/i))) { - // HSL or HSLA - hsl(200, 65%, 91%) or hsla(200, 65%, 91%, 1) - let h_ = parseFloat(m[1]) / 360, - s_ = parseFloat(m[2]) / 100, - l_ = parseFloat(m[3]) / 100, - rgb_ = HTML._hslToRgb(h_, s_, l_); - - r = rgb_[0]; - g = rgb_[1]; - b = rgb_[2]; - a = m[4] ? parseFloat(m[4]) : 1; - } else if ((m = input.match(/cmyk\((\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?)\)/i))) { - // CMYK - cmyk(0.12, 0.04, 0.00, 0.03) - let c_ = parseFloat(m[1]), - m_ = parseFloat(m[2]), - y_ = parseFloat(m[3]), - k_ = parseFloat(m[4]); - - r = Math.round(255 * (1 - c_) * (1 - k_)); - g = Math.round(255 * (1 - m_) * (1 - k_)); - b = Math.round(255 * (1 - y_) * (1 - k_)); - } - - let hsl_ = HTML._rgbToHsl(r, g, b), - h = Math.round(hsl_[0] * 360), - s = Math.round(hsl_[1] * 100), - l = Math.round(hsl_[2] * 100), - k = 1 - Math.max(r/255, g/255, b/255), - c = (1 - r/255 - k) / (1 - k), - y = (1 - b/255 - k) / (1 - k); - - m = (1 - g/255 - k) / (1 - k); - - c = isNaN(c) ? "0" : c.toFixed(2); - m = isNaN(m) ? "0" : m.toFixed(2); - y = isNaN(y) ? "0" : y.toFixed(2); - k = k.toFixed(2); - - let hex = "#" + - Utils.padLeft(Math.round(r).toString(16), 2) + - Utils.padLeft(Math.round(g).toString(16), 2) + - Utils.padLeft(Math.round(b).toString(16), 2), - rgb = "rgb(" + r + ", " + g + ", " + b + ")", - rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")", - hsl = "hsl(" + h + ", " + s + "%, " + l + "%)", - hsla = "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")", - cmyk = "cmyk(" + c + ", " + m + ", " + y + ", " + k + ")"; - - // Generate output - return "
" + - "Hex: " + hex + "\n" + - "RGB: " + rgb + "\n" + - "RGBA: " + rgba + "\n" + - "HSL: " + hsl + "\n" + - "HSLA: " + hsla + "\n" + - "CMYK: " + cmyk + - ""; - }, - - - /** - * Converts an HSL color value to RGB. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. - * Assumes h, s, and l are contained in the set [0, 1] and - * returns r, g, and b in the set [0, 255]. - * - * @private - * @author Mohsen (http://stackoverflow.com/a/9493060) - * - * @param {number} h - The hue - * @param {number} s - The saturation - * @param {number} l - The lightness - * @return {Array} The RGB representation - */ - _hslToRgb: function(h, s, l){ - let r, g, b; - - if (s === 0){ - r = g = b = l; // achromatic - } else { - const hue2rgb = function hue2rgb(p, q, t) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1/6) return p + (q - p) * 6 * t; - if (t < 1/2) return q; - if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; - return p; - }; - - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); - } - - return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; - }, - - - /** - * Converts an RGB color value to HSL. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. - * Assumes r, g, and b are contained in the set [0, 255] and - * returns h, s, and l in the set [0, 1]. - * - * @private - * @author Mohsen (http://stackoverflow.com/a/9493060) - * - * @param {number} r - The red color value - * @param {number} g - The green color value - * @param {number} b - The blue color value - * @return {Array} The HSL representation - */ - _rgbToHsl: function(r, g, b) { - r /= 255; g /= 255; b /= 255; - let max = Math.max(r, g, b), - min = Math.min(r, g, b), - h, s, l = (max + min) / 2; - - if (max === min) { - h = s = 0; // achromatic - } else { - const d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: h = (g - b) / d + (g < b ? 6 : 0); break; - case g: h = (b - r) / d + 2; break; - case b: h = (r - g) / d + 4; break; - } - h /= 6; - } - - return [h, s, l]; - }, - - - /** - * Lookup table to translate byte values to their HTML entity codes. - * - * @private - * @constant - */ - _byteToEntity: { - 34 : """, - 38 : "&", - 39 : "'", - 60 : "<", - 62 : ">", - 160 : " ", - 161 : "¡", - 162 : "¢", - 163 : "£", - 164 : "¤", - 165 : "¥", - 166 : "¦", - 167 : "§", - 168 : "¨", - 169 : "©", - 170 : "ª", - 171 : "«", - 172 : "¬", - 173 : "­", - 174 : "®", - 175 : "¯", - 176 : "°", - 177 : "±", - 178 : "²", - 179 : "³", - 180 : "´", - 181 : "µ", - 182 : "¶", - 183 : "·", - 184 : "¸", - 185 : "¹", - 186 : "º", - 187 : "»", - 188 : "¼", - 189 : "½", - 190 : "¾", - 191 : "¿", - 192 : "À", - 193 : "Á", - 194 : "Â", - 195 : "Ã", - 196 : "Ä", - 197 : "Å", - 198 : "Æ", - 199 : "Ç", - 200 : "È", - 201 : "É", - 202 : "Ê", - 203 : "Ë", - 204 : "Ì", - 205 : "Í", - 206 : "Î", - 207 : "Ï", - 208 : "Ð", - 209 : "Ñ", - 210 : "Ò", - 211 : "Ó", - 212 : "Ô", - 213 : "Õ", - 214 : "Ö", - 215 : "×", - 216 : "Ø", - 217 : "Ù", - 218 : "Ú", - 219 : "Û", - 220 : "Ü", - 221 : "Ý", - 222 : "Þ", - 223 : "ß", - 224 : "à", - 225 : "á", - 226 : "â", - 227 : "ã", - 228 : "ä", - 229 : "å", - 230 : "æ", - 231 : "ç", - 232 : "è", - 233 : "é", - 234 : "ê", - 235 : "ë", - 236 : "ì", - 237 : "í", - 238 : "î", - 239 : "ï", - 240 : "ð", - 241 : "ñ", - 242 : "ò", - 243 : "ó", - 244 : "ô", - 245 : "õ", - 246 : "ö", - 247 : "÷", - 248 : "ø", - 249 : "ù", - 250 : "ú", - 251 : "û", - 252 : "ü", - 253 : "ý", - 254 : "þ", - 255 : "ÿ", - 338 : "Œ", - 339 : "œ", - 352 : "Š", - 353 : "š", - 376 : "Ÿ", - 402 : "ƒ", - 710 : "ˆ", - 732 : "˜", - 913 : "Α", - 914 : "Β", - 915 : "Γ", - 916 : "Δ", - 917 : "Ε", - 918 : "Ζ", - 919 : "Η", - 920 : "Θ", - 921 : "Ι", - 922 : "Κ", - 923 : "Λ", - 924 : "Μ", - 925 : "Ν", - 926 : "Ξ", - 927 : "Ο", - 928 : "Π", - 929 : "Ρ", - 931 : "Σ", - 932 : "Τ", - 933 : "Υ", - 934 : "Φ", - 935 : "Χ", - 936 : "Ψ", - 937 : "Ω", - 945 : "α", - 946 : "β", - 947 : "γ", - 948 : "δ", - 949 : "ε", - 950 : "ζ", - 951 : "η", - 952 : "θ", - 953 : "ι", - 954 : "κ", - 955 : "λ", - 956 : "μ", - 957 : "ν", - 958 : "ξ", - 959 : "ο", - 960 : "π", - 961 : "ρ", - 962 : "ς", - 963 : "σ", - 964 : "τ", - 965 : "υ", - 966 : "φ", - 967 : "χ", - 968 : "ψ", - 969 : "ω", - 977 : "ϑ", - 978 : "ϒ", - 982 : "ϖ", - 8194 : " ", - 8195 : " ", - 8201 : " ", - 8204 : "‌", - 8205 : "‍", - 8206 : "‎", - 8207 : "‏", - 8211 : "–", - 8212 : "—", - 8216 : "‘", - 8217 : "’", - 8218 : "‚", - 8220 : "“", - 8221 : "”", - 8222 : "„", - 8224 : "†", - 8225 : "‡", - 8226 : "•", - 8230 : "…", - 8240 : "‰", - 8242 : "′", - 8243 : "″", - 8249 : "‹", - 8250 : "›", - 8254 : "‾", - 8260 : "⁄", - 8364 : "€", - 8465 : "ℑ", - 8472 : "℘", - 8476 : "ℜ", - 8482 : "™", - 8501 : "ℵ", - 8592 : "←", - 8593 : "↑", - 8594 : "→", - 8595 : "↓", - 8596 : "↔", - 8629 : "↵", - 8656 : "⇐", - 8657 : "⇑", - 8658 : "⇒", - 8659 : "⇓", - 8660 : "⇔", - 8704 : "∀", - 8706 : "∂", - 8707 : "∃", - 8709 : "∅", - 8711 : "∇", - 8712 : "∈", - 8713 : "∉", - 8715 : "∋", - 8719 : "∏", - 8721 : "∑", - 8722 : "−", - 8727 : "∗", - 8730 : "√", - 8733 : "∝", - 8734 : "∞", - 8736 : "∠", - 8743 : "∧", - 8744 : "∨", - 8745 : "∩", - 8746 : "∪", - 8747 : "∫", - 8756 : "∴", - 8764 : "∼", - 8773 : "≅", - 8776 : "≈", - 8800 : "≠", - 8801 : "≡", - 8804 : "≤", - 8805 : "≥", - 8834 : "⊂", - 8835 : "⊃", - 8836 : "⊄", - 8838 : "⊆", - 8839 : "⊇", - 8853 : "⊕", - 8855 : "⊗", - 8869 : "⊥", - 8901 : "⋅", - 8942 : "⋮", - 8968 : "⌈", - 8969 : "⌉", - 8970 : "⌊", - 8971 : "⌋", - 9001 : "⟨", - 9002 : "⟩", - 9674 : "◊", - 9824 : "♠", - 9827 : "♣", - 9829 : "♥", - 9830 : "♦", - }, - - - /** - * Lookup table to translate HTML entity codes to their byte values. - * - * @private - * @constant - */ - _entityToByte : { - "quot" : 34, - "amp" : 38, - "apos" : 39, - "lt" : 60, - "gt" : 62, - "nbsp" : 160, - "iexcl" : 161, - "cent" : 162, - "pound" : 163, - "curren" : 164, - "yen" : 165, - "brvbar" : 166, - "sect" : 167, - "uml" : 168, - "copy" : 169, - "ordf" : 170, - "laquo" : 171, - "not" : 172, - "shy" : 173, - "reg" : 174, - "macr" : 175, - "deg" : 176, - "plusmn" : 177, - "sup2" : 178, - "sup3" : 179, - "acute" : 180, - "micro" : 181, - "para" : 182, - "middot" : 183, - "cedil" : 184, - "sup1" : 185, - "ordm" : 186, - "raquo" : 187, - "frac14" : 188, - "frac12" : 189, - "frac34" : 190, - "iquest" : 191, - "Agrave" : 192, - "Aacute" : 193, - "Acirc" : 194, - "Atilde" : 195, - "Auml" : 196, - "Aring" : 197, - "AElig" : 198, - "Ccedil" : 199, - "Egrave" : 200, - "Eacute" : 201, - "Ecirc" : 202, - "Euml" : 203, - "Igrave" : 204, - "Iacute" : 205, - "Icirc" : 206, - "Iuml" : 207, - "ETH" : 208, - "Ntilde" : 209, - "Ograve" : 210, - "Oacute" : 211, - "Ocirc" : 212, - "Otilde" : 213, - "Ouml" : 214, - "times" : 215, - "Oslash" : 216, - "Ugrave" : 217, - "Uacute" : 218, - "Ucirc" : 219, - "Uuml" : 220, - "Yacute" : 221, - "THORN" : 222, - "szlig" : 223, - "agrave" : 224, - "aacute" : 225, - "acirc" : 226, - "atilde" : 227, - "auml" : 228, - "aring" : 229, - "aelig" : 230, - "ccedil" : 231, - "egrave" : 232, - "eacute" : 233, - "ecirc" : 234, - "euml" : 235, - "igrave" : 236, - "iacute" : 237, - "icirc" : 238, - "iuml" : 239, - "eth" : 240, - "ntilde" : 241, - "ograve" : 242, - "oacute" : 243, - "ocirc" : 244, - "otilde" : 245, - "ouml" : 246, - "divide" : 247, - "oslash" : 248, - "ugrave" : 249, - "uacute" : 250, - "ucirc" : 251, - "uuml" : 252, - "yacute" : 253, - "thorn" : 254, - "yuml" : 255, - "OElig" : 338, - "oelig" : 339, - "Scaron" : 352, - "scaron" : 353, - "Yuml" : 376, - "fnof" : 402, - "circ" : 710, - "tilde" : 732, - "Alpha" : 913, - "Beta" : 914, - "Gamma" : 915, - "Delta" : 916, - "Epsilon" : 917, - "Zeta" : 918, - "Eta" : 919, - "Theta" : 920, - "Iota" : 921, - "Kappa" : 922, - "Lambda" : 923, - "Mu" : 924, - "Nu" : 925, - "Xi" : 926, - "Omicron" : 927, - "Pi" : 928, - "Rho" : 929, - "Sigma" : 931, - "Tau" : 932, - "Upsilon" : 933, - "Phi" : 934, - "Chi" : 935, - "Psi" : 936, - "Omega" : 937, - "alpha" : 945, - "beta" : 946, - "gamma" : 947, - "delta" : 948, - "epsilon" : 949, - "zeta" : 950, - "eta" : 951, - "theta" : 952, - "iota" : 953, - "kappa" : 954, - "lambda" : 955, - "mu" : 956, - "nu" : 957, - "xi" : 958, - "omicron" : 959, - "pi" : 960, - "rho" : 961, - "sigmaf" : 962, - "sigma" : 963, - "tau" : 964, - "upsilon" : 965, - "phi" : 966, - "chi" : 967, - "psi" : 968, - "omega" : 969, - "thetasym" : 977, - "upsih" : 978, - "piv" : 982, - "ensp" : 8194, - "emsp" : 8195, - "thinsp" : 8201, - "zwnj" : 8204, - "zwj" : 8205, - "lrm" : 8206, - "rlm" : 8207, - "ndash" : 8211, - "mdash" : 8212, - "lsquo" : 8216, - "rsquo" : 8217, - "sbquo" : 8218, - "ldquo" : 8220, - "rdquo" : 8221, - "bdquo" : 8222, - "dagger" : 8224, - "Dagger" : 8225, - "bull" : 8226, - "hellip" : 8230, - "permil" : 8240, - "prime" : 8242, - "Prime" : 8243, - "lsaquo" : 8249, - "rsaquo" : 8250, - "oline" : 8254, - "frasl" : 8260, - "euro" : 8364, - "image" : 8465, - "weierp" : 8472, - "real" : 8476, - "trade" : 8482, - "alefsym" : 8501, - "larr" : 8592, - "uarr" : 8593, - "rarr" : 8594, - "darr" : 8595, - "harr" : 8596, - "crarr" : 8629, - "lArr" : 8656, - "uArr" : 8657, - "rArr" : 8658, - "dArr" : 8659, - "hArr" : 8660, - "forall" : 8704, - "part" : 8706, - "exist" : 8707, - "empty" : 8709, - "nabla" : 8711, - "isin" : 8712, - "notin" : 8713, - "ni" : 8715, - "prod" : 8719, - "sum" : 8721, - "minus" : 8722, - "lowast" : 8727, - "radic" : 8730, - "prop" : 8733, - "infin" : 8734, - "ang" : 8736, - "and" : 8743, - "or" : 8744, - "cap" : 8745, - "cup" : 8746, - "int" : 8747, - "there4" : 8756, - "sim" : 8764, - "cong" : 8773, - "asymp" : 8776, - "ne" : 8800, - "equiv" : 8801, - "le" : 8804, - "ge" : 8805, - "sub" : 8834, - "sup" : 8835, - "nsub" : 8836, - "sube" : 8838, - "supe" : 8839, - "oplus" : 8853, - "otimes" : 8855, - "perp" : 8869, - "sdot" : 8901, - "vellip" : 8942, - "lceil" : 8968, - "rceil" : 8969, - "lfloor" : 8970, - "rfloor" : 8971, - "lang" : 9001, - "rang" : 9002, - "loz" : 9674, - "spades" : 9824, - "clubs" : 9827, - "hearts" : 9829, - "diams" : 9830, - }, - - /** - * HTML to text operation - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runHTMLToText(input, args) { - return input; - }, -}; - -export default HTML; diff --git a/src/core/operations/HTTP.js b/src/core/operations/HTTP.js deleted file mode 100755 index 28c41ad5..00000000 --- a/src/core/operations/HTTP.js +++ /dev/null @@ -1,56 +0,0 @@ -import {UAS_parser as UAParser} from "../lib/uas_parser.js"; - - -/** - * HTTP operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const HTTP = { - - /** - * Strip HTTP headers operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runStripHeaders: function(input, args) { - let headerEnd = input.indexOf("\r\n\r\n"); - headerEnd = (headerEnd < 0) ? input.indexOf("\n\n") + 2 : headerEnd + 4; - - return (headerEnd < 2) ? input : input.slice(headerEnd, input.length); - }, - - - /** - * Parse User Agent operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseUserAgent: function(input, args) { - const ua = UAParser.parse(input); - - return "Type: " + ua.type + "\n" + - "Family: " + ua.uaFamily + "\n" + - "Name: " + ua.uaName + "\n" + - "URL: " + ua.uaUrl + "\n" + - "Company: " + ua.uaCompany + "\n" + - "Company URL: " + ua.uaCompanyUrl + "\n\n" + - "OS Family: " + ua.osFamily + "\n" + - "OS Name: " + ua.osName + "\n" + - "OS URL: " + ua.osUrl + "\n" + - "OS Company: " + ua.osCompany + "\n" + - "OS Company URL: " + ua.osCompanyUrl + "\n" + - "Device Type: " + ua.deviceType + "\n"; - }, - -}; - -export default HTTP; diff --git a/src/core/operations/HTTPRequest.mjs b/src/core/operations/HTTPRequest.mjs new file mode 100644 index 00000000..52029930 --- /dev/null +++ b/src/core/operations/HTTPRequest.mjs @@ -0,0 +1,147 @@ +/** + * @author tlwr [toby@toby.codes] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * HTTP request operation + */ +class HTTPRequest extends Operation { + + /** + * HTTPRequest constructor + */ + constructor() { + super(); + + this.name = "HTTP request"; + this.module = "Default"; + this.description = [ + "Makes an HTTP request and returns the response.", + "

", + "This operation supports different HTTP verbs like GET, POST, PUT, etc.", + "

", + "You can add headers line by line in the format Key: Value", + "

", + "The status code of the response, along with a limited selection of exposed headers, can be viewed by checking the 'Show response metadata' option. Only a limited set of response headers are exposed by the browser for security reasons.", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields"; + this.inputType = "string"; + this.outputType = "string"; + this.manualBake = true; + this.args = [ + { + "name": "Method", + "type": "option", + "value": [ + "GET", "POST", "HEAD", + "PUT", "PATCH", "DELETE", + "CONNECT", "TRACE", "OPTIONS" + ] + }, + { + "name": "URL", + "type": "string", + "value": "" + }, + { + "name": "Headers", + "type": "text", + "value": "" + }, + { + "name": "Mode", + "type": "option", + "value": [ + "Cross-Origin Resource Sharing", + "No CORS (limited to HEAD, GET or POST)", + ] + }, + { + "name": "Show response metadata", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [method, url, headersText, mode, showResponseMetadata] = args; + + if (url.length === 0) return ""; + + const headers = new Headers(); + headersText.split(/\r?\n/).forEach(line => { + line = line.trim(); + + if (line.length === 0) return; + + const split = line.split(":"); + if (split.length !== 2) throw `Could not parse header in line: ${line}`; + + headers.set(split[0].trim(), split[1].trim()); + }); + + const config = { + method: method, + headers: headers, + mode: modeLookup[mode], + cache: "no-cache", + }; + + if (method !== "GET" && method !== "HEAD") { + config.body = input; + } + + return fetch(url, config) + .then(r => { + if (r.status === 0 && r.type === "opaque") { + throw new OperationError("Error: Null response. Try setting the connection mode to CORS."); + } + + if (showResponseMetadata) { + let headers = ""; + for (const pair of r.headers.entries()) { + headers += " " + pair[0] + ": " + pair[1] + "\n"; + } + return r.text().then(b => { + return "####\n Status: " + r.status + " " + r.statusText + + "\n Exposed headers:\n" + headers + "####\n\n" + b; + }); + } + return r.text(); + }) + .catch(e => { + throw new OperationError(e.toString() + + "\n\nThis error could be caused by one of the following:\n" + + " - An invalid URL\n" + + " - Making a request to an insecure resource (HTTP) from a secure source (HTTPS)\n" + + " - Making a cross-origin request to a server which does not support CORS\n"); + }); + } + +} + + +/** + * Lookup table for HTTP modes + * + * @private + */ +const modeLookup = { + "Cross-Origin Resource Sharing": "cors", + "No CORS (limited to HEAD, GET or POST)": "no-cors", +}; + + +export default HTTPRequest; diff --git a/src/core/operations/HammingDistance.mjs b/src/core/operations/HammingDistance.mjs new file mode 100644 index 00000000..87c5d222 --- /dev/null +++ b/src/core/operations/HammingDistance.mjs @@ -0,0 +1,98 @@ +/** + * @author GCHQ Contributor [2] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {fromHex} from "../lib/Hex"; +import OperationError from "../errors/OperationError"; + +/** + * Hamming Distance operation + */ +class HammingDistance extends Operation { + + /** + * HammingDistance constructor + */ + constructor() { + super(); + + this.name = "Hamming Distance"; + this.module = "Default"; + this.description = "In information theory, the Hamming distance between two strings of equal length is the number of positions at which the corresponding symbols are different. In other words, it measures the minimum number of substitutions required to change one string into the other, or the minimum number of errors that could have transformed one string into the other. In a more general context, the Hamming distance is one of several string metrics for measuring the edit distance between two sequences."; + this.infoURL = "https://wikipedia.org/wiki/Hamming_distance"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "binaryShortString", + "value": "\\n\\n" + }, + { + "name": "Unit", + "type": "option", + "value": ["Byte", "Bit"] + }, + { + "name": "Input type", + "type": "option", + "value": ["Raw string", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = args[0], + byByte = args[1] === "Byte", + inputType = args[2], + samples = input.split(delim); + + if (samples.length !== 2) { + throw new OperationError("Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter."); + } + + if (samples[0].length !== samples[1].length) { + throw new OperationError("Error: Both inputs must be of the same length."); + } + + if (inputType === "Hex") { + samples[0] = fromHex(samples[0]); + samples[1] = fromHex(samples[1]); + } else { + samples[0] = Utils.strToByteArray(samples[0]); + samples[1] = Utils.strToByteArray(samples[1]); + } + + let dist = 0; + + for (let i = 0; i < samples[0].length; i++) { + const lhs = samples[0][i], + rhs = samples[1][i]; + + if (byByte && lhs !== rhs) { + dist++; + } else if (!byByte) { + let xord = lhs ^ rhs; + + while (xord) { + dist++; + xord &= xord - 1; + } + } + } + + return dist.toString(); + } + +} + +export default HammingDistance; diff --git a/src/core/operations/Hash.js b/src/core/operations/Hash.js deleted file mode 100755 index 7f713f53..00000000 --- a/src/core/operations/Hash.js +++ /dev/null @@ -1,389 +0,0 @@ -import Utils from "../Utils.js"; -import CryptoJS from "crypto-js"; -import CryptoApi from "crypto-api"; -import Checksum from "./Checksum.js"; - - -/** - * Hashing operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Hash = { - - /** - * MD2 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runMD2: function (input, args) { - return Utils.toHexFast(CryptoApi.hash("md2", input, {})); - }, - - - /** - * MD4 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runMD4: function (input, args) { - return Utils.toHexFast(CryptoApi.hash("md4", input, {})); - }, - - - /** - * MD5 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runMD5: function (input, args) { - input = CryptoJS.enc.Latin1.parse(input); // Cast to WordArray - return CryptoJS.MD5(input).toString(CryptoJS.enc.Hex); - }, - - - /** - * SHA0 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSHA0: function (input, args) { - return Utils.toHexFast(CryptoApi.hash("sha0", input, {})); - }, - - - /** - * SHA1 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSHA1: function (input, args) { - input = CryptoJS.enc.Latin1.parse(input); - return CryptoJS.SHA1(input).toString(CryptoJS.enc.Hex); - }, - - - /** - * SHA224 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSHA224: function (input, args) { - input = CryptoJS.enc.Latin1.parse(input); - return CryptoJS.SHA224(input).toString(CryptoJS.enc.Hex); - }, - - - /** - * SHA256 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSHA256: function (input, args) { - input = CryptoJS.enc.Latin1.parse(input); - return CryptoJS.SHA256(input).toString(CryptoJS.enc.Hex); - }, - - - /** - * SHA384 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSHA384: function (input, args) { - input = CryptoJS.enc.Latin1.parse(input); - return CryptoJS.SHA384(input).toString(CryptoJS.enc.Hex); - }, - - - /** - * SHA512 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSHA512: function (input, args) { - input = CryptoJS.enc.Latin1.parse(input); - return CryptoJS.SHA512(input).toString(CryptoJS.enc.Hex); - }, - - - /** - * @constant - * @default - */ - SHA3_LENGTH: ["512", "384", "256", "224"], - - /** - * SHA3 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSHA3: function (input, args) { - input = CryptoJS.enc.Latin1.parse(input); - let sha3Length = args[0], - options = { - outputLength: parseInt(sha3Length, 10) - }; - return CryptoJS.SHA3(input, options).toString(CryptoJS.enc.Hex); - }, - - - /** - * RIPEMD-160 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRIPEMD160: function (input, args) { - input = CryptoJS.enc.Latin1.parse(input); - return CryptoJS.RIPEMD160(input).toString(CryptoJS.enc.Hex); - }, - - - /** - * @constant - * @default - */ - HMAC_FUNCTIONS: ["MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "RIPEMD-160"], - - /** - * HMAC operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runHMAC: function (input, args) { - const hashFunc = args[1]; - input = CryptoJS.enc.Latin1.parse(input); - const execute = { - "MD5": CryptoJS.HmacMD5(input, args[0]), - "SHA1": CryptoJS.HmacSHA1(input, args[0]), - "SHA224": CryptoJS.HmacSHA224(input, args[0]), - "SHA256": CryptoJS.HmacSHA256(input, args[0]), - "SHA384": CryptoJS.HmacSHA384(input, args[0]), - "SHA512": CryptoJS.HmacSHA512(input, args[0]), - "SHA3": CryptoJS.HmacSHA3(input, args[0]), - "RIPEMD-160": CryptoJS.HmacRIPEMD160(input, args[0]), - }; - return execute[hashFunc].toString(CryptoJS.enc.Hex); - }, - - - /** - * Generate all hashes operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAll: function (input, args) { - let byteArray = Utils.strToByteArray(input), - output = "MD2: " + Hash.runMD2(input, []) + - "\nMD4: " + Hash.runMD4(input, []) + - "\nMD5: " + Hash.runMD5(input, []) + - "\nSHA0: " + Hash.runSHA0(input, []) + - "\nSHA1: " + Hash.runSHA1(input, []) + - "\nSHA2 224: " + Hash.runSHA224(input, []) + - "\nSHA2 256: " + Hash.runSHA256(input, []) + - "\nSHA2 384: " + Hash.runSHA384(input, []) + - "\nSHA2 512: " + Hash.runSHA512(input, []) + - "\nSHA3 224: " + Hash.runSHA3(input, ["224"]) + - "\nSHA3 256: " + Hash.runSHA3(input, ["256"]) + - "\nSHA3 384: " + Hash.runSHA3(input, ["384"]) + - "\nSHA3 512: " + Hash.runSHA3(input, ["512"]) + - "\nRIPEMD-160: " + Hash.runRIPEMD160(input, []) + - "\n\nChecksums:" + - "\nFletcher-8: " + Checksum.runFletcher8(byteArray, []) + - "\nFletcher-16: " + Checksum.runFletcher16(byteArray, []) + - "\nFletcher-32: " + Checksum.runFletcher32(byteArray, []) + - "\nFletcher-64: " + Checksum.runFletcher64(byteArray, []) + - "\nAdler-32: " + Checksum.runAdler32(byteArray, []) + - "\nCRC-32: " + Checksum.runCRC32(byteArray, []); - - return output; - }, - - - /** - * Analyse hash operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAnalyse: function(input, args) { - input = input.replace(/\s/g, ""); - - let output = "", - byteLength = input.length / 2, - bitLength = byteLength * 8, - possibleHashFunctions = []; - - if (!/^[a-f0-9]+$/i.test(input)) { - return "Invalid hash"; - } - - output += "Hash length: " + input.length + "\n" + - "Byte length: " + byteLength + "\n" + - "Bit length: " + bitLength + "\n\n" + - "Based on the length, this hash could have been generated by one of the following hashing functions:\n"; - - switch (bitLength) { - case 4: - possibleHashFunctions = [ - "Fletcher-4", - "Luhn algorithm", - "Verhoeff algorithm", - ]; - break; - case 8: - possibleHashFunctions = [ - "Fletcher-8", - ]; - break; - case 16: - possibleHashFunctions = [ - "BSD checksum", - "CRC-16", - "SYSV checksum", - "Fletcher-16" - ]; - break; - case 32: - possibleHashFunctions = [ - "CRC-32", - "Fletcher-32", - "Adler-32", - ]; - break; - case 64: - possibleHashFunctions = [ - "CRC-64", - "RIPEMD-64", - "SipHash", - ]; - break; - case 128: - possibleHashFunctions = [ - "MD5", - "MD4", - "MD2", - "HAVAL-128", - "RIPEMD-128", - "Snefru", - "Tiger-128", - ]; - break; - case 160: - possibleHashFunctions = [ - "SHA-1", - "SHA-0", - "FSB-160", - "HAS-160", - "HAVAL-160", - "RIPEMD-160", - "Tiger-160", - ]; - break; - case 192: - possibleHashFunctions = [ - "Tiger", - "HAVAL-192", - ]; - break; - case 224: - possibleHashFunctions = [ - "SHA-224", - "SHA3-224", - "ECOH-224", - "FSB-224", - "HAVAL-224", - ]; - break; - case 256: - possibleHashFunctions = [ - "SHA-256", - "SHA3-256", - "BLAKE-256", - "ECOH-256", - "FSB-256", - "GOST", - "Grøstl-256", - "HAVAL-256", - "PANAMA", - "RIPEMD-256", - "Snefru", - ]; - break; - case 320: - possibleHashFunctions = [ - "RIPEMD-320", - ]; - break; - case 384: - possibleHashFunctions = [ - "SHA-384", - "SHA3-384", - "ECOH-384", - "FSB-384", - ]; - break; - case 512: - possibleHashFunctions = [ - "SHA-512", - "SHA3-512", - "BLAKE-512", - "ECOH-512", - "FSB-512", - "Grøstl-512", - "JH", - "MD6", - "Spectral Hash", - "SWIFFT", - "Whirlpool", - ]; - break; - case 1024: - possibleHashFunctions = [ - "Fowler-Noll-Vo", - ]; - break; - default: - possibleHashFunctions = [ - "Unknown" - ]; - break; - } - - return output + possibleHashFunctions.join("\n"); - }, - -}; - -export default Hash; diff --git a/src/core/operations/HaversineDistance.mjs b/src/core/operations/HaversineDistance.mjs new file mode 100644 index 00000000..d5c0bf60 --- /dev/null +++ b/src/core/operations/HaversineDistance.mjs @@ -0,0 +1,59 @@ +/** + * @author Dachande663 [dachande663@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * HaversineDistance operation + */ +class HaversineDistance extends Operation { + + /** + * HaversineDistance constructor + */ + constructor() { + super(); + + this.name = "Haversine distance"; + this.module = "Default"; + this.description = "Returns the distance between two pairs of GPS latitude and longitude co-ordinates in metres.

e.g. 51.487263,-0.124323, 38.9517,-77.1467"; + this.infoURL = "https://wikipedia.org/wiki/Haversine_formula"; + this.inputType = "string"; + this.outputType = "number"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + + const values = input.match(/^(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?)$/); + if (!values) { + throw new OperationError("Input must in the format lat1, lng1, lat2, lng2"); + } + + const lat1 = parseFloat(values[1]); + const lng1 = parseFloat(values[3]); + const lat2 = parseFloat(values[6]); + const lng2 = parseFloat(values[8]); + + const TO_RAD = Math.PI / 180; + const dLat = (lat2-lat1) * TO_RAD; + const dLng = (lng2-lng1) * TO_RAD; + const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * TO_RAD) * Math.cos(lat2 * TO_RAD) * Math.sin(dLng/2) * Math.sin(dLng/2); + const metres = 6371000 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + + return metres; + + } + +} + +export default HaversineDistance; diff --git a/src/core/operations/Head.mjs b/src/core/operations/Head.mjs new file mode 100644 index 00000000..76e17c59 --- /dev/null +++ b/src/core/operations/Head.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * Head operation + */ +class Head extends Operation { + + /** + * Head constructor + */ + constructor() { + super(); + + this.name = "Head"; + this.module = "Default"; + this.description = "Like the UNIX head utility.
Gets the first n lines.
You can select all but the last n lines by entering a negative value for n.
The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "Number", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let delimiter = args[0]; + const number = args[1]; + + delimiter = Utils.charRep(delimiter); + const splitInput = input.split(delimiter); + + return splitInput + .filter((line, lineIndex) => { + lineIndex += 1; + + if (number < 0) { + return lineIndex <= splitInput.length + number; + } else { + return lineIndex <= number; + } + }) + .join(delimiter); + } + +} + +export default Head; diff --git a/src/core/operations/HexToObjectIdentifier.mjs b/src/core/operations/HexToObjectIdentifier.mjs new file mode 100644 index 00000000..aea5dd24 --- /dev/null +++ b/src/core/operations/HexToObjectIdentifier.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Hex to Object Identifier operation + */ +class HexToObjectIdentifier extends Operation { + + /** + * HexToObjectIdentifier constructor + */ + constructor() { + super(); + + this.name = "Hex to Object Identifier"; + this.module = "PublicKey"; + this.description = "Converts a hexadecimal string into an object identifier (OID)."; + this.infoURL = "https://wikipedia.org/wiki/Object_identifier"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.oidHexToInt(input.replace(/\s/g, "")); + } + +} + +export default HexToObjectIdentifier; diff --git a/src/core/operations/HexToPEM.mjs b/src/core/operations/HexToPEM.mjs new file mode 100644 index 00000000..b0845685 --- /dev/null +++ b/src/core/operations/HexToPEM.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Hex to PEM operation + */ +class HexToPEM extends Operation { + + /** + * HexToPEM constructor + */ + constructor() { + super(); + + this.name = "Hex to PEM"; + this.module = "PublicKey"; + this.description = "Converts a hexadecimal DER (Distinguished Encoding Rules) string into PEM (Privacy Enhanced Mail) format."; + this.infoURL = "https://wikipedia.org/wiki/Privacy-Enhanced_Mail"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Header string", + "type": "string", + "value": "CERTIFICATE" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.getPEMStringFromHex(input.replace(/\s/g, ""), args[0]); + } + +} + +export default HexToPEM; diff --git a/src/core/operations/IP.js b/src/core/operations/IP.js deleted file mode 100755 index 8a852789..00000000 --- a/src/core/operations/IP.js +++ /dev/null @@ -1,1085 +0,0 @@ -import Utils from "../Utils.js"; -import Checksum from "./Checksum.js"; -import {BigInteger} from "jsbn"; - - -/** - * Internet Protocol address operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const IP = { - - /** - * @constant - * @default - */ - INCLUDE_NETWORK_INFO: true, - /** - * @constant - * @default - */ - ENUMERATE_ADDRESSES: true, - /** - * @constant - * @default - */ - ALLOW_LARGE_LIST: false, - - /** - * Parse IP range operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseIpRange: function (input, args) { - let includeNetworkInfo = args[0], - enumerateAddresses = args[1], - allowLargeList = args[2]; - - // Check what type of input we are looking at - let ipv4CidrRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/(\d\d?)\s*$/, - ipv4RangeRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*-\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/, - ipv6CidrRegex = /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\/(\d\d?\d?)\s*$/i, - ipv6RangeRegex = /^\s*(((?=.*::)(?!.*::[^-]+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*-\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\17)::|:\b|(?![\dA-F])))|(?!\16\17)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i, - match; - - if ((match = ipv4CidrRegex.exec(input))) { - return IP._ipv4CidrRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); - } else if ((match = ipv4RangeRegex.exec(input))) { - return IP._ipv4HyphenatedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); - } else if ((match = ipv6CidrRegex.exec(input))) { - return IP._ipv6CidrRange(match, includeNetworkInfo); - } else if ((match = ipv6RangeRegex.exec(input))) { - return IP._ipv6HyphenatedRange(match, includeNetworkInfo); - } else { - return "Invalid input.\n\nEnter either a CIDR range (e.g. 10.0.0.0/24) or a hyphenated range (e.g. 10.0.0.0 - 10.0.1.0). IPv6 also supported."; - } - }, - - - /** - * @constant - * @default - */ - IPV4_REGEX: /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/, - /** - * @constant - * @default - */ - IPV6_REGEX: /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i, - - /** - * Parse IPv6 address operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseIPv6: function (input, args) { - let match, - output = ""; - - if ((match = IP.IPV6_REGEX.exec(input))) { - let ipv6 = IP._strToIpv6(match[1]), - longhand = IP._ipv6ToStr(ipv6), - shorthand = IP._ipv6ToStr(ipv6, true); - - output += "Longhand: " + longhand + "\nShorthand: " + shorthand + "\n"; - - // Detect reserved addresses - if (shorthand === "::") { - // Unspecified address - output += "\nUnspecified address corresponding to 0.0.0.0/32 in IPv4."; - output += "\nUnspecified address range: ::/128"; - } else if (shorthand === "::1") { - // Loopback address - output += "\nLoopback address to the local host corresponding to 127.0.0.1/8 in IPv4."; - output += "\nLoopback addresses range: ::1/128"; - } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && - ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0xffff) { - // IPv4-mapped IPv6 address - output += "\nIPv4-mapped IPv6 address detected. IPv6 clients will be handled natively by default, and IPv4 clients appear as IPv6 clients at their IPv4-mapped IPv6 address."; - output += "\nMapped IPv4 address: " + IP._ipv4ToStr((ipv6[6] << 16) + ipv6[7]); - output += "\nIPv4-mapped IPv6 addresses range: ::ffff:0:0/96"; - } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && - ipv6[3] === 0 && ipv6[4] === 0xffff && ipv6[5] === 0) { - // IPv4-translated address - output += "\nIPv4-translated address detected. Used by Stateless IP/ICMP Translation (SIIT). See RFCs 6145 and 6052 for more details."; - output += "\nTranslated IPv4 address: " + IP._ipv4ToStr((ipv6[6] << 16) + ipv6[7]); - output += "\nIPv4-translated addresses range: ::ffff:0:0:0/96"; - } else if (ipv6[0] === 0x100) { - // Discard prefix per RFC 6666 - output += "\nDiscard prefix detected. This is used when forwarding traffic to a sinkhole router to mitigate the effects of a denial-of-service attack. See RFC 6666 for more details."; - output += "\nDiscard range: 100::/64"; - } else if (ipv6[0] === 0x64 && ipv6[1] === 0xff9b && ipv6[2] === 0 && - ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0) { - // IPv4/IPv6 translation per RFC 6052 - output += "\n'Well-Known' prefix for IPv4/IPv6 translation detected. See RFC 6052 for more details."; - output += "\nTranslated IPv4 address: " + IP._ipv4ToStr((ipv6[6] << 16) + ipv6[7]); - output += "\n'Well-Known' prefix range: 64:ff9b::/96"; - } else if (ipv6[0] === 0x2001 && ipv6[1] === 0) { - // Teredo tunneling - output += "\nTeredo tunneling IPv6 address detected\n"; - let serverIpv4 = (ipv6[2] << 16) + ipv6[3], - udpPort = (~ipv6[5]) & 0xffff, - clientIpv4 = ~((ipv6[6] << 16) + ipv6[7]), - flagCone = (ipv6[4] >>> 15) & 1, - flagR = (ipv6[4] >>> 14) & 1, - flagRandom1 = (ipv6[4] >>> 10) & 15, - flagUg = (ipv6[4] >>> 8) & 3, - flagRandom2 = ipv6[4] & 255; - - output += "\nServer IPv4 address: " + IP._ipv4ToStr(serverIpv4) + - "\nClient IPv4 address: " + IP._ipv4ToStr(clientIpv4) + - "\nClient UDP port: " + udpPort + - "\nFlags:" + - "\n\tCone: " + flagCone; - - if (flagCone) { - output += " (Client is behind a cone NAT)"; - } else { - output += " (Client is not behind a cone NAT)"; - } - - output += "\n\tR: " + flagR; - - if (flagR) { - output += " Error: This flag should be set to 0. See RFC 5991 and RFC 4380."; - } - - output += "\n\tRandom1: " + Utils.bin(flagRandom1, 4) + - "\n\tUG: " + Utils.bin(flagUg, 2); - - if (flagUg) { - output += " Error: This flag should be set to 00. See RFC 4380."; - } - - output += "\n\tRandom2: " + Utils.bin(flagRandom2, 8); - - if (!flagR && !flagUg && flagRandom1 && flagRandom2) { - output += "\n\nThis is a valid Teredo address which complies with RFC 4380 and RFC 5991."; - } else if (!flagR && !flagUg) { - output += "\n\nThis is a valid Teredo address which complies with RFC 4380, however it does not comply with RFC 5991 (Teredo Security Updates) as there are no randomised bits in the flag field."; - } else { - output += "\n\nThis is an invalid Teredo address."; - } - output += "\n\nTeredo prefix range: 2001::/32"; - } else if (ipv6[0] === 0x2001 && ipv6[1] === 0x2 && ipv6[2] === 0) { - // Benchmarking - output += "\nAssigned to the Benchmarking Methodology Working Group (BMWG) for benchmarking IPv6. Corresponds to 198.18.0.0/15 for benchmarking IPv4. See RFC 5180 for more details."; - output += "\nBMWG range: 2001:2::/48"; - } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x10 && ipv6[1] <= 0x1f) { - // ORCHIDv1 - output += "\nDeprecated, previously ORCHIDv1 (Overlay Routable Cryptographic Hash Identifiers).\nORCHIDv1 range: 2001:10::/28\nORCHIDv2 now uses 2001:20::/28."; - } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x20 && ipv6[1] <= 0x2f) { - // ORCHIDv2 - output += "\nORCHIDv2 (Overlay Routable Cryptographic Hash Identifiers).\nThese are non-routed IPv6 addresses used for Cryptographic Hash Identifiers."; - output += "\nORCHIDv2 range: 2001:20::/28"; - } else if (ipv6[0] === 0x2001 && ipv6[1] === 0xdb8) { - // Documentation - output += "\nThis is a documentation IPv6 address. This range should be used whenever an example IPv6 address is given or to model networking scenarios. Corresponds to 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24 in IPv4."; - output += "\nDocumentation range: 2001:db8::/32"; - } else if (ipv6[0] === 0x2002) { - // 6to4 - output += "\n6to4 transition IPv6 address detected. See RFC 3056 for more details." + - "\n6to4 prefix range: 2002::/16"; - - let v4Addr = IP._ipv4ToStr((ipv6[1] << 16) + ipv6[2]), - slaId = ipv6[3], - interfaceIdStr = ipv6[4].toString(16) + ipv6[5].toString(16) + ipv6[6].toString(16) + ipv6[7].toString(16), - interfaceId = new BigInteger(interfaceIdStr, 16); - - output += "\n\nEncapsulated IPv4 address: " + v4Addr + - "\nSLA ID: " + slaId + - "\nInterface ID (base 16): " + interfaceIdStr + - "\nInterface ID (base 10): " + interfaceId.toString(); - } else if (ipv6[0] >= 0xfc00 && ipv6[0] <= 0xfdff) { - // Unique local address - output += "\nThis is a unique local address comparable to the IPv4 private addresses 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. See RFC 4193 for more details."; - output += "\nUnique local addresses range: fc00::/7"; - } else if (ipv6[0] >= 0xfe80 && ipv6[0] <= 0xfebf) { - // Link-local address - output += "\nThis is a link-local address comparable to the auto-configuration addresses 169.254.0.0/16 in IPv4."; - output += "\nLink-local addresses range: fe80::/10"; - } else if (ipv6[0] >= 0xff00) { - // Multicast - output += "\nThis is a reserved multicast address."; - output += "\nMulticast addresses range: ff00::/8"; - } - - - // Detect possible EUI-64 addresses - if ((ipv6[5] & 0xff === 0xff) && (ipv6[6] >>> 8 === 0xfe)) { - output += "\n\nThis IPv6 address contains a modified EUI-64 address, identified by the presence of FF:FE in the 12th and 13th octets."; - - let intIdent = Utils.hex(ipv6[4] >>> 8) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + - Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[5] & 0xff) + ":" + - Utils.hex(ipv6[6] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + - Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff), - mac = Utils.hex((ipv6[4] >>> 8) ^ 2) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + - Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + - Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff); - output += "\nInterface identifier: " + intIdent + - "\nMAC address: " + mac; - } - } else { - return "Invalid IPv6 address"; - } - return output; - }, - - - /** - * @constant - * @default - */ - IP_FORMAT_LIST: ["Dotted Decimal", "Decimal", "Hex"], - - /** - * Change IP format operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runChangeIpFormat: function(input, args) { - let inFormat = args[0], - outFormat = args[1], - lines = input.split("\n"), - output = "", - j = 0; - - - for (let i = 0; i < lines.length; i++) { - if (lines[i] === "") continue; - let baIp = []; - let octets; - let decimal; - - if (inFormat === outFormat) { - output += lines[i] + "\n"; - continue; - } - - // Convert to byte array IP from input format - switch (inFormat) { - case "Dotted Decimal": - octets = lines[i].split("."); - for (j = 0; j < octets.length; j++) { - baIp.push(parseInt(octets[j], 10)); - } - break; - case "Decimal": - decimal = lines[i].toString(); - baIp.push(decimal >> 24 & 255); - baIp.push(decimal >> 16 & 255); - baIp.push(decimal >> 8 & 255); - baIp.push(decimal & 255); - break; - case "Hex": - baIp = Utils.hexToByteArray(lines[i]); - break; - default: - throw "Unsupported input IP format"; - } - - let ddIp; - let decIp; - let hexIp; - - // Convert byte array IP to output format - switch (outFormat) { - case "Dotted Decimal": - ddIp = ""; - for (j = 0; j < baIp.length; j++) { - ddIp += baIp[j] + "."; - } - output += ddIp.slice(0, ddIp.length-1) + "\n"; - break; - case "Decimal": - decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0; - output += decIp.toString() + "\n"; - break; - case "Hex": - hexIp = ""; - for (j = 0; j < baIp.length; j++) { - hexIp += Utils.hex(baIp[j]); - } - output += hexIp + "\n"; - break; - default: - throw "Unsupported output IP format"; - } - } - - return output.slice(0, output.length-1); - }, - - - /** - * @constant - * @default - */ - DELIM_OPTIONS: ["Line feed", "CRLF", "Space", "Comma", "Semi-colon"], - /** - * @constant - * @default - */ - GROUP_CIDR: 24, - /** - * @constant - * @default - */ - GROUP_ONLY_SUBNET: false, - - /** - * Group IP addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runGroupIps: function(input, args) { - let delim = Utils.charRep[args[0]], - cidr = args[1], - onlySubnets = args[2], - ipv4Mask = cidr < 32 ? ~(0xFFFFFFFF >>> cidr) : 0xFFFFFFFF, - ipv6Mask = IP._genIpv6Mask(cidr), - ips = input.split(delim), - ipv4Networks = {}, - ipv6Networks = {}, - match = null, - output = "", - ip = null, - network = null, - networkStr = "", - i; - - if (cidr < 0 || cidr > 127) { - return "CIDR must be less than 32 for IPv4 or 128 for IPv6"; - } - - // Parse all IPs and add to network dictionary - for (i = 0; i < ips.length; i++) { - if ((match = IP.IPV4_REGEX.exec(ips[i]))) { - ip = IP._strToIpv4(match[1]) >>> 0; - network = ip & ipv4Mask; - - if (ipv4Networks.hasOwnProperty(network)) { - ipv4Networks[network].push(ip); - } else { - ipv4Networks[network] = [ip]; - } - } else if ((match = IP.IPV6_REGEX.exec(ips[i]))) { - ip = IP._strToIpv6(match[1]); - network = []; - networkStr = ""; - - for (let j = 0; j < 8; j++) { - network.push(ip[j] & ipv6Mask[j]); - } - - networkStr = IP._ipv6ToStr(network, true); - - if (ipv6Networks.hasOwnProperty(networkStr)) { - ipv6Networks[networkStr].push(ip); - } else { - ipv6Networks[networkStr] = [ip]; - } - } - } - - // Sort IPv4 network dictionaries and print - for (network in ipv4Networks) { - ipv4Networks[network] = ipv4Networks[network].sort(); - - output += IP._ipv4ToStr(network) + "/" + cidr + "\n"; - - if (!onlySubnets) { - for (i = 0; i < ipv4Networks[network].length; i++) { - output += " " + IP._ipv4ToStr(ipv4Networks[network][i]) + "\n"; - } - output += "\n"; - } - } - - // Sort IPv6 network dictionaries and print - for (networkStr in ipv6Networks) { - //ipv6Networks[networkStr] = ipv6Networks[networkStr].sort(); TODO - - output += networkStr + "/" + cidr + "\n"; - - if (!onlySubnets) { - for (i = 0; i < ipv6Networks[networkStr].length; i++) { - output += " " + IP._ipv6ToStr(ipv6Networks[networkStr][i], true) + "\n"; - } - output += "\n"; - } - } - - return output; - }, - - - /** - * @constant - * @default - */ - IP_HEADER_FORMAT: ["Hex", "Raw"], - - /** - * Parse IPv4 header operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {html} - */ - runParseIPv4Header: function(input, args) { - let format = args[0], - output; - - if (format === "Hex") { - input = Utils.fromHex(input); - } else if (format === "Raw") { - input = Utils.strToByteArray(input); - } else { - return "Unrecognised input format."; - } - - let version = (input[0] >>> 4) & 0x0f, - ihl = input[0] & 0x0f, - dscp = (input[1] >>> 2) & 0x3f, - ecn = input[1] & 0x03, - length = input[2] << 8 | input[3], - identification = input[4] << 8 | input[5], - flags = (input[6] >>> 5) & 0x07, - fragOffset = (input[6] & 0x1f) << 8 | input[7], - ttl = input[8], - protocol = input[9], - checksum = input[10] << 8 | input[11], - srcIP = input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15], - dstIP = input[16] << 24 | input[17] << 16 | input[18] << 8 | input[19], - checksumHeader = input.slice(0, 10).concat([0, 0]).concat(input.slice(12, 20)), - options = []; - - // Version - if (version !== 4) { - version = version + " (Error: for IPv4 headers, this should always be set to 4)"; - } - - // IHL - if (ihl < 5) { - ihl = ihl + " (Error: this should always be at least 5)"; - } else if (ihl > 5) { - // sort out options... - const optionsLen = ihl * 4 - 20; - options = input.slice(20, optionsLen + 20); - } - - // Protocol - const protocolInfo = IP._protocolLookup[protocol] || {keyword: "", protocol: ""}; - - // Checksum - let correctChecksum = Checksum.runTCPIP(checksumHeader, []), - givenChecksum = Utils.hex(checksum), - checksumResult; - if (correctChecksum === givenChecksum) { - checksumResult = givenChecksum + " (correct)"; - } else { - checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")"; - } - - output = "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; - - if (ihl > 5) { - output += ""; - } - - return output + "
FieldValue
Version" + version + "
Internet Header Length (IHL)" + ihl + " (" + (ihl * 4) + " bytes)
Differentiated Services Code Point (DSCP)" + dscp + "
Explicit Congestion Notification (ECN)" + ecn + "
Total length" + length + " bytes" + - "\n IP header: " + (ihl * 4) + " bytes" + - "\n Data: " + (length - ihl * 4) + " bytes
Identification0x" + Utils.hex(identification) + " (" + identification + ")
Flags0x" + Utils.hex(flags, 2) + - "\n Reserved bit:" + (flags >> 2) + " (must be 0)" + - "\n Don't fragment:" + (flags >> 1 & 1) + - "\n More fragments:" + (flags & 1) + "
Fragment offset" + fragOffset + "
Time-To-Live" + ttl + "
Protocol" + protocol + ", " + protocolInfo.protocol + " (" + protocolInfo.keyword + ")
Header checksum" + checksumResult + "
Source IP address" + IP._ipv4ToStr(srcIP) + "
Destination IP address" + IP._ipv4ToStr(dstIP) + "
Options" + Utils.byteArrayToHex(options) + "
"; - }, - - - /** - * @constant - * @default - * @private - */ - _LARGE_RANGE_ERROR: "The specified range contains more than 65,536 addresses. Running this query could crash your browser. If you want to run it, select the \"Allow large queries\" option. You are advised to turn off \"Auto Bake\" whilst editing large ranges.", - - /** - * Parses an IPv4 CIDR range (e.g. 192.168.0.0/24) and displays information about it. - * - * @private - * @param {RegExp} cidr - * @param {boolean} includeNetworkInfo - * @param {boolean} enumerateAddresses - * @param {boolean} allowLargeList - * @returns {string} - */ - _ipv4CidrRange: function(cidr, includeNetworkInfo, enumerateAddresses, allowLargeList) { - let output = "", - network = IP._strToIpv4(cidr[1]), - cidrRange = parseInt(cidr[2], 10); - - if (cidrRange < 0 || cidrRange > 31) { - return "IPv4 CIDR must be less than 32"; - } - - let mask = ~(0xFFFFFFFF >>> cidrRange), - ip1 = network & mask, - ip2 = ip1 | ~mask; - - if (includeNetworkInfo) { - output += "Network: " + IP._ipv4ToStr(network) + "\n"; - output += "CIDR: " + cidrRange + "\n"; - output += "Mask: " + IP._ipv4ToStr(mask) + "\n"; - output += "Range: " + IP._ipv4ToStr(ip1) + " - " + IP._ipv4ToStr(ip2) + "\n"; - output += "Total addresses in range: " + (((ip2 - ip1) >>> 0) + 1) + "\n\n"; - } - - if (enumerateAddresses) { - if (cidrRange >= 16 || allowLargeList) { - output += IP._generateIpv4Range(ip1, ip2).join("\n"); - } else { - output += IP._LARGE_RANGE_ERROR; - } - } - return output; - }, - - - /** - * Parses an IPv6 CIDR range (e.g. ff00::/48) and displays information about it. - * - * @private - * @param {RegExp} cidr - * @param {boolean} includeNetworkInfo - * @returns {string} - */ - _ipv6CidrRange: function(cidr, includeNetworkInfo) { - let output = "", - network = IP._strToIpv6(cidr[1]), - cidrRange = parseInt(cidr[cidr.length-1], 10); - - if (cidrRange < 0 || cidrRange > 127) { - return "IPv6 CIDR must be less than 128"; - } - - let mask = IP._genIpv6Mask(cidrRange), - ip1 = new Array(8), - ip2 = new Array(8), - totalDiff = "", - total = new Array(128); - - for (let i = 0; i < 8; i++) { - ip1[i] = network[i] & mask[i]; - ip2[i] = ip1[i] | (~mask[i] & 0x0000FFFF); - totalDiff = (ip2[i] - ip1[i]).toString(2); - - if (totalDiff !== "0") { - for (let n = 0; n < totalDiff.length; n++) { - total[i*16 + 16-(totalDiff.length-n)] = totalDiff[n]; - } - } - } - - if (includeNetworkInfo) { - output += "Network: " + IP._ipv6ToStr(network) + "\n"; - output += "Shorthand: " + IP._ipv6ToStr(network, true) + "\n"; - output += "CIDR: " + cidrRange + "\n"; - output += "Mask: " + IP._ipv6ToStr(mask) + "\n"; - output += "Range: " + IP._ipv6ToStr(ip1) + " - " + IP._ipv6ToStr(ip2) + "\n"; - output += "Total addresses in range: " + (parseInt(total.join(""), 2) + 1) + "\n\n"; - } - - return output; - }, - - - /** - * Generates an IPv6 subnet mask given a CIDR value. - * - * @private - * @param {number} cidr - * @returns {number[]} - */ - _genIpv6Mask: function(cidr) { - let mask = new Array(8), - shift; - - for (let i = 0; i < 8; i++) { - if (cidr > ((i+1)*16)) { - mask[i] = 0x0000FFFF; - } else { - shift = cidr-(i*16); - if (shift < 0) shift = 0; - mask[i] = ~((0x0000FFFF >>> shift) | 0xFFFF0000); - } - } - - return mask; - }, - - - /** - * Parses an IPv4 hyphenated range (e.g. 192.168.0.0 - 192.168.0.255) and displays information - * about it. - * - * @private - * @param {RegExp} range - * @param {boolean} includeNetworkInfo - * @param {boolean} enumerateAddresses - * @param {boolean} allowLargeList - * @returns {string} - */ - _ipv4HyphenatedRange: function(range, includeNetworkInfo, enumerateAddresses, allowLargeList) { - let output = "", - ip1 = IP._strToIpv4(range[1]), - ip2 = IP._strToIpv4(range[2]); - - // Calculate mask - let diff = ip1 ^ ip2, - cidr = 32, - mask = 0; - - while (diff !== 0) { - diff >>= 1; - cidr--; - mask = (mask << 1) | 1; - } - - mask = ~mask >>> 0; - let network = ip1 & mask, - subIp1 = network & mask, - subIp2 = subIp1 | ~mask; - - if (includeNetworkInfo) { - output += "Minimum subnet required to hold this range:\n"; - output += "\tNetwork: " + IP._ipv4ToStr(network) + "\n"; - output += "\tCIDR: " + cidr + "\n"; - output += "\tMask: " + IP._ipv4ToStr(mask) + "\n"; - output += "\tSubnet range: " + IP._ipv4ToStr(subIp1) + " - " + IP._ipv4ToStr(subIp2) + "\n"; - output += "\tTotal addresses in subnet: " + (((subIp2 - subIp1) >>> 0) + 1) + "\n\n"; - output += "Range: " + IP._ipv4ToStr(ip1) + " - " + IP._ipv4ToStr(ip2) + "\n"; - output += "Total addresses in range: " + (((ip2 - ip1) >>> 0) + 1) + "\n\n"; - } - - if (enumerateAddresses) { - if (((ip2 - ip1) >>> 0) <= 65536 || allowLargeList) { - output += IP._generateIpv4Range(ip1, ip2).join("\n"); - } else { - output += IP._LARGE_RANGE_ERROR; - } - } - return output; - }, - - - /** - * Parses an IPv6 hyphenated range (e.g. ff00:: - ffff::) and displays information about it. - * - * @private - * @param {RegExp} range - * @param {boolean} includeNetworkInfo - * @returns {string} - */ - _ipv6HyphenatedRange: function(range, includeNetworkInfo) { - let output = "", - ip1 = IP._strToIpv6(range[1]), - ip2 = IP._strToIpv6(range[14]); - - let t = "", - total = new Array(128).fill(), - i; - - for (i = 0; i < 8; i++) { - t = (ip2[i] - ip1[i]).toString(2); - if (t !== "0") { - for (let n = 0; n < t.length; n++) { - total[i*16 + 16-(t.length-n)] = t[n]; - } - } - } - - if (includeNetworkInfo) { - output += "Range: " + IP._ipv6ToStr(ip1) + " - " + IP._ipv6ToStr(ip2) + "\n"; - output += "Shorthand range: " + IP._ipv6ToStr(ip1, true) + " - " + IP._ipv6ToStr(ip2, true) + "\n"; - output += "Total addresses in range: " + (parseInt(total.join(""), 2) + 1) + "\n\n"; - } - - return output; - }, - - - /** - * Converts an IPv4 address from string format to numerical format. - * - * @private - * @param {string} ipStr - * @returns {number} - * - * @example - * // returns 168427520 - * IP._strToIpv4("10.10.0.0"); - */ - _strToIpv4: function (ipStr) { - let blocks = ipStr.split("."), - numBlocks = parseBlocks(blocks), - result = 0; - - result += numBlocks[0] << 24; - result += numBlocks[1] << 16; - result += numBlocks[2] << 8; - result += numBlocks[3]; - - return result; - - /** - * Converts a list of 4 numeric strings in the range 0-255 to a list of numbers. - */ - function parseBlocks(blocks) { - if (blocks.length !== 4) - throw "More than 4 blocks."; - - const numBlocks = []; - for (let i = 0; i < 4; i++) { - numBlocks[i] = parseInt(blocks[i], 10); - if (numBlocks[i] < 0 || numBlocks[i] > 255) - throw "Block out of range."; - } - return numBlocks; - } - }, - - - /** - * Converts an IPv4 address from numerical format to string format. - * - * @private - * @param {number} ipInt - * @returns {string} - * - * @example - * // returns "10.10.0.0" - * IP._ipv4ToStr(168427520); - */ - _ipv4ToStr: function(ipInt) { - let blockA = (ipInt >> 24) & 255, - blockB = (ipInt >> 16) & 255, - blockC = (ipInt >> 8) & 255, - blockD = ipInt & 255; - - return blockA + "." + blockB + "." + blockC + "." + blockD; - }, - - - /** - * Converts an IPv6 address from string format to numerical array format. - * - * @private - * @param {string} ipStr - * @returns {number[]} - * - * @example - * // returns [65280, 0, 0, 0, 0, 0, 4369, 8738] - * IP._strToIpv6("ff00::1111:2222"); - */ - _strToIpv6: function(ipStr) { - let blocks = ipStr.split(":"), - numBlocks = parseBlocks(blocks), - j = 0, - ipv6 = new Array(8); - - for (let i = 0; i < 8; i++) { - if (isNaN(numBlocks[j])) { - ipv6[i] = 0; - if (i === (8-numBlocks.slice(j).length)) j++; - } else { - ipv6[i] = numBlocks[j]; - j++; - } - } - return ipv6; - - /** - * Converts a list of 3-8 numeric hex strings in the range 0-65535 to a list of numbers. - */ - function parseBlocks(blocks) { - if (blocks.length < 3 || blocks.length > 8) - throw "Badly formatted IPv6 address."; - const numBlocks = []; - for (let i = 0; i < blocks.length; i++) { - numBlocks[i] = parseInt(blocks[i], 16); - if (numBlocks[i] < 0 || numBlocks[i] > 65535) - throw "Block out of range."; - } - return numBlocks; - } - }, - - - /** - * Converts an IPv6 address from numerical array format to string format. - * - * @private - * @param {number[]} ipv6 - * @param {boolean} compact - Whether or not to return the address in shorthand or not - * @returns {string} - * - * @example - * // returns "ff00::1111:2222" - * IP._ipv6ToStr([65280, 0, 0, 0, 0, 0, 4369, 8738], true); - * - * // returns "ff00:0000:0000:0000:0000:0000:1111:2222" - * IP._ipv6ToStr([65280, 0, 0, 0, 0, 0, 4369, 8738], false); - */ - _ipv6ToStr: function(ipv6, compact) { - let output = "", - i = 0; - - if (compact) { - let start = -1, - end = -1, - s = 0, - e = -1; - - for (i = 0; i < 8; i++) { - if (ipv6[i] === 0 && e === (i-1)) { - e = i; - } else if (ipv6[i] === 0) { - s = i; e = i; - } - if (e >= 0 && (e-s) > (end - start)) { - start = s; - end = e; - } - } - - for (i = 0; i < 8; i++) { - if (i !== start) { - output += Utils.hex(ipv6[i], 1) + ":"; - } else { - output += ":"; - i = end; - if (end === 7) output += ":"; - } - } - if (output[0] === ":") - output = ":" + output; - } else { - for (i = 0; i < 8; i++) { - output += Utils.hex(ipv6[i], 4) + ":"; - } - } - return output.slice(0, output.length-1); - }, - - - /** - * Generates a list of IPv4 addresses in string format between two given numerical values. - * - * @private - * @param {number} ip - * @param {number} endIp - * @returns {string[]} - * - * @example - * // returns ["0.0.0.1", "0.0.0.2", "0.0.0.3"] - * IP._generateIpv4Range(1, 3); - */ - _generateIpv4Range: function(ip, endIp) { - const range = []; - if (endIp >= ip) { - for (; ip <= endIp; ip++) { - range.push(IP._ipv4ToStr(ip)); - } - } else { - range[0] = "Second IP address smaller than first."; - } - return range; - }, - - - /** - * Lookup table for Internet Protocols. - * Taken from https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml - * - * @private - * @constant - */ - _protocolLookup: { - 0: {keyword: "HOPOPT", protocol: "IPv6 Hop-by-Hop Option"}, - 1: {keyword: "ICMP", protocol: "Internet Control Message"}, - 2: {keyword: "IGMP", protocol: "Internet Group Management"}, - 3: {keyword: "GGP", protocol: "Gateway-to-Gateway"}, - 4: {keyword: "IPv4", protocol: "IPv4 encapsulation"}, - 5: {keyword: "ST", protocol: "Stream"}, - 6: {keyword: "TCP", protocol: "Transmission Control"}, - 7: {keyword: "CBT", protocol: "CBT"}, - 8: {keyword: "EGP", protocol: "Exterior Gateway Protocol"}, - 9: {keyword: "IGP", protocol: "any private interior gateway (used by Cisco for their IGRP)"}, - 10: {keyword: "BBN-RCC-MON", protocol: "BBN RCC Monitoring"}, - 11: {keyword: "NVP-II", protocol: "Network Voice Protocol"}, - 12: {keyword: "PUP", protocol: "PUP"}, - 13: {keyword: "ARGUS (deprecated)", protocol: "ARGUS"}, - 14: {keyword: "EMCON", protocol: "EMCON"}, - 15: {keyword: "XNET", protocol: "Cross Net Debugger"}, - 16: {keyword: "CHAOS", protocol: "Chaos"}, - 17: {keyword: "UDP", protocol: "User Datagram"}, - 18: {keyword: "MUX", protocol: "Multiplexing"}, - 19: {keyword: "DCN-MEAS", protocol: "DCN Measurement Subsystems"}, - 20: {keyword: "HMP", protocol: "Host Monitoring"}, - 21: {keyword: "PRM", protocol: "Packet Radio Measurement"}, - 22: {keyword: "XNS-IDP", protocol: "XEROX NS IDP"}, - 23: {keyword: "TRUNK-1", protocol: "Trunk-1"}, - 24: {keyword: "TRUNK-2", protocol: "Trunk-2"}, - 25: {keyword: "LEAF-1", protocol: "Leaf-1"}, - 26: {keyword: "LEAF-2", protocol: "Leaf-2"}, - 27: {keyword: "RDP", protocol: "Reliable Data Protocol"}, - 28: {keyword: "IRTP", protocol: "Internet Reliable Transaction"}, - 29: {keyword: "ISO-TP4", protocol: "ISO Transport Protocol Class 4"}, - 30: {keyword: "NETBLT", protocol: "Bulk Data Transfer Protocol"}, - 31: {keyword: "MFE-NSP", protocol: "MFE Network Services Protocol"}, - 32: {keyword: "MERIT-INP", protocol: "MERIT Internodal Protocol"}, - 33: {keyword: "DCCP", protocol: "Datagram Congestion Control Protocol"}, - 34: {keyword: "3PC", protocol: "Third Party Connect Protocol"}, - 35: {keyword: "IDPR", protocol: "Inter-Domain Policy Routing Protocol"}, - 36: {keyword: "XTP", protocol: "XTP"}, - 37: {keyword: "DDP", protocol: "Datagram Delivery Protocol"}, - 38: {keyword: "IDPR-CMTP", protocol: "IDPR Control Message Transport Proto"}, - 39: {keyword: "TP++", protocol: "TP++ Transport Protocol"}, - 40: {keyword: "IL", protocol: "IL Transport Protocol"}, - 41: {keyword: "IPv6", protocol: "IPv6 encapsulation"}, - 42: {keyword: "SDRP", protocol: "Source Demand Routing Protocol"}, - 43: {keyword: "IPv6-Route", protocol: "Routing Header for IPv6"}, - 44: {keyword: "IPv6-Frag", protocol: "Fragment Header for IPv6"}, - 45: {keyword: "IDRP", protocol: "Inter-Domain Routing Protocol"}, - 46: {keyword: "RSVP", protocol: "Reservation Protocol"}, - 47: {keyword: "GRE", protocol: "Generic Routing Encapsulation"}, - 48: {keyword: "DSR", protocol: "Dynamic Source Routing Protocol"}, - 49: {keyword: "BNA", protocol: "BNA"}, - 50: {keyword: "ESP", protocol: "Encap Security Payload"}, - 51: {keyword: "AH", protocol: "Authentication Header"}, - 52: {keyword: "I-NLSP", protocol: "Integrated Net Layer Security TUBA"}, - 53: {keyword: "SWIPE (deprecated)", protocol: "IP with Encryption"}, - 54: {keyword: "NARP", protocol: "NBMA Address Resolution Protocol"}, - 55: {keyword: "MOBILE", protocol: "IP Mobility"}, - 56: {keyword: "TLSP", protocol: "Transport Layer Security Protocol using Kryptonet key management"}, - 57: {keyword: "SKIP", protocol: "SKIP"}, - 58: {keyword: "IPv6-ICMP", protocol: "ICMP for IPv6"}, - 59: {keyword: "IPv6-NoNxt", protocol: "No Next Header for IPv6"}, - 60: {keyword: "IPv6-Opts", protocol: "Destination Options for IPv6"}, - 61: {keyword: "", protocol: "any host internal protocol"}, - 62: {keyword: "CFTP", protocol: "CFTP"}, - 63: {keyword: "", protocol: "any local network"}, - 64: {keyword: "SAT-EXPAK", protocol: "SATNET and Backroom EXPAK"}, - 65: {keyword: "KRYPTOLAN", protocol: "Kryptolan"}, - 66: {keyword: "RVD", protocol: "MIT Remote Virtual Disk Protocol"}, - 67: {keyword: "IPPC", protocol: "Internet Pluribus Packet Core"}, - 68: {keyword: "", protocol: "any distributed file system"}, - 69: {keyword: "SAT-MON", protocol: "SATNET Monitoring"}, - 70: {keyword: "VISA", protocol: "VISA Protocol"}, - 71: {keyword: "IPCV", protocol: "Internet Packet Core Utility"}, - 72: {keyword: "CPNX", protocol: "Computer Protocol Network Executive"}, - 73: {keyword: "CPHB", protocol: "Computer Protocol Heart Beat"}, - 74: {keyword: "WSN", protocol: "Wang Span Network"}, - 75: {keyword: "PVP", protocol: "Packet Video Protocol"}, - 76: {keyword: "BR-SAT-MON", protocol: "Backroom SATNET Monitoring"}, - 77: {keyword: "SUN-ND", protocol: "SUN ND PROTOCOL-Temporary"}, - 78: {keyword: "WB-MON", protocol: "WIDEBAND Monitoring"}, - 79: {keyword: "WB-EXPAK", protocol: "WIDEBAND EXPAK"}, - 80: {keyword: "ISO-IP", protocol: "ISO Internet Protocol"}, - 81: {keyword: "VMTP", protocol: "VMTP"}, - 82: {keyword: "SECURE-VMTP", protocol: "SECURE-VMTP"}, - 83: {keyword: "VINES", protocol: "VINES"}, - 84: {keyword: "TTP", protocol: "Transaction Transport Protocol"}, - 85: {keyword: "NSFNET-IGP", protocol: "NSFNET-IGP"}, - 86: {keyword: "DGP", protocol: "Dissimilar Gateway Protocol"}, - 87: {keyword: "TCF", protocol: "TCF"}, - 88: {keyword: "EIGRP", protocol: "EIGRP"}, - 89: {keyword: "OSPFIGP", protocol: "OSPFIGP"}, - 90: {keyword: "Sprite-RPC", protocol: "Sprite RPC Protocol"}, - 91: {keyword: "LARP", protocol: "Locus Address Resolution Protocol"}, - 92: {keyword: "MTP", protocol: "Multicast Transport Protocol"}, - 93: {keyword: "AX.25", protocol: "AX.25 Frames"}, - 94: {keyword: "IPIP", protocol: "IP-within-IP Encapsulation Protocol"}, - 95: {keyword: "MICP (deprecated)", protocol: "Mobile Internetworking Control Pro."}, - 96: {keyword: "SCC-SP", protocol: "Semaphore Communications Sec. Pro."}, - 97: {keyword: "ETHERIP", protocol: "Ethernet-within-IP Encapsulation"}, - 98: {keyword: "ENCAP", protocol: "Encapsulation Header"}, - 99: {keyword: "", protocol: "any private encryption scheme"}, - 100: {keyword: "GMTP", protocol: "GMTP"}, - 101: {keyword: "IFMP", protocol: "Ipsilon Flow Management Protocol"}, - 102: {keyword: "PNNI", protocol: "PNNI over IP"}, - 103: {keyword: "PIM", protocol: "Protocol Independent Multicast"}, - 104: {keyword: "ARIS", protocol: "ARIS"}, - 105: {keyword: "SCPS", protocol: "SCPS"}, - 106: {keyword: "QNX", protocol: "QNX"}, - 107: {keyword: "A/N", protocol: "Active Networks"}, - 108: {keyword: "IPComp", protocol: "IP Payload Compression Protocol"}, - 109: {keyword: "SNP", protocol: "Sitara Networks Protocol"}, - 110: {keyword: "Compaq-Peer", protocol: "Compaq Peer Protocol"}, - 111: {keyword: "IPX-in-IP", protocol: "IPX in IP"}, - 112: {keyword: "VRRP", protocol: "Virtual Router Redundancy Protocol"}, - 113: {keyword: "PGM", protocol: "PGM Reliable Transport Protocol"}, - 114: {keyword: "", protocol: "any 0-hop protocol"}, - 115: {keyword: "L2TP", protocol: "Layer Two Tunneling Protocol"}, - 116: {keyword: "DDX", protocol: "D-II Data Exchange (DDX)"}, - 117: {keyword: "IATP", protocol: "Interactive Agent Transfer Protocol"}, - 118: {keyword: "STP", protocol: "Schedule Transfer Protocol"}, - 119: {keyword: "SRP", protocol: "SpectraLink Radio Protocol"}, - 120: {keyword: "UTI", protocol: "UTI"}, - 121: {keyword: "SMP", protocol: "Simple Message Protocol"}, - 122: {keyword: "SM (deprecated)", protocol: "Simple Multicast Protocol"}, - 123: {keyword: "PTP", protocol: "Performance Transparency Protocol"}, - 124: {keyword: "ISIS over IPv4", protocol: ""}, - 125: {keyword: "FIRE", protocol: ""}, - 126: {keyword: "CRTP", protocol: "Combat Radio Transport Protocol"}, - 127: {keyword: "CRUDP", protocol: "Combat Radio User Datagram"}, - 128: {keyword: "SSCOPMCE", protocol: ""}, - 129: {keyword: "IPLT", protocol: ""}, - 130: {keyword: "SPS", protocol: "Secure Packet Shield"}, - 131: {keyword: "PIPE", protocol: "Private IP Encapsulation within IP"}, - 132: {keyword: "SCTP", protocol: "Stream Control Transmission Protocol"}, - 133: {keyword: "FC", protocol: "Fibre Channel"}, - 134: {keyword: "RSVP-E2E-IGNORE", protocol: ""}, - 135: {keyword: "Mobility Header", protocol: ""}, - 136: {keyword: "UDPLite", protocol: ""}, - 137: {keyword: "MPLS-in-IP", protocol: ""}, - 138: {keyword: "manet", protocol: "MANET Protocols"}, - 139: {keyword: "HIP", protocol: "Host Identity Protocol"}, - 140: {keyword: "Shim6", protocol: "Shim6 Protocol"}, - 141: {keyword: "WESP", protocol: "Wrapped Encapsulating Security Payload"}, - 142: {keyword: "ROHC", protocol: "Robust Header Compression"}, - 253: {keyword: "", protocol: "Use for experimentation and testing"}, - 254: {keyword: "", protocol: "Use for experimentation and testing"}, - 255: {keyword: "Reserved", protocol: ""} - }, - -}; - -export default IP; diff --git a/src/core/operations/Image.js b/src/core/operations/Image.js deleted file mode 100644 index 9ebafaf0..00000000 --- a/src/core/operations/Image.js +++ /dev/null @@ -1,99 +0,0 @@ -import * as ExifParser from "exif-parser"; -import Utils from "../Utils.js"; -import FileType from "./FileType.js"; - - -/** - * Image operations. - * - * @author tlwr [toby@toby.codes] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const Image = { - - /** - * Extract EXIF operation. - * - * Extracts EXIF data from a byteArray, representing a JPG or a TIFF image. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runEXIF(input, args) { - try { - const bytes = Uint8Array.from(input); - const parser = ExifParser.create(bytes.buffer); - const result = parser.parse(); - - let lines = []; - for (let tagName in result.tags) { - let value = result.tags[tagName]; - lines.push(`${tagName}: ${value}`); - } - - const numTags = lines.length; - lines.unshift(`Found ${numTags} tags.\n`); - return lines.join("\n"); - } catch (err) { - throw "Could not extract EXIF data from image: " + err; - } - }, - - - /** - * @constant - * @default - */ - INPUT_FORMAT: ["Raw", "Base64", "Hex"], - - /** - * Render Image operation. - * - * @author n1474335 [n1474335@gmail.com] - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runRenderImage(input, args) { - const inputFormat = args[0]; - let dataURI = "data:"; - - if (!input.length) return ""; - - // Convert input to raw bytes - switch (inputFormat) { - case "Hex": - input = Utils.fromHex(input); - break; - case "Base64": - // Don't trust the Base64 entered by the user. - // Unwrap it first, then re-encode later. - input = Utils.fromBase64(input, null, "byteArray"); - break; - case "Raw": - default: - input = Utils.strToByteArray(input); - break; - } - - // Determine file type - const type = FileType.magicType(input); - if (type && type.mime.indexOf("image") === 0) { - dataURI += type.mime + ";"; - } else { - throw "Invalid file type"; - } - - // Add image data to URI - dataURI += "base64," + Utils.toBase64(input); - - return ""; - }, - -}; - -export default Image; diff --git a/src/core/operations/JPathExpression.mjs b/src/core/operations/JPathExpression.mjs new file mode 100644 index 00000000..df99fc56 --- /dev/null +++ b/src/core/operations/JPathExpression.mjs @@ -0,0 +1,69 @@ +/** + * @author Matt C (matt@artemisbot.uk) + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import jpath from "jsonpath"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * JPath expression operation + */ +class JPathExpression extends Operation { + + /** + * JPathExpression constructor + */ + constructor() { + super(); + + this.name = "JPath expression"; + this.module = "Code"; + this.description = "Extract information from a JSON object with a JPath query."; + this.infoURL = "http://goessner.net/articles/JsonPath/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Query", + "type": "string", + "value": "" + }, + { + "name": "Result delimiter", + "type": "binaryShortString", + "value": "\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [query, delimiter] = args; + let results, + obj; + + try { + obj = JSON.parse(input); + } catch (err) { + throw new OperationError(`Invalid input JSON: ${err.message}`); + } + + try { + results = jpath.query(obj, query); + } catch (err) { + throw new OperationError(`Invalid JPath expression: ${err.message}`); + } + + return results.map(result => JSON.stringify(result)).join(delimiter); + } + +} + +export default JPathExpression; diff --git a/src/core/operations/JS.js b/src/core/operations/JS.js deleted file mode 100755 index 593e8fc6..00000000 --- a/src/core/operations/JS.js +++ /dev/null @@ -1,164 +0,0 @@ -import esprima from "esprima"; -import escodegen from "escodegen"; -import esmangle from "esmangle"; - - -/** - * JavaScript operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const JS = { - - /** - * @constant - * @default - */ - PARSE_LOC: false, - /** - * @constant - * @default - */ - PARSE_RANGE: false, - /** - * @constant - * @default - */ - PARSE_TOKENS: false, - /** - * @constant - * @default - */ - PARSE_COMMENT: false, - /** - * @constant - * @default - */ - PARSE_TOLERANT: false, - - /** - * JavaScript Parser operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParse: function (input, args) { - let parseLoc = args[0], - parseRange = args[1], - parseTokens = args[2], - parseComment = args[3], - parseTolerant = args[4], - result = {}, - options = { - loc: parseLoc, - range: parseRange, - tokens: parseTokens, - comment: parseComment, - tolerant: parseTolerant - }; - - result = esprima.parse(input, options); - return JSON.stringify(result, null, 2); - }, - - - /** - * @constant - * @default - */ - BEAUTIFY_INDENT: "\\t", - /** - * @constant - * @default - */ - BEAUTIFY_QUOTES: ["Auto", "Single", "Double"], - /** - * @constant - * @default - */ - BEAUTIFY_SEMICOLONS: true, - /** - * @constant - * @default - */ - BEAUTIFY_COMMENT: true, - - /** - * JavaScript Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runBeautify: function(input, args) { - let beautifyIndent = args[0] || JS.BEAUTIFY_INDENT, - quotes = args[1].toLowerCase(), - beautifySemicolons = args[2], - beautifyComment = args[3], - result = "", - AST; - - try { - AST = esprima.parse(input, { - range: true, - tokens: true, - comment: true - }); - - const options = { - format: { - indent: { - style: beautifyIndent - }, - quotes: quotes, - semicolons: beautifySemicolons, - }, - comment: beautifyComment - }; - - if (options.comment) - AST = escodegen.attachComments(AST, AST.comments, AST.tokens); - - result = escodegen.generate(AST, options); - } catch (e) { - // Leave original error so the user can see the detail - throw "Unable to parse JavaScript.
" + e.message; - } - return result; - }, - - - /** - * JavaScript Minify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runMinify: function(input, args) { - let result = "", - AST = esprima.parse(input), - optimisedAST = esmangle.optimize(AST, null), - mangledAST = esmangle.mangle(optimisedAST); - - result = escodegen.generate(mangledAST, { - format: { - renumber: true, - hexadecimal: true, - escapeless: true, - compact: true, - semicolons: false, - parentheses: false - } - }); - return result; - }, - -}; - -export default JS; diff --git a/src/core/operations/JSONBeautify.mjs b/src/core/operations/JSONBeautify.mjs new file mode 100644 index 00000000..a2b2dfdd --- /dev/null +++ b/src/core/operations/JSONBeautify.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author Phillip Nordwall [phillip.nordwall@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * JSON Beautify operation + */ +class JSONBeautify extends Operation { + + /** + * JSONBeautify constructor + */ + constructor() { + super(); + + this.name = "JSON Beautify"; + this.module = "Code"; + this.description = "Indents and prettifies JavaScript Object Notation (JSON) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Indent string", + "type": "binaryShortString", + "value": " " + }, + { + "name": "Sort Object Keys", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [indentStr, sortBool] = args; + + if (!input) return ""; + if (sortBool) { + input = JSON.stringify(JSONBeautify._sort(JSON.parse(input))); + } + return vkbeautify.json(input, indentStr); + } + + + /** + * Sort JSON representation of an object + * + * @author Phillip Nordwall [phillip.nordwall@gmail.com] + * @private + * @param {object} o + * @returns {object} + */ + static _sort(o) { + if (Array.isArray(o)) { + return o.map(JSONBeautify._sort); + } else if ("[object Object]" === Object.prototype.toString.call(o)) { + return Object.keys(o).sort().reduce(function(a, k) { + a[k] = JSONBeautify._sort(o[k]); + return a; + }, {}); + } + return o; + } +} + +export default JSONBeautify; diff --git a/src/core/operations/JSONMinify.mjs b/src/core/operations/JSONMinify.mjs new file mode 100644 index 00000000..ca594397 --- /dev/null +++ b/src/core/operations/JSONMinify.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * JSON Minify operation + */ +class JSONMinify extends Operation { + + /** + * JSONMinify constructor + */ + constructor() { + super(); + + this.name = "JSON Minify"; + this.module = "Code"; + this.description = "Compresses JavaScript Object Notation (JSON) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + return vkbeautify.jsonmin(input); + } + +} + +export default JSONMinify; diff --git a/src/core/operations/JSONToCSV.mjs b/src/core/operations/JSONToCSV.mjs new file mode 100644 index 00000000..c3d078e3 --- /dev/null +++ b/src/core/operations/JSONToCSV.mjs @@ -0,0 +1,111 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * JSON to CSV operation + */ +class JSONToCSV extends Operation { + + /** + * JSONToCSV constructor + */ + constructor() { + super(); + + this.name = "JSON to CSV"; + this.module = "Default"; + this.description = "Converts JSON data to a CSV based on the definition in RFC 4180."; + this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = [ + { + name: "Cell delimiter", + type: "binaryShortString", + value: "," + }, + { + name: "Row delimiter", + type: "binaryShortString", + value: "\\r\\n" + } + ]; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [cellDelim, rowDelim] = args; + + // Record values so they don't have to be passed to other functions explicitly + this.cellDelim = cellDelim; + this.rowDelim = rowDelim; + const self = this; + + try { + // If the JSON is an array of arrays, this is easy + if (input[0] instanceof Array) { + return input + .map(row => row + .map(self.escapeCellContents.bind(self)) + .join(cellDelim) + ) + .join(rowDelim) + + rowDelim; + } + + // If it's an array of dictionaries... + const header = Object.keys(input[0]); + return header + .map(self.escapeCellContents.bind(self)) + .join(cellDelim) + + rowDelim + + input + .map(row => header + .map(h => row[h]) + .map(self.escapeCellContents.bind(self)) + .join(cellDelim) + ) + .join(rowDelim) + + rowDelim; + } catch (err) { + throw new OperationError("Unable to parse JSON to CSV: " + err.toString()); + } + } + + /** + * Correctly escapes a cell's contents based on the cell and row delimiters. + * + * @param {string} data + * @returns {string} + */ + escapeCellContents(data) { + // Double quotes should be doubled up + data = data.replace(/"/g, '""'); + + // If the cell contains a cell or row delimiter or a double quote, it mut be enclosed in double quotes + if ( + data.indexOf(this.cellDelim) >= 0 || + data.indexOf(this.rowDelim) >= 0 || + data.indexOf("\n") >= 0 || + data.indexOf("\r") >= 0 || + data.indexOf('"') >= 0 + ) { + data = `"${data}"`; + } + + return data; + } + +} + +export default JSONToCSV; diff --git a/src/core/operations/JWTDecode.mjs b/src/core/operations/JWTDecode.mjs new file mode 100644 index 00000000..2166a447 --- /dev/null +++ b/src/core/operations/JWTDecode.mjs @@ -0,0 +1,51 @@ +/** + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import jwt from "jsonwebtoken"; +import OperationError from "../errors/OperationError"; + +/** + * JWT Decode operation + */ +class JWTDecode extends Operation { + + /** + * JWTDecode constructor + */ + constructor() { + super(); + + this.name = "JWT Decode"; + this.module = "Crypto"; + this.description = "Decodes a JSON Web Token without checking whether the provided secret / private key is valid. Use 'JWT Verify' to check if the signature is valid as well."; + this.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token"; + this.inputType = "string"; + this.outputType = "JSON"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + try { + const decoded = jwt.decode(input, { + json: true, + complete: true + }); + + return decoded.payload; + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default JWTDecode; diff --git a/src/core/operations/JWTSign.mjs b/src/core/operations/JWTSign.mjs new file mode 100644 index 00000000..b3f79b57 --- /dev/null +++ b/src/core/operations/JWTSign.mjs @@ -0,0 +1,74 @@ +/** + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import jwt from "jsonwebtoken"; +import OperationError from "../errors/OperationError"; + +/** + * JWT Sign operation + */ +class JWTSign extends Operation { + + /** + * JWTSign constructor + */ + constructor() { + super(); + + this.name = "JWT Sign"; + this.module = "Crypto"; + this.description = "Signs a JSON object as a JSON Web Token using a provided secret / private key.

The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA."; + this.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = [ + { + name: "Private/Secret Key", + type: "text", + value: "secret" + }, + { + name: "Signing algorithm", + type: "option", + value: [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "None" + ] + } + ]; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key, algorithm] = args; + + try { + return jwt.sign(input, key, { + algorithm: algorithm === "None" ? "none" : algorithm + }); + } catch (err) { + throw new OperationError(`Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA. + +${err}`); + } + } + +} + +export default JWTSign; diff --git a/src/core/operations/JWTVerify.mjs b/src/core/operations/JWTVerify.mjs new file mode 100644 index 00000000..651e7662 --- /dev/null +++ b/src/core/operations/JWTVerify.mjs @@ -0,0 +1,65 @@ +/** + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import jwt from "jsonwebtoken"; +import OperationError from "../errors/OperationError"; + +/** + * JWT Verify operation + */ +class JWTVerify extends Operation { + + /** + * JWTVerify constructor + */ + constructor() { + super(); + + this.name = "JWT Verify"; + this.module = "Crypto"; + this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key.

The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA."; + this.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token"; + this.inputType = "string"; + this.outputType = "JSON"; + this.args = [ + { + name: "Private/Secret Key", + type: "text", + value: "secret" + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key] = args; + + try { + const verified = jwt.verify(input, key, { algorithms: [ + "HS256", + "HS384", + "HS512", + "none" + ]}); + + if (verified.hasOwnProperty("name") && verified.name === "JsonWebTokenError") { + throw new OperationError(verified.message); + } + + return verified; + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default JWTVerify; diff --git a/src/core/operations/JavaScriptBeautify.mjs b/src/core/operations/JavaScriptBeautify.mjs new file mode 100644 index 00000000..a4bf326b --- /dev/null +++ b/src/core/operations/JavaScriptBeautify.mjs @@ -0,0 +1,95 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import escodegen from "escodegen"; +import * as esprima from "esprima"; + +/** + * JavaScript Beautify operation + */ +class JavaScriptBeautify extends Operation { + + /** + * JavaScriptBeautify constructor + */ + constructor() { + super(); + + this.name = "JavaScript Beautify"; + this.module = "Code"; + this.description = "Parses and pretty prints valid JavaScript code. Also works with JavaScript Object Notation (JSON)."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Indent string", + "type": "binaryShortString", + "value": "\\t" + }, + { + "name": "Quotes", + "type": "option", + "value": ["Auto", "Single", "Double"] + }, + { + "name": "Semicolons before closing braces", + "type": "boolean", + "value": true + }, + { + "name": "Include comments", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const beautifyIndent = args[0] || "\\t", + quotes = args[1].toLowerCase(), + [,, beautifySemicolons, beautifyComment] = args; + let result = "", + AST; + + try { + AST = esprima.parseScript(input, { + range: true, + tokens: true, + comment: true + }); + + const options = { + format: { + indent: { + style: beautifyIndent + }, + quotes: quotes, + semicolons: beautifySemicolons, + }, + comment: beautifyComment + }; + + if (options.comment) + AST = escodegen.attachComments(AST, AST.comments, AST.tokens); + + result = escodegen.generate(AST, options); + } catch (e) { + // Leave original error so the user can see the detail + throw new OperationError("Unable to parse JavaScript.
" + e.message); + } + return result; + } + +} + +export default JavaScriptBeautify; diff --git a/src/core/operations/JavaScriptMinify.mjs b/src/core/operations/JavaScriptMinify.mjs new file mode 100644 index 00000000..9841d5bb --- /dev/null +++ b/src/core/operations/JavaScriptMinify.mjs @@ -0,0 +1,57 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import * as esprima from "esprima"; +import escodegen from "escodegen"; +import esmangle from "esmangle"; + +/** + * JavaScript Minify operation + */ +class JavaScriptMinify extends Operation { + + /** + * JavaScriptMinify constructor + */ + constructor() { + super(); + + this.name = "JavaScript Minify"; + this.module = "Code"; + this.description = "Compresses JavaScript code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let result = ""; + const AST = esprima.parseScript(input), + optimisedAST = esmangle.optimize(AST, null), + mangledAST = esmangle.mangle(optimisedAST); + + result = escodegen.generate(mangledAST, { + format: { + renumber: true, + hexadecimal: true, + escapeless: true, + compact: true, + semicolons: false, + parentheses: false + } + }); + return result; + } + +} + +export default JavaScriptMinify; diff --git a/src/core/operations/JavaScriptParser.mjs b/src/core/operations/JavaScriptParser.mjs new file mode 100644 index 00000000..40ee7a57 --- /dev/null +++ b/src/core/operations/JavaScriptParser.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import * as esprima from "esprima"; + +/** + * JavaScript Parser operation + */ +class JavaScriptParser extends Operation { + + /** + * JavaScriptParser constructor + */ + constructor() { + super(); + + this.name = "JavaScript Parser"; + this.module = "Code"; + this.description = "Returns an Abstract Syntax Tree for valid JavaScript code."; + this.infoURL = "https://en.wikipedia.org/wiki/Abstract_syntax_tree"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Location info", + "type": "boolean", + "value": false + }, + { + "name": "Range info", + "type": "boolean", + "value": false + }, + { + "name": "Include tokens array", + "type": "boolean", + "value": false + }, + { + "name": "Include comments array", + "type": "boolean", + "value": false + }, + { + "name": "Report errors and try to continue", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [parseLoc, parseRange, parseTokens, parseComment, parseTolerant] = args, + options = { + loc: parseLoc, + range: parseRange, + tokens: parseTokens, + comment: parseComment, + tolerant: parseTolerant + }; + let result = {}; + + result = esprima.parseScript(input, options); + return JSON.stringify(result, null, 2); + } + +} + +export default JavaScriptParser; diff --git a/src/core/operations/Jump.mjs b/src/core/operations/Jump.mjs new file mode 100644 index 00000000..30fca5a0 --- /dev/null +++ b/src/core/operations/Jump.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { getLabelIndex } from "../lib/FlowControl"; + +/** + * Jump operation + */ +class Jump extends Operation { + + /** + * Jump constructor + */ + constructor() { + super(); + + this.name = "Jump"; + this.flowControl = true; + this.module = "Default"; + this.description = "Jump forwards or backwards to the specified Label"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Label name", + "type": "string", + "value": "" + }, + { + "name": "Maximum jumps (if jumping backwards)", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @param {number} state.numJumps - The number of jumps taken so far. + * @returns {Object} The updated state of the recipe. + */ + run(state) { + const ings = state.opList[state.progress].ingValues; + const [label, maxJumps] = ings; + const jmpIndex = getLabelIndex(label, state); + + if (state.numJumps >= maxJumps || jmpIndex === -1) { + return state; + } + + state.progress = jmpIndex; + state.numJumps++; + return state; + } + +} + +export default Jump; diff --git a/src/core/operations/Keccak.mjs b/src/core/operations/Keccak.mjs new file mode 100644 index 00000000..0c930589 --- /dev/null +++ b/src/core/operations/Keccak.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import JSSHA3 from "js-sha3"; +import OperationError from "../errors/OperationError"; + +/** + * Keccak operation + */ +class Keccak extends Operation { + + /** + * Keccak constructor + */ + constructor() { + super(); + + this.name = "Keccak"; + this.module = "Crypto"; + this.description = "The Keccak hash algorithm was designed by Guido Bertoni, Joan Daemen, Micha\xebl Peeters, and Gilles Van Assche, building upon RadioGat\xfan. It was selected as the winner of the SHA-3 design competition.

This version of the algorithm is Keccak[c=2d] and differs from the SHA-3 specification."; + this.infoURL = "https://wikipedia.org/wiki/SHA-3"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Size", + "type": "option", + "value": ["512", "384", "256", "224"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = parseInt(args[0], 10); + let algo; + + switch (size) { + case 224: + algo = JSSHA3.keccak224; + break; + case 384: + algo = JSSHA3.keccak384; + break; + case 256: + algo = JSSHA3.keccak256; + break; + case 512: + algo = JSSHA3.keccak512; + break; + default: + throw new OperationError("Invalid size"); + } + + return algo(input); + } + +} + +export default Keccak; diff --git a/src/core/operations/Label.mjs b/src/core/operations/Label.mjs new file mode 100644 index 00000000..1444f3ac --- /dev/null +++ b/src/core/operations/Label.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Label operation. For use with Jump and Conditional Jump. + */ +class Label extends Operation { + + /** + * Label constructor + */ + constructor() { + super(); + + this.name = "Label"; + this.flowControl = true; + this.module = "Default"; + this.description = "Provides a location for conditional and fixed jumps to redirect execution to."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Name", + "type": "shortString", + "value": "" + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + run(state) { + return state; + } + +} + +export default Label; diff --git a/src/core/operations/MAC.js b/src/core/operations/MAC.js deleted file mode 100755 index 4beb939f..00000000 --- a/src/core/operations/MAC.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * MAC address operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const MAC = { - - /** - * @constant - * @default - */ - OUTPUT_CASE: ["Both", "Upper only", "Lower only"], - /** - * @constant - * @default - */ - NO_DELIM: true, - /** - * @constant - * @default - */ - DASH_DELIM: true, - /** - * @constant - * @default - */ - COLON_DELIM: true, - /** - * @constant - * @default - */ - CISCO_STYLE: false, - - /** - * Format MAC addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFormat: function(input, args) { - if (!input) return ""; - - let outputCase = args[0], - noDelim = args[1], - dashDelim = args[2], - colonDelim = args[3], - ciscoStyle = args[4], - outputList = [], - macs = input.toLowerCase().split(/[,\s\r\n]+/); - - macs.forEach(function(mac) { - let cleanMac = mac.replace(/[:.-]+/g, ""), - macHyphen = cleanMac.replace(/(.{2}(?=.))/g, "$1-"), - macColon = cleanMac.replace(/(.{2}(?=.))/g, "$1:"), - macCisco = cleanMac.replace(/(.{4}(?=.))/g, "$1."); - - if (outputCase === "Lower only") { - if (noDelim) outputList.push(cleanMac); - if (dashDelim) outputList.push(macHyphen); - if (colonDelim) outputList.push(macColon); - if (ciscoStyle) outputList.push(macCisco); - } else if (outputCase === "Upper only") { - if (noDelim) outputList.push(cleanMac.toUpperCase()); - if (dashDelim) outputList.push(macHyphen.toUpperCase()); - if (colonDelim) outputList.push(macColon.toUpperCase()); - if (ciscoStyle) outputList.push(macCisco.toUpperCase()); - } else { - if (noDelim) outputList.push(cleanMac, cleanMac.toUpperCase()); - if (dashDelim) outputList.push(macHyphen, macHyphen.toUpperCase()); - if (colonDelim) outputList.push(macColon, macColon.toUpperCase()); - if (ciscoStyle) outputList.push(macCisco, macCisco.toUpperCase()); - } - - outputList.push( - "" // Empty line to delimit groups - ); - }); - - // Return the data as a string - return outputList.join("\n"); - }, - -}; - -export default MAC; diff --git a/src/core/operations/MD2.mjs b/src/core/operations/MD2.mjs new file mode 100644 index 00000000..dfe2c7a3 --- /dev/null +++ b/src/core/operations/MD2.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * MD2 operation + */ +class MD2 extends Operation { + + /** + * MD2 constructor + */ + constructor() { + super(); + + this.name = "MD2"; + this.module = "Crypto"; + this.description = "The MD2 (Message-Digest 2) algorithm is a cryptographic hash function developed by Ronald Rivest in 1989. The algorithm is optimized for 8-bit computers.

Although MD2 is no longer considered secure, even as of 2014, it remains in use in public key infrastructures as part of certificates generated with MD2 and RSA."; + this.infoURL = "https://wikipedia.org/wiki/MD2_(cryptography)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("md2", input); + } + +} + +export default MD2; diff --git a/src/core/operations/MD4.mjs b/src/core/operations/MD4.mjs new file mode 100644 index 00000000..7872c7b8 --- /dev/null +++ b/src/core/operations/MD4.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * MD4 operation + */ +class MD4 extends Operation { + + /** + * MD4 constructor + */ + constructor() { + super(); + + this.name = "MD4"; + this.module = "Crypto"; + this.description = "The MD4 (Message-Digest 4) algorithm is a cryptographic hash function developed by Ronald Rivest in 1990. The digest length is 128 bits. The algorithm has influenced later designs, such as the MD5, SHA-1 and RIPEMD algorithms.

The security of MD4 has been severely compromised."; + this.infoURL = "https://wikipedia.org/wiki/MD4"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("md4", input); + } + +} + +export default MD4; diff --git a/src/core/operations/MD5.mjs b/src/core/operations/MD5.mjs new file mode 100644 index 00000000..96de7108 --- /dev/null +++ b/src/core/operations/MD5.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * MD5 operation + */ +class MD5 extends Operation { + + /** + * MD5 constructor + */ + constructor() { + super(); + + this.name = "MD5"; + this.module = "Crypto"; + this.description = "MD5 (Message-Digest 5) is a widely used hash function. It has been used in a variety of security applications and is also commonly used to check the integrity of files.

However, MD5 is not collision resistant and it isn't suitable for applications like SSL/TLS certificates or digital signatures that rely on this property."; + this.infoURL = "https://wikipedia.org/wiki/MD5"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("md5", input); + } + +} + +export default MD5; diff --git a/src/core/operations/MD6.mjs b/src/core/operations/MD6.mjs new file mode 100644 index 00000000..0e53a1b0 --- /dev/null +++ b/src/core/operations/MD6.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import NodeMD6 from "node-md6"; + +/** + * MD6 operation + */ +class MD6 extends Operation { + + /** + * MD6 constructor + */ + constructor() { + super(); + + this.name = "MD6"; + this.module = "Crypto"; + this.description = "The MD6 (Message-Digest 6) algorithm is a cryptographic hash function. It uses a Merkle tree-like structure to allow for immense parallel computation of hashes for very long inputs."; + this.infoURL = "https://wikipedia.org/wiki/MD6"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Size", + "type": "number", + "value": 256 + }, + { + "name": "Levels", + "type": "number", + "value": 64 + }, + { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [size, levels, key] = args; + + if (size < 0 || size > 512) + throw new OperationError("Size must be between 0 and 512"); + if (levels < 0) + throw new OperationError("Levels must be greater than 0"); + + return NodeMD6.getHashOfText(input, size, key, levels); + } + +} + +export default MD6; diff --git a/src/core/operations/Magic.mjs b/src/core/operations/Magic.mjs new file mode 100644 index 00000000..1555ebad --- /dev/null +++ b/src/core/operations/Magic.mjs @@ -0,0 +1,168 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import Dish from "../Dish"; +import MagicLib from "../lib/Magic"; + +/** + * Magic operation + */ +class Magic extends Operation { + + /** + * Magic constructor + */ + constructor() { + super(); + + this.name = "Magic"; + this.flowControl = true; + this.module = "Default"; + this.description = "The Magic operation attempts to detect various properties of the input data and suggests which operations could help to make more sense of it.

Options
Depth: If an operation appears to match the data, it will be run and the result will be analysed further. This argument controls the maximum number of levels of recursion.

Intensive mode: When this is turned on, various operations like XOR, bit rotates, and character encodings are brute-forced to attempt to detect valid data underneath. To improve performance, only the first 100 bytes of the data is brute-forced.

Extensive language support: At each stage, the relative byte frequencies of the data will be compared to average frequencies for a number of languages. The default set consists of ~40 of the most commonly used languages on the Internet. The extensive list consists of 284 languages and can result in many languages matching the data if their byte frequencies are similar.

Optionally enter a regular expression to match a string you expect to find to filter results (crib)."; + this.infoURL = "https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.presentType = "html"; + this.args = [ + { + "name": "Depth", + "type": "number", + "value": 3 + }, + { + "name": "Intensive mode", + "type": "boolean", + "value": false + }, + { + "name": "Extensive language support", + "type": "boolean", + "value": false + }, + { + "name": "Crib (known plaintext string or regex)", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + async run(state) { + const ings = state.opList[state.progress].ingValues, + [depth, intensive, extLang, crib] = ings, + dish = state.dish, + magic = new MagicLib(await dish.get(Dish.ARRAY_BUFFER)), + cribRegex = (crib && crib.length) ? new RegExp(crib, "i") : null; + let options = await magic.speculativeExecution(depth, extLang, intensive, [], false, cribRegex); + + // Filter down to results which matched the crib + if (cribRegex) { + options = options.filter(option => option.matchesCrib); + } + + // Record the current state for use when presenting + this.state = state; + + dish.set(options, Dish.JSON); + return state; + } + + /** + * Displays Magic results in HTML for web apps. + * + * @param {JSON} options + * @returns {html} + */ + present(options) { + const currentRecipeConfig = this.state.opList.map(op => op.config); + + let output = ` + + + + + `; + + /** + * Returns a CSS colour value based on an integer input. + * + * @param {number} val + * @returns {string} + */ + function chooseColour(val) { + if (val < 3) return "green"; + if (val < 5) return "goldenrod"; + return "red"; + } + + options.forEach(option => { + // Construct recipe URL + // Replace this Magic op with the generated recipe + const recipeConfig = currentRecipeConfig.slice(0, this.state.progress) + .concat(option.recipe) + .concat(currentRecipeConfig.slice(this.state.progress + 1)), + recipeURL = "recipe=" + Utils.encodeURIFragment(Utils.generatePrettyRecipe(recipeConfig)); + + let language = "", + fileType = "", + matchingOps = "", + useful = ""; + const entropy = `Entropy: ${option.entropy.toFixed(2)}`, + validUTF8 = option.isUTF8 ? "Valid UTF8\n" : ""; + + if (option.languageScores[0].probability > 0) { + let likelyLangs = option.languageScores.filter(l => l.probability > 0); + if (likelyLangs.length < 1) likelyLangs = [option.languageScores[0]]; + language = "" + + "Possible languages:\n " + + likelyLangs.map(lang => { + return MagicLib.codeToLanguage(lang.lang); + }).join("\n ") + + "\n"; + } + + if (option.fileType) { + fileType = `File type: ${option.fileType.mime} (${option.fileType.ext})\n`; + } + + if (option.matchingOps.length) { + matchingOps = `Matching ops: ${[...new Set(option.matchingOps.map(op => op.op))].join(", ")}\n`; + } + + if (option.useful) { + useful = "Useful op detected\n"; + } + + output += ` + + + + `; + }); + + output += "
Recipe (click to load)Result snippetProperties
${Utils.generatePrettyRecipe(option.recipe, true)}${Utils.escapeHtml(Utils.printable(Utils.truncate(option.data, 99)))}${language}${fileType}${matchingOps}${useful}${validUTF8}${entropy}
"; + + if (!options.length) { + output = "Nothing of interest could be detected about the input data.\nHave you tried modifying the operation arguments?"; + } + + return output; + } + +} + +export default Magic; diff --git a/src/core/operations/Mean.mjs b/src/core/operations/Mean.mjs new file mode 100644 index 00000000..a7b110ce --- /dev/null +++ b/src/core/operations/Mean.mjs @@ -0,0 +1,51 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { mean, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; +import BigNumber from "bignumber.js"; + +/** + * Mean operation + */ +class Mean extends Operation { + + /** + * Mean constructor + */ + constructor() { + super(); + + this.name = "Mean"; + this.module = "Default"; + this.description = "Computes the mean (average) of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 .5 becomes 4.75"; + this.infoURL = "https://wikipedia.org/wiki/Arithmetic_mean"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = mean(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Mean; diff --git a/src/core/operations/Median.mjs b/src/core/operations/Median.mjs new file mode 100644 index 00000000..7101a403 --- /dev/null +++ b/src/core/operations/Median.mjs @@ -0,0 +1,51 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { median, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + +/** + * Median operation + */ +class Median extends Operation { + + /** + * Median constructor + */ + constructor() { + super(); + + this.name = "Median"; + this.module = "Default"; + this.description = "Computes the median of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 1 .5 becomes 4.5"; + this.infoURL = "https://wikipedia.org/wiki/Median"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = median(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Median; diff --git a/src/core/operations/Merge.mjs b/src/core/operations/Merge.mjs new file mode 100644 index 00000000..462660c4 --- /dev/null +++ b/src/core/operations/Merge.mjs @@ -0,0 +1,44 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Merge operation + */ +class Merge extends Operation { + + /** + * Merge constructor + */ + constructor() { + super(); + + this.name = "Merge"; + this.flowControl = true; + this.module = "Default"; + this.description = "Consolidate all branches back into a single trunk. The opposite of Fork."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + run(state) { + // No need to actually do anything here. The fork operation will + // merge when it sees this operation. + return state; + } + +} + +export default Merge; diff --git a/src/core/operations/MicrosoftScriptDecoder.mjs b/src/core/operations/MicrosoftScriptDecoder.mjs new file mode 100644 index 00000000..bb475ffc --- /dev/null +++ b/src/core/operations/MicrosoftScriptDecoder.mjs @@ -0,0 +1,218 @@ +/** + * @author bmwhitn [brian.m.whitney@outlook.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Microsoft Script Decoder operation + */ +class MicrosoftScriptDecoder extends Operation { + + /** + * MicrosoftScriptDecoder constructor + */ + constructor() { + super(); + + this.name = "Microsoft Script Decoder"; + this.module = "Default"; + this.description = "Decodes Microsoft Encoded Script files that have been encoded with Microsoft's custom encoding. These are often VBS (Visual Basic Script) files that are encoded and renamed with a '.vbe' extention or JS (JScript) files renamed with a '.jse' extention.

Sample

Encoded:
#@~^RQAAAA==-mD~sX|:/TP{~J:+dYbxL~@!F@*@!+@*@!&@*eEI@#@&@#@&.jm.raY 214Wv:zms/obI0xEAAA==^#~@

Decoded:
var my_msg = "Testing <1><2><3>!";\n\nVScript.Echo(my_msg);"; + this.infoURL = "https://wikipedia.org/wiki/JScript.Encode"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const matcher = /#@~\^.{6}==(.+).{6}==\^#~@/; + const encodedData = matcher.exec(input); + if (encodedData){ + return MicrosoftScriptDecoder._decode(encodedData[1]); + } else { + return ""; + } + } + + /** + * Decodes Microsoft Encoded Script files that can be read and executed by cscript.exe/wscript.exe. + * This is a conversion of a Python script that was originally created by Didier Stevens + * (https://DidierStevens.com). + * + * @private + * @param {string} data + * @returns {string} + */ + static _decode(data) { + const result = []; + let index = -1; + data = data.replace(/@&/g, String.fromCharCode(10)) + .replace(/@#/g, String.fromCharCode(13)) + .replace(/@\*/g, ">") + .replace(/@!/g, "<") + .replace(/@\$/g, "@"); + + for (let i = 0; i < data.length; i++) { + const byte = data.charCodeAt(i); + let char = data.charAt(i); + if (byte < 128) { + index++; + } + + if ((byte === 9 || byte > 31 && byte < 128) && + byte !== 60 && + byte !== 62 && + byte !== 64) { + char = D_DECODE[byte].charAt(D_COMBINATION[index % 64]); + } + result.push(char); + } + return result.join(""); + } + +} + +const D_DECODE = [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "\x57\x6E\x7B", + "\x4A\x4C\x41", + "\x0B\x0B\x0B", + "\x0C\x0C\x0C", + "\x4A\x4C\x41", + "\x0E\x0E\x0E", + "\x0F\x0F\x0F", + "\x10\x10\x10", + "\x11\x11\x11", + "\x12\x12\x12", + "\x13\x13\x13", + "\x14\x14\x14", + "\x15\x15\x15", + "\x16\x16\x16", + "\x17\x17\x17", + "\x18\x18\x18", + "\x19\x19\x19", + "\x1A\x1A\x1A", + "\x1B\x1B\x1B", + "\x1C\x1C\x1C", + "\x1D\x1D\x1D", + "\x1E\x1E\x1E", + "\x1F\x1F\x1F", + "\x2E\x2D\x32", + "\x47\x75\x30", + "\x7A\x52\x21", + "\x56\x60\x29", + "\x42\x71\x5B", + "\x6A\x5E\x38", + "\x2F\x49\x33", + "\x26\x5C\x3D", + "\x49\x62\x58", + "\x41\x7D\x3A", + "\x34\x29\x35", + "\x32\x36\x65", + "\x5B\x20\x39", + "\x76\x7C\x5C", + "\x72\x7A\x56", + "\x43\x7F\x73", + "\x38\x6B\x66", + "\x39\x63\x4E", + "\x70\x33\x45", + "\x45\x2B\x6B", + "\x68\x68\x62", + "\x71\x51\x59", + "\x4F\x66\x78", + "\x09\x76\x5E", + "\x62\x31\x7D", + "\x44\x64\x4A", + "\x23\x54\x6D", + "\x75\x43\x71", + "\x4A\x4C\x41", + "\x7E\x3A\x60", + "\x4A\x4C\x41", + "\x5E\x7E\x53", + "\x40\x4C\x40", + "\x77\x45\x42", + "\x4A\x2C\x27", + "\x61\x2A\x48", + "\x5D\x74\x72", + "\x22\x27\x75", + "\x4B\x37\x31", + "\x6F\x44\x37", + "\x4E\x79\x4D", + "\x3B\x59\x52", + "\x4C\x2F\x22", + "\x50\x6F\x54", + "\x67\x26\x6A", + "\x2A\x72\x47", + "\x7D\x6A\x64", + "\x74\x39\x2D", + "\x54\x7B\x20", + "\x2B\x3F\x7F", + "\x2D\x38\x2E", + "\x2C\x77\x4C", + "\x30\x67\x5D", + "\x6E\x53\x7E", + "\x6B\x47\x6C", + "\x66\x34\x6F", + "\x35\x78\x79", + "\x25\x5D\x74", + "\x21\x30\x43", + "\x64\x23\x26", + "\x4D\x5A\x76", + "\x52\x5B\x25", + "\x63\x6C\x24", + "\x3F\x48\x2B", + "\x7B\x55\x28", + "\x78\x70\x23", + "\x29\x69\x41", + "\x28\x2E\x34", + "\x73\x4C\x09", + "\x59\x21\x2A", + "\x33\x24\x44", + "\x7F\x4E\x3F", + "\x6D\x50\x77", + "\x55\x09\x3B", + "\x53\x56\x55", + "\x7C\x73\x69", + "\x3A\x35\x61", + "\x5F\x61\x63", + "\x65\x4B\x50", + "\x46\x58\x67", + "\x58\x3B\x51", + "\x31\x57\x49", + "\x69\x22\x4F", + "\x6C\x6D\x46", + "\x5A\x4D\x68", + "\x48\x25\x7C", + "\x27\x28\x36", + "\x5C\x46\x70", + "\x3D\x4A\x6E", + "\x24\x32\x7A", + "\x79\x41\x2F", + "\x37\x3D\x5F", + "\x60\x5F\x4B", + "\x51\x4F\x5A", + "\x20\x42\x2C", + "\x36\x65\x57" +]; + +const D_COMBINATION = [ + 0, 1, 2, 0, 1, 2, 1, 2, 2, 1, 2, 1, 0, 2, 1, 2, 0, 2, 1, 2, 0, 0, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1, + 0, 0, 2, 1, 2, 1, 2, 0, 2, 0, 0, 1, 2, 0, 2, 1, 0, 2, 1, 2, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 2, 1 +]; + +export default MicrosoftScriptDecoder; diff --git a/src/core/operations/MorseCode.js b/src/core/operations/MorseCode.js deleted file mode 100644 index ae5d091b..00000000 --- a/src/core/operations/MorseCode.js +++ /dev/null @@ -1,190 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Morse Code translation operations. - * - * @author tlwr [toby@toby.codes] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const MorseCode = { - - /** - * @constant - * @default - */ - FORMAT_OPTIONS: ["-/.", "_/.", "Dash/Dot", "DASH/DOT", "dash/dot"], - /** - * @constant - * @default - */ - LETTER_DELIM_OPTIONS: ["Space", "Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"], - /** - * @constant - * @default - */ - WORD_DELIM_OPTIONS: ["Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"], - /** - * @constant - * @default - */ - MORSE_TABLE: { - "A": "", - "B": "", - "C": "", - "D": "", - "E": "", - "F": "", - "G": "", - "H": "", - "I": "", - "J": "", - "K": "", - "L": "", - "M": "", - "N": "", - "O": "", - "P": "", - "Q": "", - "R": "", - "S": "", - "T": "", - "U": "", - "V": "", - "W": "", - "X": "", - "Y": "", - "Z": "", - "1": "", - "2": "", - "3": "", - "4": "", - "5": "", - "6": "", - "7": "", - "8": "", - "9": "", - "0": "", - ".": "", - ",": "", - ":": "", - ";": "", - "!": "", - "?": "", - "'": "", - "\"": "", - "/": "", - "-": "", - "+": "", - "(": "", - ")": "", - "@": "", - "=": "", - "&": "", - "_": "", - "$": "" - }, - - - /** - * To Morse Code operation. - * - * @param {number} input - * @param {Object[]} args - * @returns {string} - */ - runTo: function(input, args) { - const format = args[0].split("/"); - const dash = format[0]; - const dot = format[1]; - - const letterDelim = Utils.charRep[args[1]]; - const wordDelim = Utils.charRep[args[2]]; - - input = input.split(/\r?\n/); - input = Array.prototype.map.call(input, function(line) { - let words = line.split(/ +/); - words = Array.prototype.map.call(words, function(word) { - const letters = Array.prototype.map.call(word, function(character) { - const letter = character.toUpperCase(); - if (typeof MorseCode.MORSE_TABLE[letter] == "undefined") { - return ""; - } - - return MorseCode.MORSE_TABLE[letter]; - }); - - return letters.join(""); - }); - line = words.join(""); - return line; - }); - input = input.join("\n"); - - input = input.replace( - /|||/g, - function(match) { - switch (match) { - case "": return dash; - case "": return dot; - case "": return letterDelim; - case "": return wordDelim; - } - } - ); - - return input; - }, - - - /** - * From Morse Code operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFrom: (function() { - let reversedTable = null; - const reverseTable = function() { - reversedTable = {}; - - for (const letter in MorseCode.MORSE_TABLE) { - const signal = MorseCode.MORSE_TABLE[letter]; - reversedTable[signal] = letter; - } - }; - - return function(input, args) { - if (reversedTable === null) { - reverseTable(); - } - - const letterDelim = Utils.charRep[args[0]]; - const wordDelim = Utils.charRep[args[1]]; - - input = input.replace(/-|‐|−|_|–|—|dash/ig, ""); //hyphen-minus|hyphen|minus-sign|undersore|en-dash|em-dash - input = input.replace(/\.|·|dot/ig, ""); - - let words = input.split(wordDelim); - words = Array.prototype.map.call(words, function(word) { - const signals = word.split(letterDelim); - - const letters = signals.map(function(signal) { - return reversedTable[signal]; - }); - - return letters.join(""); - }); - words = words.join(" "); - - return words; - }; - })(), - -}; - -export default MorseCode; diff --git a/src/core/operations/Multiply.mjs b/src/core/operations/Multiply.mjs new file mode 100644 index 00000000..825078e2 --- /dev/null +++ b/src/core/operations/Multiply.mjs @@ -0,0 +1,52 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { multi, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + + +/** + * Multiply operation + */ +class Multiply extends Operation { + + /** + * Multiply constructor + */ + constructor() { + super(); + + this.name = "Multiply"; + this.module = "Default"; + this.description = "Multiplies a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 40"; + this.infoURL = "https://wikipedia.org/wiki/Multiplication"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = multi(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Multiply; diff --git a/src/core/operations/NOT.mjs b/src/core/operations/NOT.mjs new file mode 100644 index 00000000..e7f00393 --- /dev/null +++ b/src/core/operations/NOT.mjs @@ -0,0 +1,67 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { bitOp, not } from "../lib/BitwiseOp"; + +/** + * NOT operation + */ +class NOT extends Operation { + + /** + * NOT constructor + */ + constructor() { + super(); + + this.name = "NOT"; + this.module = "Default"; + this.description = "Returns the inverse of each byte."; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#NOT"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + return bitOp(input, null, not); + } + + /** + * Highlight NOT + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight NOT in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default NOT; diff --git a/src/core/operations/NetBIOS.js b/src/core/operations/NetBIOS.js deleted file mode 100644 index 0927775a..00000000 --- a/src/core/operations/NetBIOS.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * NetBIOS operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const NetBIOS = { - - /** - * @constant - * @default - */ - OFFSET: 65, - - /** - * Encode NetBIOS Name operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runEncodeName: function(input, args) { - let output = [], - offset = args[0]; - - for (let i = 0; i < input.length; i++) { - output.push((input[i] >> 4) + offset); - output.push((input[i] & 0xf) + offset); - } - - return output; - }, - - - /** - * Decode NetBIOS Name operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runDecodeName: function(input, args) { - let output = [], - offset = args[0]; - - for (let i = 0; i < input.length; i += 2) { - output.push(((input[i] - offset) << 4) | - ((input[i + 1] - offset) & 0xf)); - } - - return output; - }, - -}; - -export default NetBIOS; diff --git a/src/core/operations/Numberwang.js b/src/core/operations/Numberwang.js deleted file mode 100755 index 9d0fce68..00000000 --- a/src/core/operations/Numberwang.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Numberwang operations. - * - * @author Unknown Male 282 - * @namespace - */ -const Numberwang = { - - /** - * Numberwang operation. Remain indoors. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - run: function(input, args) { - if (!input) return "Let's play Wangernumb!"; - const match = input.match(/\d+/); - if (match) { - return match[0] + "! That's Numberwang!"; - } else { - // That's a bad miss! - return "Sorry, that's not Numberwang. Let's rotate the board!"; - } - }, - -}; - -export default Numberwang; diff --git a/src/core/operations/Numberwang.mjs b/src/core/operations/Numberwang.mjs new file mode 100644 index 00000000..96100fbb --- /dev/null +++ b/src/core/operations/Numberwang.mjs @@ -0,0 +1,101 @@ +/** + * @author Unknown Male 282 + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Numberwang operation. Remain indoors. + */ +class Numberwang extends Operation { + + /** + * Numberwang constructor + */ + constructor() { + super(); + + this.name = "Numberwang"; + this.module = "Default"; + this.description = "Based on the popular gameshow by Mitchell and Webb."; + this.infoURL = "https://wikipedia.org/wiki/That_Mitchell_and_Webb_Look#Recurring_sketches"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output; + if (!input) { + output = "Let's play Wangernumb!"; + } else { + const match = input.match(/(f0rty-s1x|shinty-six|filth-hundred and neeb|-?√?\d+(\.\d+)?i?([a-z]?)%?)/i); + if (match) { + if (match[3]) output = match[0] + "! That's AlphaNumericWang!"; + else output = match[0] + "! That's Numberwang!"; + } else { + // That's a bad miss! + output = "Sorry, that's not Numberwang. Let's rotate the board!"; + } + } + + const rand = Math.floor(Math.random() * didYouKnow.length); + return output + "\n\nDid you know: " + didYouKnow[rand]; + } + +} + +/** + * Taken from http://numberwang.wikia.com/wiki/Numberwang_Wikia + * + * @constant + */ +const didYouKnow = [ + "Numberwang, contrary to popular belief, is a fruit and not a vegetable.", + "Robert Webb once got WordWang while presenting an episode of Numberwang.", + "The 6705th digit of pi is Numberwang.", + "Numberwang was invented on a Sevenday.", + "Contrary to popular belief, Albert Einstein always got good grades in Numberwang at school. He once scored ^4$ on a test.", + "680 asteroids have been named after Numberwang.", + "Archimedes is most famous for proclaiming \"That's Numberwang!\" during an epiphany about water displacement he had while taking a bath.", + "Numberwang Day is celebrated in Japan on every day of the year apart from June 6.", + "Biologists recently discovered Numberwang within a strand of human DNA.", + "Numbernot is a special type of non-Numberwang number. It is divisible by 3 and the letter \"y\".", + "Julie once got 612.04 Numberwangs in a single episode of Emmerdale.", + "In India, it is traditional to shout out \"Numberwang!\" instead of checkmate during games of chess.", + "There is a rule on Countdown which states that if you get Numberwang in the numbers round, you automatically win. It has only ever been invoked twice.", + "\"Numberwang\" was the third-most common baby name for a brief period in 1722.", + "\"The Lion King\" was loosely based on Numberwang.", + "\"A Numberwang a day keeps the doctor away\" is how Donny Cosy, the oldest man in the world, explained how he was in such good health at the age of 136.", + "The \"number lock\" button on a keyboard is based on the popular round of the same name in \"Numberwang\".", + "Cambridge became the first university to offer a course in Numberwang in 1567.", + "Schrödinger's Numberwang is a number that has been confusing dentists for centuries.", + "\"Harry Potter and the Numberwang of Numberwang\" was rejected by publishers -41 times before it became a bestseller.", + "\"Numberwang\" is the longest-running British game show in history; it has aired 226 seasons, each containing 19 episodes, which makes a grand total of 132 episodes.", + "The triple Numberwang bonus was discovered by archaeologist Thomas Jefferson in Somerset.", + "Numberwang is illegal in parts of Czechoslovakia.", + "Numberwang was discovered in India in the 12th century.", + "Numberwang has the chemical formula Zn4SO2(HgEs)3.", + "The first pack of cards ever created featured two \"Numberwang\" cards instead of jokers.", + "Julius Caesar was killed by an overdose of Numberwang.", + "The most Numberwang musical note is G#.", + "In 1934, the forty-third Google Doodle promoted the upcoming television show \"Numberwang on Ice\".", + "A recent psychology study found that toddlers were 17% faster at identifying numbers which were Numberwang.", + "There are 700 ways to commit a foul in the television show \"Numberwang\". All 700 of these fouls were committed by Julie in one single episode in 1473.", + "Astronomers suspect God is Numberwang.", + "Numberwang is the official beverage of Canada.", + "In the pilot episode of \"The Price is Right\", if a contestant got the value of an item exactly right they were told \"That's Numberwang!\" and immediately won ₹5.7032.", + "The first person to get three Numberwangs in a row was Madonna.", + "\"Numberwang\" has the code U+46402 in Unicode.", + "The musical note \"Numberwang\" is between D# and E♮.", + "Numberwang was first played on the moon in 1834.", +]; + +export default Numberwang; diff --git a/src/core/operations/OR.mjs b/src/core/operations/OR.mjs new file mode 100644 index 00000000..30948379 --- /dev/null +++ b/src/core/operations/OR.mjs @@ -0,0 +1,77 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import { bitOp, or, BITWISE_OP_DELIMS } from "../lib/BitwiseOp"; + +/** + * OR operation + */ +class OR extends Operation { + + /** + * OR constructor + */ + constructor() { + super(); + + this.name = "OR"; + this.module = "Default"; + this.description = "OR the input with the given key.
e.g. fe023da5"; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#OR"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": BITWISE_OP_DELIMS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); + + return bitOp(input, key, or); + } + + /** + * Highlight OR + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight OR in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default OR; diff --git a/src/core/operations/OS.js b/src/core/operations/OS.js deleted file mode 100755 index 9b8bd96c..00000000 --- a/src/core/operations/OS.js +++ /dev/null @@ -1,311 +0,0 @@ -/** - * Operating system operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const OS = { - - /** - * Parse UNIX file permissions operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseUnixPerms: function(input, args) { - let perms = { - d : false, // directory - sl : false, // symbolic link - np : false, // named pipe - s : false, // socket - cd : false, // character device - bd : false, // block device - dr : false, // door - sb : false, // sticky bit - su : false, // setuid - sg : false, // setgid - ru : false, // read user - wu : false, // write user - eu : false, // execute user - rg : false, // read group - wg : false, // write group - eg : false, // execute group - ro : false, // read other - wo : false, // write other - eo : false // execute other - }, - d = 0, - u = 0, - g = 0, - o = 0, - output = "", - octal = null, - textual = null; - - if (input.search(/\s*[0-7]{1,4}\s*/i) === 0) { - // Input is octal - octal = input.match(/\s*([0-7]{1,4})\s*/i)[1]; - - if (octal.length === 4) { - d = parseInt(octal[0], 8); - u = parseInt(octal[1], 8); - g = parseInt(octal[2], 8); - o = parseInt(octal[3], 8); - } else { - if (octal.length > 0) u = parseInt(octal[0], 8); - if (octal.length > 1) g = parseInt(octal[1], 8); - if (octal.length > 2) o = parseInt(octal[2], 8); - } - - perms.su = d >> 2 & 0x1; - perms.sg = d >> 1 & 0x1; - perms.sb = d & 0x1; - - perms.ru = u >> 2 & 0x1; - perms.wu = u >> 1 & 0x1; - perms.eu = u & 0x1; - - perms.rg = g >> 2 & 0x1; - perms.wg = g >> 1 & 0x1; - perms.eg = g & 0x1; - - perms.ro = o >> 2 & 0x1; - perms.wo = o >> 1 & 0x1; - perms.eo = o & 0x1; - } else if (input.search(/\s*[dlpcbDrwxsStT-]{1,10}\s*/) === 0) { - // Input is textual - textual = input.match(/\s*([dlpcbDrwxsStT-]{1,10})\s*/)[1]; - - switch (textual[0]) { - case "d": - perms.d = true; - break; - case "l": - perms.sl = true; - break; - case "p": - perms.np = true; - break; - case "s": - perms.s = true; - break; - case "c": - perms.cd = true; - break; - case "b": - perms.bd = true; - break; - case "D": - perms.dr = true; - break; - } - - if (textual.length > 1) perms.ru = textual[1] === "r"; - if (textual.length > 2) perms.wu = textual[2] === "w"; - if (textual.length > 3) { - switch (textual[3]) { - case "x": - perms.eu = true; - break; - case "s": - perms.eu = true; - perms.su = true; - break; - case "S": - perms.su = true; - break; - } - } - - if (textual.length > 4) perms.rg = textual[4] === "r"; - if (textual.length > 5) perms.wg = textual[5] === "w"; - if (textual.length > 6) { - switch (textual[6]) { - case "x": - perms.eg = true; - break; - case "s": - perms.eg = true; - perms.sg = true; - break; - case "S": - perms.sg = true; - break; - } - } - - if (textual.length > 7) perms.ro = textual[7] === "r"; - if (textual.length > 8) perms.wo = textual[8] === "w"; - if (textual.length > 9) { - switch (textual[9]) { - case "x": - perms.eo = true; - break; - case "t": - perms.eo = true; - perms.sb = true; - break; - case "T": - perms.sb = true; - break; - } - } - } else { - return "Invalid input format.\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format."; - } - - output += "Textual representation: " + OS._permsToStr(perms); - output += "\nOctal representation: " + OS._permsToOctal(perms); - - // File type - if (textual) { - output += "\nFile type: " + OS._ftFromPerms(perms); - } - - // setuid, setgid - if (perms.su) { - output += "\nThe setuid flag is set"; - } - if (perms.sg) { - output += "\nThe setgid flag is set"; - } - - // sticky bit - if (perms.sb) { - output += "\nThe sticky bit is set"; - } - - // Permission matrix - output += "\n\n +---------+-------+-------+-------+\n" + - " | | User | Group | Other |\n" + - " +---------+-------+-------+-------+\n" + - " | Read | " + (perms.ru ? "X" : " ") + " | " + (perms.rg ? "X" : " ") + " | " + (perms.ro ? "X" : " ") + " |\n" + - " +---------+-------+-------+-------+\n" + - " | Write | " + (perms.wu ? "X" : " ") + " | " + (perms.wg ? "X" : " ") + " | " + (perms.wo ? "X" : " ") + " |\n" + - " +---------+-------+-------+-------+\n" + - " | Execute | " + (perms.eu ? "X" : " ") + " | " + (perms.eg ? "X" : " ") + " | " + (perms.eo ? "X" : " ") + " |\n" + - " +---------+-------+-------+-------+\n"; - - return output; - }, - - - /** - * Given a permissions object dictionary, generates a textual permissions string. - * - * @private - * @param {Object} perms - * @returns {string} - */ - _permsToStr: function(perms) { - let str = "", - type = "-"; - - if (perms.d) type = "d"; - if (perms.sl) type = "l"; - if (perms.np) type = "p"; - if (perms.s) type = "s"; - if (perms.cd) type = "c"; - if (perms.bd) type = "b"; - if (perms.dr) type = "D"; - - str = type; - - str += perms.ru ? "r" : "-"; - str += perms.wu ? "w" : "-"; - if (perms.eu && perms.su) { - str += "s"; - } else if (perms.su) { - str += "S"; - } else if (perms.eu) { - str += "x"; - } else { - str += "-"; - } - - str += perms.rg ? "r" : "-"; - str += perms.wg ? "w" : "-"; - if (perms.eg && perms.sg) { - str += "s"; - } else if (perms.sg) { - str += "S"; - } else if (perms.eg) { - str += "x"; - } else { - str += "-"; - } - - str += perms.ro ? "r" : "-"; - str += perms.wo ? "w" : "-"; - if (perms.eo && perms.sb) { - str += "t"; - } else if (perms.sb) { - str += "T"; - } else if (perms.eo) { - str += "x"; - } else { - str += "-"; - } - - return str; - }, - - - /** - * Given a permissions object dictionary, generates an octal permissions string. - * - * @private - * @param {Object} perms - * @returns {string} - */ - _permsToOctal: function(perms) { - let d = 0, - u = 0, - g = 0, - o = 0; - - if (perms.su) d += 4; - if (perms.sg) d += 2; - if (perms.sb) d += 1; - - if (perms.ru) u += 4; - if (perms.wu) u += 2; - if (perms.eu) u += 1; - - if (perms.rg) g += 4; - if (perms.wg) g += 2; - if (perms.eg) g += 1; - - if (perms.ro) o += 4; - if (perms.wo) o += 2; - if (perms.eo) o += 1; - - return d.toString() + u.toString() + g.toString() + o.toString(); - }, - - - /** - * Given a permissions object dictionary, returns the file type. - * - * @private - * @param {Object} perms - * @returns {string} - */ - _ftFromPerms: function(perms) { - if (perms.d) return "Directory"; - if (perms.sl) return "Symbolic link"; - if (perms.np) return "Named pipe"; - if (perms.s) return "Socket"; - if (perms.cd) return "Character device"; - if (perms.bd) return "Block device"; - if (perms.dr) return "Door"; - return "Regular file"; - }, - -}; - -export default OS; diff --git a/src/core/operations/ObjectIdentifierToHex.mjs b/src/core/operations/ObjectIdentifierToHex.mjs new file mode 100644 index 00000000..90e45ea5 --- /dev/null +++ b/src/core/operations/ObjectIdentifierToHex.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Object Identifier to Hex operation + */ +class ObjectIdentifierToHex extends Operation { + + /** + * ObjectIdentifierToHex constructor + */ + constructor() { + super(); + + this.name = "Object Identifier to Hex"; + this.module = "PublicKey"; + this.description = "Converts an object identifier (OID) into a hexadecimal string."; + this.infoURL = "https://wikipedia.org/wiki/Object_identifier"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.oidIntToHex(input); + } + +} + +export default ObjectIdentifierToHex; diff --git a/src/core/operations/OffsetChecker.mjs b/src/core/operations/OffsetChecker.mjs new file mode 100644 index 00000000..07b15d2f --- /dev/null +++ b/src/core/operations/OffsetChecker.mjs @@ -0,0 +1,107 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; + +/** + * Offset checker operation + */ +class OffsetChecker extends Operation { + + /** + * OffsetChecker constructor + */ + constructor() { + super(); + + this.name = "Offset checker"; + this.module = "Default"; + this.description = "Compares multiple inputs (separated by the specified delimiter) and highlights matching characters which appear at the same position in all samples."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Sample delimiter", + "type": "binaryString", + "value": "\\n\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const sampleDelim = args[0], + samples = input.split(sampleDelim), + outputs = new Array(samples.length); + let i = 0, + s = 0, + match = false, + inMatch = false, + chr; + + if (!samples || samples.length < 2) { + throw new OperationError("Not enough samples, perhaps you need to modify the sample delimiter or add more data?"); + } + + // Initialise output strings + outputs.fill("", 0, samples.length); + + // Loop through each character in the first sample + for (i = 0; i < samples[0].length; i++) { + chr = samples[0][i]; + match = false; + + // Loop through each sample to see if the chars are the same + for (s = 1; s < samples.length; s++) { + if (samples[s][i] !== chr) { + match = false; + break; + } + match = true; + } + + // Write output for each sample + for (s = 0; s < samples.length; s++) { + if (samples[s].length <= i) { + if (inMatch) outputs[s] += ""; + if (s === samples.length - 1) inMatch = false; + continue; + } + + if (match && !inMatch) { + outputs[s] += "" + Utils.escapeHtml(samples[s][i]); + if (samples[s].length === i + 1) outputs[s] += ""; + if (s === samples.length - 1) inMatch = true; + } else if (!match && inMatch) { + outputs[s] += "" + Utils.escapeHtml(samples[s][i]); + if (s === samples.length - 1) inMatch = false; + } else { + outputs[s] += Utils.escapeHtml(samples[s][i]); + if (inMatch && samples[s].length === i + 1) { + outputs[s] += ""; + if (samples[s].length - 1 !== i) inMatch = false; + } + } + + if (samples[0].length - 1 === i) { + if (inMatch) outputs[s] += ""; + outputs[s] += Utils.escapeHtml(samples[s].substring(i + 1)); + } + } + } + + return outputs.join(sampleDelim); + } + +} + +export default OffsetChecker; diff --git a/src/core/operations/PEMToHex.mjs b/src/core/operations/PEMToHex.mjs new file mode 100644 index 00000000..a2c989fe --- /dev/null +++ b/src/core/operations/PEMToHex.mjs @@ -0,0 +1,51 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * PEM to Hex operation + */ +class PEMToHex extends Operation { + + /** + * PEMToHex constructor + */ + constructor() { + super(); + + this.name = "PEM to Hex"; + this.module = "PublicKey"; + this.description = "Converts PEM (Privacy Enhanced Mail) format to a hexadecimal DER (Distinguished Encoding Rules) string."; + this.infoURL = "https://en.wikipedia.org/wiki/X.690#DER_encoding"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.indexOf("-----BEGIN") < 0) { + // Add header so that the KEYUTIL function works + input = "-----BEGIN CERTIFICATE-----" + input; + } + if (input.indexOf("-----END") < 0) { + // Add footer so that the KEYUTIL function works + input = input + "-----END CERTIFICATE-----"; + } + const cert = new r.X509(); + cert.readCertPEM(input); + return cert.hex; + } + +} + +export default PEMToHex; diff --git a/src/core/operations/PGPDecrypt.mjs b/src/core/operations/PGPDecrypt.mjs new file mode 100644 index 00000000..ccf13cd1 --- /dev/null +++ b/src/core/operations/PGPDecrypt.mjs @@ -0,0 +1,87 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Decrypt operation + */ +class PGPDecrypt extends Operation { + + /** + * PGPDecrypt constructor + */ + constructor() { + super(); + + this.name = "PGP Decrypt"; + this.module = "PGP"; + this.description = [ + "Input: the ASCII-armoured PGP message you want to decrypt.", + "

", + "Arguments: the ASCII-armoured PGP private key of the recipient, ", + "(and the private key password if necessary).", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function uses the Keybase implementation of PGP.", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Private key of recipient", + "type": "text", + "value": "" + }, + { + "name": "Private key passphrase", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid private key + */ + async run(input, args) { + const encryptedMessage = input, + [privateKey, passphrase] = args, + keyring = new kbpgp.keyring.KeyRing(); + let plaintextMessage; + + if (!privateKey) throw new OperationError("Enter the private key of the recipient."); + + const key = await importPrivateKey(privateKey, passphrase); + keyring.add_key_manager(key); + + try { + plaintextMessage = await promisify(kbpgp.unbox)({ + armored: encryptedMessage, + keyfetch: keyring, + asp: ASP + }); + } catch (err) { + throw new OperationError(`Couldn't decrypt message with provided private key: ${err}`); + } + + return plaintextMessage.toString(); + } + +} + +export default PGPDecrypt; diff --git a/src/core/operations/PGPDecryptAndVerify.mjs b/src/core/operations/PGPDecryptAndVerify.mjs new file mode 100644 index 00000000..58c61c25 --- /dev/null +++ b/src/core/operations/PGPDecryptAndVerify.mjs @@ -0,0 +1,123 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Decrypt and Verify operation + */ +class PGPDecryptAndVerify extends Operation { + + /** + * PGPDecryptAndVerify constructor + */ + constructor() { + super(); + + this.name = "PGP Decrypt and Verify"; + this.module = "PGP"; + this.description = [ + "Input: the ASCII-armoured encrypted PGP message you want to verify.", + "

", + "Arguments: the ASCII-armoured PGP public key of the signer, ", + "the ASCII-armoured private key of the recipient (and the private key password if necessary).", + "

", + "This operation uses PGP to decrypt and verify an encrypted digital signature.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function uses the Keybase implementation of PGP.", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Public key of signer", + "type": "text", + "value": "" + }, + { + "name": "Private key of recipient", + "type": "text", + "value": "" + }, + { + "name": "Private key password", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const signedMessage = input, + [publicKey, privateKey, passphrase] = args, + keyring = new kbpgp.keyring.KeyRing(); + let unboxedLiterals; + + if (!publicKey) throw new OperationError("Enter the public key of the signer."); + if (!privateKey) throw new OperationError("Enter the private key of the recipient."); + const privKey = await importPrivateKey(privateKey, passphrase); + const pubKey = await importPublicKey(publicKey); + keyring.add_key_manager(privKey); + keyring.add_key_manager(pubKey); + + try { + unboxedLiterals = await promisify(kbpgp.unbox)({ + armored: signedMessage, + keyfetch: keyring, + asp: ASP + }); + const ds = unboxedLiterals[0].get_data_signer(); + if (ds) { + const km = ds.get_key_manager(); + if (km) { + const signer = km.get_userids_mark_primary()[0].components; + let text = "Signed by "; + if (signer.email || signer.username || signer.comment) { + if (signer.username) { + text += `${signer.username} `; + } + if (signer.comment) { + text += `${signer.comment} `; + } + if (signer.email) { + text += `<${signer.email}>`; + } + text += "\n"; + } + text += [ + `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`, + `Signed on ${new Date(ds.sig.hashed_subpackets[0].time * 1000).toUTCString()}`, + "----------------------------------\n" + ].join("\n"); + text += unboxedLiterals.toString(); + return text.trim(); + } else { + throw new OperationError("Could not identify a key manager."); + } + } else { + throw new OperationError("The data does not appear to be signed."); + } + } catch (err) { + throw new OperationError(`Couldn't verify message: ${err}`); + } + } + +} + +export default PGPDecryptAndVerify; diff --git a/src/core/operations/PGPEncrypt.mjs b/src/core/operations/PGPEncrypt.mjs new file mode 100644 index 00000000..5d19de32 --- /dev/null +++ b/src/core/operations/PGPEncrypt.mjs @@ -0,0 +1,79 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPublicKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Encrypt operation + */ +class PGPEncrypt extends Operation { + + /** + * PGPEncrypt constructor + */ + constructor() { + super(); + + this.name = "PGP Encrypt"; + this.module = "PGP"; + this.description = [ + "Input: the message you want to encrypt.", + "

", + "Arguments: the ASCII-armoured PGP public key of the recipient.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function uses the Keybase implementation of PGP.", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Public key of recipient", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if failed private key import or failed encryption + */ + async run(input, args) { + const plaintextMessage = input, + plainPubKey = args[0]; + let encryptedMessage; + + if (!plainPubKey) throw new OperationError("Enter the public key of the recipient."); + + const key = await importPublicKey(plainPubKey); + + try { + encryptedMessage = await promisify(kbpgp.box)({ + "msg": plaintextMessage, + "encrypt_for": key, + "asp": ASP + }); + } catch (err) { + throw new OperationError(`Couldn't encrypt message with provided public key: ${err}`); + } + + return encryptedMessage.toString(); + } + +} + +export default PGPEncrypt; diff --git a/src/core/operations/PGPEncryptAndSign.mjs b/src/core/operations/PGPEncryptAndSign.mjs new file mode 100644 index 00000000..d9d19ce3 --- /dev/null +++ b/src/core/operations/PGPEncryptAndSign.mjs @@ -0,0 +1,94 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Encrypt and Sign operation + */ +class PGPEncryptAndSign extends Operation { + + /** + * PGPEncryptAndSign constructor + */ + constructor() { + super(); + + this.name = "PGP Encrypt and Sign"; + this.module = "PGP"; + this.description = [ + "Input: the cleartext you want to sign.", + "

", + "Arguments: the ASCII-armoured private key of the signer (plus the private key password if necessary)", + "and the ASCII-armoured PGP public key of the recipient.", + "

", + "This operation uses PGP to produce an encrypted digital signature.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function uses the Keybase implementation of PGP.", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Private key of signer", + "type": "text", + "value": "" + }, + { + "name": "Private key passphrase", + "type": "string", + "value": "" + }, + { + "name": "Public key of recipient", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if failure to sign message + */ + async run(input, args) { + const message = input, + [privateKey, passphrase, publicKey] = args; + let signedMessage; + + if (!privateKey) throw new OperationError("Enter the private key of the signer."); + if (!publicKey) throw new OperationError("Enter the public key of the recipient."); + const privKey = await importPrivateKey(privateKey, passphrase); + const pubKey = await importPublicKey(publicKey); + + try { + signedMessage = await promisify(kbpgp.box)({ + "msg": message, + "encrypt_for": pubKey, + "sign_with": privKey, + "asp": ASP + }); + } catch (err) { + throw new OperationError(`Couldn't sign message: ${err}`); + } + + return signedMessage; + } + +} + +export default PGPEncryptAndSign; diff --git a/src/core/operations/PHPDeserialize.mjs b/src/core/operations/PHPDeserialize.mjs new file mode 100644 index 00000000..760e4e05 --- /dev/null +++ b/src/core/operations/PHPDeserialize.mjs @@ -0,0 +1,172 @@ +/** + * @author Jarmo van Lenthe [github.com/jarmovanlenthe] + * @copyright Jarmo van Lenthe + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * PHP Deserialize operation + */ +class PHPDeserialize extends Operation { + + /** + * PHPDeserialize constructor + */ + constructor() { + super(); + + this.name = "PHP Deserialize"; + this.module = "Default"; + this.description = "Deserializes PHP serialized data, outputting keyed arrays as JSON.

This function does not support object tags.

Example:
a:2:{s:1:"a";i:10;i:0;a:1:{s:2:"ab";b:1;}}
becomes
{"a": 10,0: {"ab": true}}

Output valid JSON: JSON doesn't support integers as keys, whereas PHP serialization does. Enabling this will cast these integers to strings. This will also escape backslashes."; + this.infoURL = "http://www.phpinternalsbook.com/classes_objects/serialization.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Output valid JSON", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + /** + * Recursive method for deserializing. + * @returns {*} + */ + function handleInput() { + /** + * Read `length` characters from the input, shifting them out the input. + * @param length + * @returns {string} + */ + function read(length) { + let result = ""; + for (let idx = 0; idx < length; idx++) { + const char = inputPart.shift(); + if (char === undefined) { + throw new OperationError("End of input reached before end of script"); + } + result += char; + } + return result; + } + + /** + * Read characters from the input until `until` is found. + * @param until + * @returns {string} + */ + function readUntil(until) { + let result = ""; + for (;;) { + const char = read(1); + if (char === until) { + break; + } else { + result += char; + } + } + return result; + + } + + /** + * Read characters from the input that must be equal to `expect` + * @param expect + * @returns {string} + */ + function expect(expect) { + const result = read(expect.length); + if (result !== expect) { + throw new OperationError("Unexpected input found"); + } + return result; + } + + /** + * Helper function to handle deserialized arrays. + * @returns {Array} + */ + function handleArray() { + const items = parseInt(readUntil(":"), 10) * 2; + expect("{"); + const result = []; + let isKey = true; + let lastItem = null; + for (let idx = 0; idx < items; idx++) { + const item = handleInput(); + if (isKey) { + lastItem = item; + isKey = false; + } else { + const numberCheck = lastItem.match(/[0-9]+/); + if (args[0] && numberCheck && numberCheck[0].length === lastItem.length) { + result.push("\"" + lastItem + "\": " + item); + } else { + result.push(lastItem + ": " + item); + } + isKey = true; + } + } + expect("}"); + return result; + } + + + const kind = read(1).toLowerCase(); + + switch (kind) { + case "n": + expect(";"); + return ""; + + case "i": + case "d": + case "b": { + expect(":"); + const data = readUntil(";"); + if (kind === "b") { + return (parseInt(data, 10) !== 0); + } + return data; + } + + case "a": + expect(":"); + return "{" + handleArray() + "}"; + + case "s": { + expect(":"); + const length = readUntil(":"); + expect("\""); + const value = read(length); + expect("\";"); + if (args[0]) { + return "\"" + value.replace(/"/g, "\\\"") + "\""; + } else { + return "\"" + value + "\""; + } + } + + default: + throw new OperationError("Unknown type: " + kind); + } + } + + const inputPart = input.split(""); + return handleInput(); + } + +} + +export default PHPDeserialize; diff --git a/src/core/operations/PadLines.mjs b/src/core/operations/PadLines.mjs new file mode 100644 index 00000000..e9e2b45a --- /dev/null +++ b/src/core/operations/PadLines.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Pad lines operation + */ +class PadLines extends Operation { + + /** + * PadLines constructor + */ + constructor() { + super(); + + this.name = "Pad lines"; + this.module = "Default"; + this.description = "Add the specified number of the specified character to the beginning or end of each line"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Position", + "type": "option", + "value": ["Start", "End"] + }, + { + "name": "Length", + "type": "number", + "value": 5 + }, + { + "name": "Character", + "type": "binaryShortString", + "value": " " + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [position, len, chr] = args, + lines = input.split("\n"); + let output = "", + i = 0; + + if (position === "Start") { + for (i = 0; i < lines.length; i++) { + output += lines[i].padStart(lines[i].length+len, chr) + "\n"; + } + } else if (position === "End") { + for (i = 0; i < lines.length; i++) { + output += lines[i].padEnd(lines[i].length+len, chr) + "\n"; + } + } + + return output.slice(0, output.length-1); + } + +} + +export default PadLines; diff --git a/src/core/operations/ParseASN1HexString.mjs b/src/core/operations/ParseASN1HexString.mjs new file mode 100644 index 00000000..92c070a4 --- /dev/null +++ b/src/core/operations/ParseASN1HexString.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Parse ASN.1 hex string operation + */ +class ParseASN1HexString extends Operation { + + /** + * ParseASN1HexString constructor + */ + constructor() { + super(); + + this.name = "Parse ASN.1 hex string"; + this.module = "PublicKey"; + this.description = "Abstract Syntax Notation One (ASN.1) is a standard and notation that describes rules and structures for representing, encoding, transmitting, and decoding data in telecommunications and computer networking.

This operation parses arbitrary ASN.1 data and presents the resulting tree."; + this.infoURL = "https://wikipedia.org/wiki/Abstract_Syntax_Notation_One"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Starting index", + "type": "number", + "value": 0 + }, + { + "name": "Truncate octet strings longer than", + "type": "number", + "value": 32 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [index, truncateLen] = args; + return r.ASN1HEX.dump(input.replace(/\s/g, ""), { + "ommitLongOctet": truncateLen + }, index); + } + +} + +export default ParseASN1HexString; diff --git a/src/core/operations/ParseColourCode.mjs b/src/core/operations/ParseColourCode.mjs new file mode 100644 index 00000000..c97f9b89 --- /dev/null +++ b/src/core/operations/ParseColourCode.mjs @@ -0,0 +1,196 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Parse colour code operation + */ +class ParseColourCode extends Operation { + + /** + * ParseColourCode constructor + */ + constructor() { + super(); + + this.name = "Parse colour code"; + this.module = "Default"; + this.description = "Converts a colour code in a standard format to other standard formats and displays the colour itself.

Example inputs
  • #d9edf7
  • rgba(217,237,247,1)
  • hsla(200,65%,91%,1)
  • cmyk(0.12, 0.04, 0.00, 0.03)
"; + this.infoURL = "https://wikipedia.org/wiki/Web_colors"; + this.inputType = "string"; + this.outputType = "html"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + let m = null, + r = 0, g = 0, b = 0, a = 1; + + // Read in the input + if ((m = input.match(/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i))) { + // Hex - #d9edf7 + r = parseInt(m[1], 16); + g = parseInt(m[2], 16); + b = parseInt(m[3], 16); + } else if ((m = input.match(/rgba?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)(?:,\s?(\d(?:\.\d+)?))?\)/i))) { + // RGB or RGBA - rgb(217,237,247) or rgba(217,237,247,1) + r = parseFloat(m[1]); + g = parseFloat(m[2]); + b = parseFloat(m[3]); + a = m[4] ? parseFloat(m[4]) : 1; + } else if ((m = input.match(/hsla?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)%,\s?(\d{1,3}(?:\.\d+)?)%(?:,\s?(\d(?:\.\d+)?))?\)/i))) { + // HSL or HSLA - hsl(200, 65%, 91%) or hsla(200, 65%, 91%, 1) + const h_ = parseFloat(m[1]) / 360, + s_ = parseFloat(m[2]) / 100, + l_ = parseFloat(m[3]) / 100, + rgb_ = ParseColourCode._hslToRgb(h_, s_, l_); + + r = rgb_[0]; + g = rgb_[1]; + b = rgb_[2]; + a = m[4] ? parseFloat(m[4]) : 1; + } else if ((m = input.match(/cmyk\((\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?)\)/i))) { + // CMYK - cmyk(0.12, 0.04, 0.00, 0.03) + const c_ = parseFloat(m[1]), + m_ = parseFloat(m[2]), + y_ = parseFloat(m[3]), + k_ = parseFloat(m[4]); + + r = Math.round(255 * (1 - c_) * (1 - k_)); + g = Math.round(255 * (1 - m_) * (1 - k_)); + b = Math.round(255 * (1 - y_) * (1 - k_)); + } + + const hsl_ = ParseColourCode._rgbToHsl(r, g, b), + h = Math.round(hsl_[0] * 360), + s = Math.round(hsl_[1] * 100), + l = Math.round(hsl_[2] * 100); + let k = 1 - Math.max(r/255, g/255, b/255), + c = (1 - r/255 - k) / (1 - k), + y = (1 - b/255 - k) / (1 - k); + + m = (1 - g/255 - k) / (1 - k); + + c = isNaN(c) ? "0" : c.toFixed(2); + m = isNaN(m) ? "0" : m.toFixed(2); + y = isNaN(y) ? "0" : y.toFixed(2); + k = k.toFixed(2); + + const hex = "#" + + Math.round(r).toString(16).padStart(2, "0") + + Math.round(g).toString(16).padStart(2, "0") + + Math.round(b).toString(16).padStart(2, "0"), + rgb = "rgb(" + r + ", " + g + ", " + b + ")", + rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")", + hsl = "hsl(" + h + ", " + s + "%, " + l + "%)", + hsla = "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")", + cmyk = "cmyk(" + c + ", " + m + ", " + y + ", " + k + ")"; + + // Generate output + return `
+Hex: ${hex} +RGB: ${rgb} +RGBA: ${rgba} +HSL: ${hsl} +HSLA: ${hsla} +CMYK: ${cmyk} +`; + } + + /** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @author Mohsen (http://stackoverflow.com/a/9493060) + * + * @param {number} h - The hue + * @param {number} s - The saturation + * @param {number} l - The lightness + * @return {Array} The RGB representation + */ + static _hslToRgb(h, s, l) { + let r, g, b; + + if (s === 0){ + r = g = b = l; // achromatic + } else { + const hue2rgb = function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1/6) return p + (q - p) * 6 * t; + if (t < 1/2) return q; + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + }; + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + } + + /** + * Converts an RGB color value to HSL. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. + * Assumes r, g, and b are contained in the set [0, 255] and + * returns h, s, and l in the set [0, 1]. + * + * @author Mohsen (http://stackoverflow.com/a/9493060) + * + * @param {number} r - The red color value + * @param {number} g - The green color value + * @param {number} b - The blue color value + * @return {Array} The HSL representation + */ + static _rgbToHsl(r, g, b) { + r /= 255; g /= 255; b /= 255; + const max = Math.max(r, g, b), + min = Math.min(r, g, b), + l = (max + min) / 2; + let h, s; + + if (max === min) { + h = s = 0; // achromatic + } else { + const d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + + return [h, s, l]; + } +} + +export default ParseColourCode; diff --git a/src/core/operations/ParseDateTime.mjs b/src/core/operations/ParseDateTime.mjs new file mode 100644 index 00000000..82af1c32 --- /dev/null +++ b/src/core/operations/ParseDateTime.mjs @@ -0,0 +1,83 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import moment from "moment-timezone"; +import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime"; + +/** + * Parse DateTime operation + */ +class ParseDateTime extends Operation { + + /** + * ParseDateTime constructor + */ + constructor() { + super(); + + this.name = "Parse DateTime"; + this.module = "Default"; + this.description = "Parses a DateTime string in your specified format and displays it in whichever timezone you choose with the following information:
  • Date
  • Time
  • Period (AM/PM)
  • Timezone
  • UTC offset
  • Daylight Saving Time
  • Leap year
  • Days in this month
  • Day of year
  • Week number
  • Quarter
Run with no input to see format string examples if required."; + this.infoURL = "https://momentjs.com/docs/#/parsing/string-format/"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Built in formats", + "type": "populateOption", + "value": DATETIME_FORMATS, + "target": 1 + }, + { + "name": "Input format string", + "type": "binaryString", + "value": "DD/MM/YYYY HH:mm:ss" + }, + { + "name": "Input timezone", + "type": "option", + "value": ["UTC"].concat(moment.tz.names()) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const inputFormat = args[1], + inputTimezone = args[2]; + let date, + output = ""; + + try { + date = moment.tz(input, inputFormat, inputTimezone); + if (!date || date.format() === "Invalid date") throw Error; + } catch (err) { + return `Invalid format.\n\n${FORMAT_EXAMPLES}`; + } + + output += "Date: " + date.format("dddd Do MMMM YYYY") + + "\nTime: " + date.format("HH:mm:ss") + + "\nPeriod: " + date.format("A") + + "\nTimezone: " + date.format("z") + + "\nUTC offset: " + date.format("ZZ") + + "\n\nDaylight Saving Time: " + date.isDST() + + "\nLeap year: " + date.isLeapYear() + + "\nDays in this month: " + date.daysInMonth() + + "\n\nDay of year: " + date.dayOfYear() + + "\nWeek number: " + date.weekYear() + + "\nQuarter: " + date.quarter(); + + return output; + } + +} + +export default ParseDateTime; diff --git a/src/core/operations/ParseIPRange.mjs b/src/core/operations/ParseIPRange.mjs new file mode 100644 index 00000000..38bcc222 --- /dev/null +++ b/src/core/operations/ParseIPRange.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author Klaxon [klaxon@veyr.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import {ipv4CidrRange, ipv4HyphenatedRange, ipv4ListedRange, ipv6CidrRange, ipv6HyphenatedRange, ipv6ListedRange} from "../lib/IP"; + +/** + * Parse IP range operation + */ +class ParseIPRange extends Operation { + + /** + * ParseIPRange constructor + */ + constructor() { + super(); + + this.name = "Parse IP range"; + this.module = "Default"; + this.description = "Given a CIDR range (e.g. 10.0.0.0/24), hyphenated range (e.g. 10.0.0.0 - 10.0.1.0), or a list of IPs and/or CIDR ranges (separated by a new line), this operation provides network information and enumerates all IP addresses in the range.

IPv6 is supported but will not be enumerated."; + this.infoURL = "https://wikipedia.org/wiki/Subnetwork"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Include network info", + "type": "boolean", + "value": true + }, + { + "name": "Enumerate IP addresses", + "type": "boolean", + "value": true + }, + { + "name": "Allow large queries", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [ + includeNetworkInfo, + enumerateAddresses, + allowLargeList + ] = args; + + // Check what type of input we are looking at + const ipv4CidrRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/(\d\d?)\s*$/, + ipv4RangeRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*-\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/, + ipv4ListRegex = /^\s*(((?:\d{1,3}\.){3}\d{1,3})(\/(\d\d?))?(\n|$)(\n*))+\s*$/, + ipv6CidrRegex = /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\/(\d\d?\d?)\s*$/i, + ipv6RangeRegex = /^\s*(((?=.*::)(?!.*::[^-]+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*-\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\17)::|:\b|(?![\dA-F])))|(?!\16\17)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i, + ipv6ListRegex = /^\s*((((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))(\/(\d\d?\d?))?(\n|$)(\n*))+\s*$/i; + let match; + + if ((match = ipv4CidrRegex.exec(input))) { + return ipv4CidrRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); + } else if ((match = ipv4RangeRegex.exec(input))) { + return ipv4HyphenatedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); + } else if ((match = ipv4ListRegex.exec(input))) { + return ipv4ListedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); + } else if ((match = ipv6CidrRegex.exec(input))) { + return ipv6CidrRange(match, includeNetworkInfo); + } else if ((match = ipv6RangeRegex.exec(input))) { + return ipv6HyphenatedRange(match, includeNetworkInfo); + } else if ((match = ipv6ListRegex.exec(input))) { + return ipv6ListedRange(match, includeNetworkInfo); + } else { + throw new OperationError("Invalid input.\n\nEnter either a CIDR range (e.g. 10.0.0.0/24) or a hyphenated range (e.g. 10.0.0.0 - 10.0.1.0). IPv6 also supported."); + } + } + +} + + +export default ParseIPRange; diff --git a/src/core/operations/ParseIPv4Header.mjs b/src/core/operations/ParseIPv4Header.mjs new file mode 100644 index 00000000..06114329 --- /dev/null +++ b/src/core/operations/ParseIPv4Header.mjs @@ -0,0 +1,130 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {fromHex, toHex} from "../lib/Hex"; +import {ipv4ToStr, protocolLookup} from "../lib/IP"; +import TCPIPChecksum from "./TCPIPChecksum"; + +/** + * Parse IPv4 header operation + */ +class ParseIPv4Header extends Operation { + + /** + * ParseIPv4Header constructor + */ + constructor() { + super(); + + this.name = "Parse IPv4 header"; + this.module = "Default"; + this.description = "Given an IPv4 header, this operations parses and displays each field in an easily readable format."; + this.infoURL = "https://wikipedia.org/wiki/IPv4#Header"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const format = args[0]; + let output; + + if (format === "Hex") { + input = fromHex(input); + } else if (format === "Raw") { + input = Utils.strToByteArray(input); + } else { + throw new OperationError("Unrecognised input format."); + } + + let ihl = input[0] & 0x0f; + const dscp = (input[1] >>> 2) & 0x3f, + ecn = input[1] & 0x03, + length = input[2] << 8 | input[3], + identification = input[4] << 8 | input[5], + flags = (input[6] >>> 5) & 0x07, + fragOffset = (input[6] & 0x1f) << 8 | input[7], + ttl = input[8], + protocol = input[9], + checksum = input[10] << 8 | input[11], + srcIP = input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15], + dstIP = input[16] << 24 | input[17] << 16 | input[18] << 8 | input[19], + checksumHeader = input.slice(0, 10).concat([0, 0]).concat(input.slice(12, 20)); + let version = (input[0] >>> 4) & 0x0f, + options = []; + + + // Version + if (version !== 4) { + version = version + " (Error: for IPv4 headers, this should always be set to 4)"; + } + + // IHL + if (ihl < 5) { + ihl = ihl + " (Error: this should always be at least 5)"; + } else if (ihl > 5) { + // sort out options... + const optionsLen = ihl * 4 - 20; + options = input.slice(20, optionsLen + 20); + } + + // Protocol + const protocolInfo = protocolLookup[protocol] || {keyword: "", protocol: ""}; + + // Checksum + const correctChecksum = (new TCPIPChecksum).run(checksumHeader), + givenChecksum = Utils.hex(checksum); + let checksumResult; + if (correctChecksum === givenChecksum) { + checksumResult = givenChecksum + " (correct)"; + } else { + checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")"; + } + + output = ` + + + + + + + + + + + + +`; + + if (ihl > 5) { + output += ``; + } + + return output + "
FieldValue
Version${version}
Internet Header Length (IHL)${ihl} (${ihl * 4} bytes)
Differentiated Services Code Point (DSCP)${dscp}
Explicit Congestion Notification (ECN)${ecn}
Total length${length} bytes + IP header: ${ihl * 4} bytes + Data: ${length - ihl * 4} bytes
Identification0x${Utils.hex(identification)} (${identification})
Flags0x${Utils.hex(flags, 2)} + Reserved bit:${flags >> 2} (must be 0) + Don't fragment:${flags >> 1 & 1} + More fragments:${flags & 1}
Fragment offset${fragOffset}
Time-To-Live${ttl}
Protocol${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})
Header checksum${checksumResult}
Source IP address${ipv4ToStr(srcIP)}
Destination IP address${ipv4ToStr(dstIP)}
Options${toHex(options)}
"; + } + +} + +export default ParseIPv4Header; diff --git a/src/core/operations/ParseIPv6Address.mjs b/src/core/operations/ParseIPv6Address.mjs new file mode 100644 index 00000000..3cf9f2dd --- /dev/null +++ b/src/core/operations/ParseIPv6Address.mjs @@ -0,0 +1,193 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {strToIpv6, ipv6ToStr, ipv4ToStr, IPV6_REGEX} from "../lib/IP"; +import BigNumber from "bignumber.js"; + +/** + * Parse IPv6 address operation + */ +class ParseIPv6Address extends Operation { + + /** + * ParseIPv6Address constructor + */ + constructor() { + super(); + + this.name = "Parse IPv6 address"; + this.module = "Default"; + this.description = "Displays the longhand and shorthand versions of a valid IPv6 address.

Recognises all reserved ranges and parses encapsulated or tunnelled addresses including Teredo and 6to4."; + this.infoURL = "https://wikipedia.org/wiki/IPv6_address"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let match, + output = ""; + + if ((match = IPV6_REGEX.exec(input))) { + const ipv6 = strToIpv6(match[1]), + longhand = ipv6ToStr(ipv6), + shorthand = ipv6ToStr(ipv6, true); + + output += "Longhand: " + longhand + "\nShorthand: " + shorthand + "\n"; + + // Detect reserved addresses + if (shorthand === "::") { + // Unspecified address + output += "\nUnspecified address corresponding to 0.0.0.0/32 in IPv4."; + output += "\nUnspecified address range: ::/128"; + } else if (shorthand === "::1") { + // Loopback address + output += "\nLoopback address to the local host corresponding to 127.0.0.1/8 in IPv4."; + output += "\nLoopback addresses range: ::1/128"; + } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && + ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0xffff) { + // IPv4-mapped IPv6 address + output += "\nIPv4-mapped IPv6 address detected. IPv6 clients will be handled natively by default, and IPv4 clients appear as IPv6 clients at their IPv4-mapped IPv6 address."; + output += "\nMapped IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); + output += "\nIPv4-mapped IPv6 addresses range: ::ffff:0:0/96"; + } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && + ipv6[3] === 0 && ipv6[4] === 0xffff && ipv6[5] === 0) { + // IPv4-translated address + output += "\nIPv4-translated address detected. Used by Stateless IP/ICMP Translation (SIIT). See RFCs 6145 and 6052 for more details."; + output += "\nTranslated IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); + output += "\nIPv4-translated addresses range: ::ffff:0:0:0/96"; + } else if (ipv6[0] === 0x100) { + // Discard prefix per RFC 6666 + output += "\nDiscard prefix detected. This is used when forwarding traffic to a sinkhole router to mitigate the effects of a denial-of-service attack. See RFC 6666 for more details."; + output += "\nDiscard range: 100::/64"; + } else if (ipv6[0] === 0x64 && ipv6[1] === 0xff9b && ipv6[2] === 0 && + ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0) { + // IPv4/IPv6 translation per RFC 6052 + output += "\n'Well-Known' prefix for IPv4/IPv6 translation detected. See RFC 6052 for more details."; + output += "\nTranslated IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); + output += "\n'Well-Known' prefix range: 64:ff9b::/96"; + } else if (ipv6[0] === 0x2001 && ipv6[1] === 0) { + // Teredo tunneling + output += "\nTeredo tunneling IPv6 address detected\n"; + const serverIpv4 = (ipv6[2] << 16) + ipv6[3], + udpPort = (~ipv6[5]) & 0xffff, + clientIpv4 = ~((ipv6[6] << 16) + ipv6[7]), + flagCone = (ipv6[4] >>> 15) & 1, + flagR = (ipv6[4] >>> 14) & 1, + flagRandom1 = (ipv6[4] >>> 10) & 15, + flagUg = (ipv6[4] >>> 8) & 3, + flagRandom2 = ipv6[4] & 255; + + output += "\nServer IPv4 address: " + ipv4ToStr(serverIpv4) + + "\nClient IPv4 address: " + ipv4ToStr(clientIpv4) + + "\nClient UDP port: " + udpPort + + "\nFlags:" + + "\n\tCone: " + flagCone; + + if (flagCone) { + output += " (Client is behind a cone NAT)"; + } else { + output += " (Client is not behind a cone NAT)"; + } + + output += "\n\tR: " + flagR; + + if (flagR) { + output += " Error: This flag should be set to 0. See RFC 5991 and RFC 4380."; + } + + output += "\n\tRandom1: " + Utils.bin(flagRandom1, 4) + + "\n\tUG: " + Utils.bin(flagUg, 2); + + if (flagUg) { + output += " Error: This flag should be set to 00. See RFC 4380."; + } + + output += "\n\tRandom2: " + Utils.bin(flagRandom2, 8); + + if (!flagR && !flagUg && flagRandom1 && flagRandom2) { + output += "\n\nThis is a valid Teredo address which complies with RFC 4380 and RFC 5991."; + } else if (!flagR && !flagUg) { + output += "\n\nThis is a valid Teredo address which complies with RFC 4380, however it does not comply with RFC 5991 (Teredo Security Updates) as there are no randomised bits in the flag field."; + } else { + output += "\n\nThis is an invalid Teredo address."; + } + output += "\n\nTeredo prefix range: 2001::/32"; + } else if (ipv6[0] === 0x2001 && ipv6[1] === 0x2 && ipv6[2] === 0) { + // Benchmarking + output += "\nAssigned to the Benchmarking Methodology Working Group (BMWG) for benchmarking IPv6. Corresponds to 198.18.0.0/15 for benchmarking IPv4. See RFC 5180 for more details."; + output += "\nBMWG range: 2001:2::/48"; + } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x10 && ipv6[1] <= 0x1f) { + // ORCHIDv1 + output += "\nDeprecated, previously ORCHIDv1 (Overlay Routable Cryptographic Hash Identifiers).\nORCHIDv1 range: 2001:10::/28\nORCHIDv2 now uses 2001:20::/28."; + } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x20 && ipv6[1] <= 0x2f) { + // ORCHIDv2 + output += "\nORCHIDv2 (Overlay Routable Cryptographic Hash Identifiers).\nThese are non-routed IPv6 addresses used for Cryptographic Hash Identifiers."; + output += "\nORCHIDv2 range: 2001:20::/28"; + } else if (ipv6[0] === 0x2001 && ipv6[1] === 0xdb8) { + // Documentation + output += "\nThis is a documentation IPv6 address. This range should be used whenever an example IPv6 address is given or to model networking scenarios. Corresponds to 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24 in IPv4."; + output += "\nDocumentation range: 2001:db8::/32"; + } else if (ipv6[0] === 0x2002) { + // 6to4 + output += "\n6to4 transition IPv6 address detected. See RFC 3056 for more details." + + "\n6to4 prefix range: 2002::/16"; + + const v4Addr = ipv4ToStr((ipv6[1] << 16) + ipv6[2]), + slaId = ipv6[3], + interfaceIdStr = ipv6[4].toString(16) + ipv6[5].toString(16) + ipv6[6].toString(16) + ipv6[7].toString(16), + interfaceId = new BigNumber(interfaceIdStr, 16); + + output += "\n\nEncapsulated IPv4 address: " + v4Addr + + "\nSLA ID: " + slaId + + "\nInterface ID (base 16): " + interfaceIdStr + + "\nInterface ID (base 10): " + interfaceId.toString(); + } else if (ipv6[0] >= 0xfc00 && ipv6[0] <= 0xfdff) { + // Unique local address + output += "\nThis is a unique local address comparable to the IPv4 private addresses 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. See RFC 4193 for more details."; + output += "\nUnique local addresses range: fc00::/7"; + } else if (ipv6[0] >= 0xfe80 && ipv6[0] <= 0xfebf) { + // Link-local address + output += "\nThis is a link-local address comparable to the auto-configuration addresses 169.254.0.0/16 in IPv4."; + output += "\nLink-local addresses range: fe80::/10"; + } else if (ipv6[0] >= 0xff00) { + // Multicast + output += "\nThis is a reserved multicast address."; + output += "\nMulticast addresses range: ff00::/8"; + } + + + // Detect possible EUI-64 addresses + if ((ipv6[5] & 0xff === 0xff) && (ipv6[6] >>> 8 === 0xfe)) { + output += "\n\nThis IPv6 address contains a modified EUI-64 address, identified by the presence of FF:FE in the 12th and 13th octets."; + + const intIdent = Utils.hex(ipv6[4] >>> 8) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + + Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[5] & 0xff) + ":" + + Utils.hex(ipv6[6] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + + Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff), + mac = Utils.hex((ipv6[4] >>> 8) ^ 2) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + + Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + + Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff); + output += "\nInterface identifier: " + intIdent + + "\nMAC address: " + mac; + } + } else { + throw new OperationError("Invalid IPv6 address"); + } + return output; + } + +} + +export default ParseIPv6Address; diff --git a/src/core/operations/ParseQRCode.mjs b/src/core/operations/ParseQRCode.mjs new file mode 100644 index 00000000..75a24d55 --- /dev/null +++ b/src/core/operations/ParseQRCode.mjs @@ -0,0 +1,107 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Magic from "../lib/Magic"; +import jsqr from "jsqr"; +import jimp from "jimp"; + +/** + * Parse QR Code operation + */ +class ParseQRCode extends Operation { + + /** + * ParseQRCode constructor + */ + constructor() { + super(); + + this.name = "Parse QR Code"; + this.module = "Image"; + this.description = "Reads an image file and attempts to detect and read a Quick Response (QR) code from the image.

Normalise Image
Attempts to normalise the image before parsing it to improve detection of a QR code."; + this.infoURL = "https://wikipedia.org/wiki/QR_code"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Normalise image", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const type = Magic.magicFileType(input); + const [normalise] = args; + + // Make sure that the input is an image + if (type && type.mime.indexOf("image") === 0) { + let image = input; + + if (normalise) { + // Process the image to be easier to read by jsqr + // Disables the alpha channel + // Sets the image default background to white + // Normalises the image colours + // Makes the image greyscale + // Converts image to a JPEG + image = await new Promise((resolve, reject) => { + jimp.read(Buffer.from(input)) + .then(image => { + image + .rgba(false) + .background(0xFFFFFFFF) + .normalize() + .greyscale() + .getBuffer(jimp.MIME_JPEG, (error, result) => { + resolve(result); + }); + }) + .catch(err => { + reject(new OperationError("Error reading the image file.")); + }); + }); + } + + if (image instanceof OperationError) { + throw image; + } + + return new Promise((resolve, reject) => { + jimp.read(Buffer.from(image)) + .then(image => { + if (image.bitmap != null) { + const qrData = jsqr(image.bitmap.data, image.getWidth(), image.getHeight()); + if (qrData != null) { + resolve(qrData.data); + } else { + reject(new OperationError("Couldn't read a QR code from the image.")); + } + } else { + reject(new OperationError("Error reading the image file.")); + } + }) + .catch(err => { + reject(new OperationError("Error reading the image file.")); + }); + }); + } else { + throw new OperationError("Invalid file type."); + } + + } + +} + +export default ParseQRCode; diff --git a/src/core/operations/ParseTLV.mjs b/src/core/operations/ParseTLV.mjs new file mode 100644 index 00000000..a87144a8 --- /dev/null +++ b/src/core/operations/ParseTLV.mjs @@ -0,0 +1,76 @@ +/** + * @author gchq77703 [] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import TLVParser from "../lib/TLVParser"; +import OperationError from "../errors/OperationError"; + +/** + * Parse TLV operation + */ +class ParseTLV extends Operation { + + /** + * ParseTLV constructor + */ + constructor() { + super(); + + this.name = "Parse TLV"; + this.module = "Default"; + this.description = "Converts a Type-Length-Value (TLV) encoded string into a JSON object. Can optionally include a Key / Type entry.

Tags: Key-Length-Value, KLV, Length-Value, LV"; + this.infoURL = "https://wikipedia.org/wiki/Type-length-value"; + this.inputType = "byteArray"; + this.outputType = "JSON"; + this.args = [ + { + name: "Type/Key size", + type: "number", + value: 1 + }, + { + name: "Length size", + type: "number", + value: 1 + }, + { + name: "Use BER", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [bytesInKey, bytesInLength, basicEncodingRules] = args; + + if (bytesInKey <= 0 && bytesInLength <= 0) + throw new OperationError("Type or Length size must be greater than 0"); + + const tlv = new TLVParser(input, { bytesInLength, basicEncodingRules }); + + const data = []; + + while (!tlv.atEnd()) { + const key = bytesInKey ? tlv.getValue(bytesInKey) : undefined; + const length = tlv.getLength(); + const value = tlv.getValue(length); + + data.push({ key, length, value }); + } + + return data; + } + +} + +export default ParseTLV; diff --git a/src/core/operations/ParseUNIXFilePermissions.mjs b/src/core/operations/ParseUNIXFilePermissions.mjs new file mode 100644 index 00000000..8a0eac7a --- /dev/null +++ b/src/core/operations/ParseUNIXFilePermissions.mjs @@ -0,0 +1,325 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Parse UNIX file permissions operation + */ +class ParseUNIXFilePermissions extends Operation { + + /** + * ParseUNIXFilePermissions constructor + */ + constructor() { + super(); + + this.name = "Parse UNIX file permissions"; + this.module = "Default"; + this.description = "Given a UNIX/Linux file permission string in octal or textual format, this operation explains which permissions are granted to which user groups.

Input should be in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format."; + this.infoURL = "https://wikipedia.org/wiki/File_system_permissions#Traditional_Unix_permissions"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const perms = { + d: false, // directory + sl: false, // symbolic link + np: false, // named pipe + s: false, // socket + cd: false, // character device + bd: false, // block device + dr: false, // door + sb: false, // sticky bit + su: false, // setuid + sg: false, // setgid + ru: false, // read user + wu: false, // write user + eu: false, // execute user + rg: false, // read group + wg: false, // write group + eg: false, // execute group + ro: false, // read other + wo: false, // write other + eo: false // execute other + }; + let d = 0, + u = 0, + g = 0, + o = 0, + output = "", + octal = null, + textual = null; + + if (input.search(/\s*[0-7]{1,4}\s*/i) === 0) { + // Input is octal + octal = input.match(/\s*([0-7]{1,4})\s*/i)[1]; + + if (octal.length === 4) { + d = parseInt(octal[0], 8); + u = parseInt(octal[1], 8); + g = parseInt(octal[2], 8); + o = parseInt(octal[3], 8); + } else { + if (octal.length > 0) u = parseInt(octal[0], 8); + if (octal.length > 1) g = parseInt(octal[1], 8); + if (octal.length > 2) o = parseInt(octal[2], 8); + } + + perms.su = d >> 2 & 0x1; + perms.sg = d >> 1 & 0x1; + perms.sb = d & 0x1; + + perms.ru = u >> 2 & 0x1; + perms.wu = u >> 1 & 0x1; + perms.eu = u & 0x1; + + perms.rg = g >> 2 & 0x1; + perms.wg = g >> 1 & 0x1; + perms.eg = g & 0x1; + + perms.ro = o >> 2 & 0x1; + perms.wo = o >> 1 & 0x1; + perms.eo = o & 0x1; + } else if (input.search(/\s*[dlpcbDrwxsStT-]{1,10}\s*/) === 0) { + // Input is textual + textual = input.match(/\s*([dlpcbDrwxsStT-]{1,10})\s*/)[1]; + + switch (textual[0]) { + case "d": + perms.d = true; + break; + case "l": + perms.sl = true; + break; + case "p": + perms.np = true; + break; + case "s": + perms.s = true; + break; + case "c": + perms.cd = true; + break; + case "b": + perms.bd = true; + break; + case "D": + perms.dr = true; + break; + } + + if (textual.length > 1) perms.ru = textual[1] === "r"; + if (textual.length > 2) perms.wu = textual[2] === "w"; + if (textual.length > 3) { + switch (textual[3]) { + case "x": + perms.eu = true; + break; + case "s": + perms.eu = true; + perms.su = true; + break; + case "S": + perms.su = true; + break; + } + } + + if (textual.length > 4) perms.rg = textual[4] === "r"; + if (textual.length > 5) perms.wg = textual[5] === "w"; + if (textual.length > 6) { + switch (textual[6]) { + case "x": + perms.eg = true; + break; + case "s": + perms.eg = true; + perms.sg = true; + break; + case "S": + perms.sg = true; + break; + } + } + + if (textual.length > 7) perms.ro = textual[7] === "r"; + if (textual.length > 8) perms.wo = textual[8] === "w"; + if (textual.length > 9) { + switch (textual[9]) { + case "x": + perms.eo = true; + break; + case "t": + perms.eo = true; + perms.sb = true; + break; + case "T": + perms.sb = true; + break; + } + } + } else { + throw new OperationError("Invalid input format.\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format."); + } + + output += "Textual representation: " + permsToStr(perms); + output += "\nOctal representation: " + permsToOctal(perms); + + // File type + if (textual) { + output += "\nFile type: " + ftFromPerms(perms); + } + + // setuid, setgid + if (perms.su) { + output += "\nThe setuid flag is set"; + } + if (perms.sg) { + output += "\nThe setgid flag is set"; + } + + // sticky bit + if (perms.sb) { + output += "\nThe sticky bit is set"; + } + + // Permission matrix + output += ` + + +---------+-------+-------+-------+ + | | User | Group | Other | + +---------+-------+-------+-------+ + | Read | ${perms.ru ? "X" : " "} | ${perms.rg ? "X" : " "} | ${perms.ro ? "X" : " "} | + +---------+-------+-------+-------+ + | Write | ${perms.wu ? "X" : " "} | ${perms.wg ? "X" : " "} | ${perms.wo ? "X" : " "} | + +---------+-------+-------+-------+ + | Execute | ${perms.eu ? "X" : " "} | ${perms.eg ? "X" : " "} | ${perms.eo ? "X" : " "} | + +---------+-------+-------+-------+`; + + return output; + } + +} + + +/** + * Given a permissions object dictionary, generates a textual permissions string. + * + * @param {Object} perms + * @returns {string} + */ +function permsToStr(perms) { + let str = "", + type = "-"; + + if (perms.d) type = "d"; + if (perms.sl) type = "l"; + if (perms.np) type = "p"; + if (perms.s) type = "s"; + if (perms.cd) type = "c"; + if (perms.bd) type = "b"; + if (perms.dr) type = "D"; + + str = type; + + str += perms.ru ? "r" : "-"; + str += perms.wu ? "w" : "-"; + if (perms.eu && perms.su) { + str += "s"; + } else if (perms.su) { + str += "S"; + } else if (perms.eu) { + str += "x"; + } else { + str += "-"; + } + + str += perms.rg ? "r" : "-"; + str += perms.wg ? "w" : "-"; + if (perms.eg && perms.sg) { + str += "s"; + } else if (perms.sg) { + str += "S"; + } else if (perms.eg) { + str += "x"; + } else { + str += "-"; + } + + str += perms.ro ? "r" : "-"; + str += perms.wo ? "w" : "-"; + if (perms.eo && perms.sb) { + str += "t"; + } else if (perms.sb) { + str += "T"; + } else if (perms.eo) { + str += "x"; + } else { + str += "-"; + } + + return str; +} + +/** + * Given a permissions object dictionary, generates an octal permissions string. + * + * @param {Object} perms + * @returns {string} + */ +function permsToOctal(perms) { + let d = 0, + u = 0, + g = 0, + o = 0; + + if (perms.su) d += 4; + if (perms.sg) d += 2; + if (perms.sb) d += 1; + + if (perms.ru) u += 4; + if (perms.wu) u += 2; + if (perms.eu) u += 1; + + if (perms.rg) g += 4; + if (perms.wg) g += 2; + if (perms.eg) g += 1; + + if (perms.ro) o += 4; + if (perms.wo) o += 2; + if (perms.eo) o += 1; + + return d.toString() + u.toString() + g.toString() + o.toString(); +} + + +/** + * Given a permissions object dictionary, returns the file type. + * + * @param {Object} perms + * @returns {string} + */ +function ftFromPerms(perms) { + if (perms.d) return "Directory"; + if (perms.sl) return "Symbolic link"; + if (perms.np) return "Named pipe"; + if (perms.s) return "Socket"; + if (perms.cd) return "Character device"; + if (perms.bd) return "Block device"; + if (perms.dr) return "Door"; + return "Regular file"; +} + +export default ParseUNIXFilePermissions; diff --git a/src/core/operations/ParseURI.mjs b/src/core/operations/ParseURI.mjs new file mode 100644 index 00000000..d86cb061 --- /dev/null +++ b/src/core/operations/ParseURI.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import url from "url"; + +/** + * Parse URI operation + */ +class ParseURI extends Operation { + + /** + * ParseURI constructor + */ + constructor() { + super(); + + this.name = "Parse URI"; + this.module = "URL"; + this.description = "Pretty prints complicated Uniform Resource Identifier (URI) strings for ease of reading. Particularly useful for Uniform Resource Locators (URLs) with a lot of arguments."; + this.infoURL = "https://wikipedia.org/wiki/Uniform_Resource_Identifier"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const uri = url.parse(input, true); + + let output = ""; + + if (uri.protocol) output += "Protocol:\t" + uri.protocol + "\n"; + if (uri.auth) output += "Auth:\t\t" + uri.auth + "\n"; + if (uri.hostname) output += "Hostname:\t" + uri.hostname + "\n"; + if (uri.port) output += "Port:\t\t" + uri.port + "\n"; + if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n"; + if (uri.query) { + const keys = Object.keys(uri.query); + let padding = 0; + + keys.forEach(k => { + padding = (k.length > padding) ? k.length : padding; + }); + + output += "Arguments:\n"; + for (const key in uri.query) { + output += "\t" + key.padEnd(padding, " "); + if (uri.query[key].length) { + output += " = " + uri.query[key] + "\n"; + } else { + output += "\n"; + } + } + } + if (uri.hash) output += "Hash:\t\t" + uri.hash + "\n"; + + return output; + } + +} + +export default ParseURI; diff --git a/src/core/operations/ParseUserAgent.mjs b/src/core/operations/ParseUserAgent.mjs new file mode 100644 index 00000000..03ccc86e --- /dev/null +++ b/src/core/operations/ParseUserAgent.mjs @@ -0,0 +1,56 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import UAParser from "ua-parser-js"; + +/** + * Parse User Agent operation + */ +class ParseUserAgent extends Operation { + + /** + * ParseUserAgent constructor + */ + constructor() { + super(); + + this.name = "Parse User Agent"; + this.module = "UserAgent"; + this.description = "Attempts to identify and categorise information contained in a user-agent string."; + this.infoURL = "https://wikipedia.org/wiki/User_agent"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const ua = UAParser(input); + return `Browser + Name: ${ua.browser.name || "unknown"} + Version: ${ua.browser.version || "unknown"} +Device + Model: ${ua.device.model || "unknown"} + Type: ${ua.device.type || "unknown"} + Vendor: ${ua.device.vendor || "unknown"} +Engine + Name: ${ua.engine.name || "unknown"} + Version: ${ua.engine.version || "unknown"} +OS + Name: ${ua.os.name || "unknown"} + Version: ${ua.os.version || "unknown"} +CPU + Architecture: ${ua.cpu.architecture || "unknown"}`; + } + +} + +export default ParseUserAgent; diff --git a/src/core/operations/ParseX509Certificate.mjs b/src/core/operations/ParseX509Certificate.mjs new file mode 100644 index 00000000..a57f661e --- /dev/null +++ b/src/core/operations/ParseX509Certificate.mjs @@ -0,0 +1,220 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import { fromBase64 } from "../lib/Base64"; +import { toHex } from "../lib/Hex"; +import { formatByteStr, formatDnStr } from "../lib/PublicKey"; +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Parse X.509 certificate operation + */ +class ParseX509Certificate extends Operation { + + /** + * ParseX509Certificate constructor + */ + constructor() { + super(); + + this.name = "Parse X.509 certificate"; + this.module = "PublicKey"; + this.description = "X.509 is an ITU-T standard for a public key infrastructure (PKI) and Privilege Management Infrastructure (PMI). It is commonly involved with SSL/TLS security.

This operation displays the contents of a certificate in a human readable format, similar to the openssl command line tool.

Tags: X509, server hello, handshake"; + this.infoURL = "https://wikipedia.org/wiki/X.509"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["PEM", "DER Hex", "Base64", "Raw"] + } + ]; + this.patterns = [ + { + "match": "^-+BEGIN CERTIFICATE-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE-+\\r?\\n?$", + "flags": "i", + "args": [ + "PEM" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input.length) { + return "No input"; + } + + const cert = new r.X509(), + inputFormat = args[0]; + + switch (inputFormat) { + case "DER Hex": + input = input.replace(/\s/g, ""); + cert.readCertHex(input); + break; + case "PEM": + cert.readCertPEM(input); + break; + case "Base64": + cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), "")); + break; + case "Raw": + cert.readCertHex(toHex(Utils.strToByteArray(input), "")); + break; + default: + throw "Undefined input format"; + } + + const sn = cert.getSerialNumberHex(), + issuer = cert.getIssuerString(), + subject = cert.getSubjectString(), + pk = cert.getPublicKey(), + pkFields = [], + sig = cert.getSignatureValueHex(); + + let pkStr = "", + sigStr = "", + extensions = ""; + + // Public Key fields + pkFields.push({ + key: "Algorithm", + value: pk.type + }); + + if (pk.type === "EC") { // ECDSA + pkFields.push({ + key: "Curve Name", + value: pk.curveName + }); + pkFields.push({ + key: "Length", + value: (((new r.BigInteger(pk.pubKeyHex, 16)).bitLength()-3) /2) + " bits" + }); + pkFields.push({ + key: "pub", + value: formatByteStr(pk.pubKeyHex, 16, 18) + }); + } else if (pk.type === "DSA") { // DSA + pkFields.push({ + key: "pub", + value: formatByteStr(pk.y.toString(16), 16, 18) + }); + pkFields.push({ + key: "P", + value: formatByteStr(pk.p.toString(16), 16, 18) + }); + pkFields.push({ + key: "Q", + value: formatByteStr(pk.q.toString(16), 16, 18) + }); + pkFields.push({ + key: "G", + value: formatByteStr(pk.g.toString(16), 16, 18) + }); + } else if (pk.e) { // RSA + pkFields.push({ + key: "Length", + value: pk.n.bitLength() + " bits" + }); + pkFields.push({ + key: "Modulus", + value: formatByteStr(pk.n.toString(16), 16, 18) + }); + pkFields.push({ + key: "Exponent", + value: pk.e + " (0x" + pk.e.toString(16) + ")" + }); + } else { + pkFields.push({ + key: "Error", + value: "Unknown Public Key type" + }); + } + + // Format Public Key fields + for (let i = 0; i < pkFields.length; i++) { + pkStr += ` ${pkFields[i].key}:${(pkFields[i].value + "\n").padStart( + 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, + " " + )}`; + } + + // Signature fields + let breakoutSig = false; + try { + breakoutSig = r.ASN1HEX.dump(sig).indexOf("SEQUENCE") === 0; + } catch (err) { + // Error processing signature, output without further breakout + } + + if (breakoutSig) { // DSA or ECDSA + sigStr = ` r: ${formatByteStr(r.ASN1HEX.getV(sig, 4), 16, 18)} + s: ${formatByteStr(r.ASN1HEX.getV(sig, 48), 16, 18)}`; + } else { // RSA or unknown + sigStr = ` Signature: ${formatByteStr(sig, 16, 18)}`; + } + + // Extensions + try { + extensions = cert.getInfo().split("X509v3 Extensions:\n")[1].split("signature")[0]; + } catch (err) {} + + const issuerStr = formatDnStr(issuer, 2), + nbDate = formatDate(cert.getNotBefore()), + naDate = formatDate(cert.getNotAfter()), + subjectStr = formatDnStr(subject, 2); + + return `Version: ${cert.version} (0x${Utils.hex(cert.version - 1)}) +Serial number: ${new r.BigInteger(sn, 16).toString()} (0x${sn}) +Algorithm ID: ${cert.getSignatureAlgorithmField()} +Validity + Not Before: ${nbDate} (dd-mm-yyyy hh:mm:ss) (${cert.getNotBefore()}) + Not After: ${naDate} (dd-mm-yyyy hh:mm:ss) (${cert.getNotAfter()}) +Issuer +${issuerStr} +Subject +${subjectStr} +Public Key +${pkStr.slice(0, -1)} +Certificate Signature + Algorithm: ${cert.getSignatureAlgorithmName()} +${sigStr} + +Extensions +${extensions}`; + } + +} + +/** + * Formats dates. + * + * @param {string} dateStr + * @returns {string} + */ +function formatDate (dateStr) { + if (dateStr.length === 13) { // UTC Time + dateStr = (dateStr[0] < "5" ? "20" : "19") + dateStr; + } + return dateStr[6] + dateStr[7] + "/" + + dateStr[4] + dateStr[5] + "/" + + dateStr[0] + dateStr[1] + dateStr[2] + dateStr[3] + " " + + dateStr[8] + dateStr[9] + ":" + + dateStr[10] + dateStr[11] + ":" + + dateStr[12] + dateStr[13]; +} + +export default ParseX509Certificate; diff --git a/src/core/operations/PlayMedia.mjs b/src/core/operations/PlayMedia.mjs new file mode 100644 index 00000000..81328a73 --- /dev/null +++ b/src/core/operations/PlayMedia.mjs @@ -0,0 +1,102 @@ +/** + * @author anthony-arnold [anthony.arnold@uqconnect.edu.au] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import { fromBase64, toBase64 } from "../lib/Base64"; +import { fromHex } from "../lib/Hex"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; +import Magic from "../lib/Magic"; + +/** + * PlayMedia operation + */ +class PlayMedia extends Operation { + + /** + * PlayMedia constructor + */ + constructor() { + super(); + + this.name = "Play Media"; + this.module = "Default"; + this.description = "Plays the input as audio or video depending on the type.

Tags: sound, movie, mp3, mp4, mov, webm, wav, ogg"; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "byteArray"; + this.presentType = "html"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["Raw", "Base64", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} The multimedia data as bytes. + */ + run(input, args) { + const [inputFormat] = args; + + if (!input.length) return []; + + // Convert input to raw bytes + switch (inputFormat) { + case "Hex": + input = fromHex(input); + break; + case "Base64": + // Don't trust the Base64 entered by the user. + // Unwrap it first, then re-encode later. + input = fromBase64(input, undefined, "byteArray"); + break; + case "Raw": + default: + input = Utils.strToByteArray(input); + break; + } + + + // Determine file type + const type = Magic.magicFileType(input); + if (!(type && /^audio|video/.test(type.mime))) { + throw new OperationError("Invalid or unrecognised file type"); + } + + return input; + } + + /** + * Displays an audio or video element that may be able to play the media + * file. + * + * @param data {byteArray} Data containing an audio or video file. + * @returns {string} Markup to display a media player. + */ + async present(data) { + if (!data.length) return ""; + + const type = Magic.magicFileType(data); + const matches = /^audio|video/.exec(type.mime); + if (!matches) { + throw new OperationError("Invalid file type"); + } + const dataURI = `data:${type.mime};base64,${toBase64(data)}`; + const element = matches[0]; + + let html = `<${element} src='${dataURI}' type='${type.mime}' controls>`; + html += "

Unsupported media type.

"; + html += ``; + return html; + } +} + +export default PlayMedia; diff --git a/src/core/operations/PowerSet.mjs b/src/core/operations/PowerSet.mjs new file mode 100644 index 00000000..6866c254 --- /dev/null +++ b/src/core/operations/PowerSet.mjs @@ -0,0 +1,93 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Power Set operation + */ +class PowerSet extends Operation { + + /** + * Power set constructor + */ + constructor() { + super(); + + this.name = "Power Set"; + this.module = "Default"; + this.description = "Calculates all the subsets of a set."; + this.infoURL = "https://wikipedia.org/wiki/Power_set"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Generate the power set + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + [this.itemDelimiter] = args; + // Split and filter empty strings + const inputArray = input.split(this.itemDelimiter).filter(a => a); + + if (inputArray.length) { + return this.runPowerSet(inputArray); + } + + return ""; + } + + /** + * Return the power set of the inputted set. + * + * @param {Object[]} a + * @returns {Object[]} + */ + runPowerSet(a) { + // empty array items getting picked up + a = a.filter(i => i.length); + if (!a.length) { + return []; + } + + /** + * Decimal to binary function + * @param {*} dec + */ + const toBinary = (dec) => (dec >>> 0).toString(2); + const result = new Set(); + // Get the decimal number to make a binary as long as the input + const maxBinaryValue = parseInt(Number(a.map(i => "1").reduce((p, c) => p + c)), 2); + // Make an array of each binary number from 0 to maximum + const binaries = [...Array(maxBinaryValue + 1).keys()] + .map(toBinary) + .map(i => i.padStart(toBinary(maxBinaryValue).length, "0")); + + // XOR the input with each binary to get each unique permutation + binaries.forEach((binary) => { + const split = binary.split(""); + result.add(a.filter((item, index) => split[index] === "1")); + }); + + // map for formatting & put in length order. + return [...result] + .map(r => r.join(this.itemDelimiter)).sort((a, b) => a.length - b.length) + .map(i => `${i}\n`).join(""); + } +} + +export default PowerSet; diff --git a/src/core/operations/PseudoRandomNumberGenerator.mjs b/src/core/operations/PseudoRandomNumberGenerator.mjs new file mode 100644 index 00000000..6fdebefd --- /dev/null +++ b/src/core/operations/PseudoRandomNumberGenerator.mjs @@ -0,0 +1,81 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import forge from "node-forge/dist/forge.min.js"; +import BigNumber from "bignumber.js"; + +/** + * Pseudo-Random Number Generator operation + */ +class PseudoRandomNumberGenerator extends Operation { + + /** + * PseudoRandomNumberGenerator constructor + */ + constructor() { + super(); + + this.name = "Pseudo-Random Number Generator"; + this.module = "Ciphers"; + this.description = "A cryptographically-secure pseudo-random number generator (PRNG).

This operation uses the browser's built-in crypto.getRandomValues() method if available. If this cannot be found, it falls back to a Fortuna-based PRNG algorithm."; + this.infoURL = "https://wikipedia.org/wiki/Pseudorandom_number_generator"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Number of bytes", + "type": "number", + "value": 32 + }, + { + "name": "Output as", + "type": "option", + "value": ["Hex", "Integer", "Byte array", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [numBytes, outputAs] = args; + + let bytes; + + if (ENVIRONMENT_IS_WORKER() && self.crypto) { + bytes = self.crypto.getRandomValues(new Uint8Array(numBytes)); + bytes = Utils.arrayBufferToStr(bytes.buffer); + } else { + bytes = forge.random.getBytesSync(numBytes); + } + + let value = new BigNumber(0), + i; + + switch (outputAs) { + case "Hex": + return forge.util.bytesToHex(bytes); + case "Integer": + for (i = bytes.length - 1; i >= 0; i--) { + value = value.times(256).plus(bytes.charCodeAt(i)); + } + return value.toFixed(); + case "Byte array": + return JSON.stringify(Utils.strToCharcode(bytes)); + case "Raw": + default: + return bytes; + } + } + +} + +export default PseudoRandomNumberGenerator; diff --git a/src/core/operations/PublicKey.js b/src/core/operations/PublicKey.js deleted file mode 100755 index 237c968a..00000000 --- a/src/core/operations/PublicKey.js +++ /dev/null @@ -1,1064 +0,0 @@ -import Utils from "../Utils.js"; -import * as r from "jsrsasign"; - - -/** - * Public Key operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const PublicKey = { - - /** - * @constant - * @default - */ - X509_INPUT_FORMAT: ["PEM", "DER Hex", "Base64", "Raw"], - - /** - * Parse X.509 certificate operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseX509: function (input, args) { - let cert = new r.X509(), - inputFormat = args[0]; - - if (!input.length) { - return "No input"; - } - - switch (inputFormat) { - case "DER Hex": - input = input.replace(/\s/g, ""); - cert.hex = input; - cert.pem = r.KJUR.asn1.ASN1Util.getPEMStringFromHex(input, "CERTIFICATE"); - break; - case "PEM": - cert.hex = r.X509.pemToHex(input); - cert.pem = input; - break; - case "Base64": - cert.hex = Utils.toHex(Utils.fromBase64(input, null, "byteArray"), ""); - cert.pem = r.KJUR.asn1.ASN1Util.getPEMStringFromHex(cert.hex, "CERTIFICATE"); - break; - case "Raw": - cert.hex = Utils.toHex(Utils.strToByteArray(input), ""); - cert.pem = r.KJUR.asn1.ASN1Util.getPEMStringFromHex(cert.hex, "CERTIFICATE"); - break; - default: - throw "Undefined input format"; - } - - let version = r.ASN1HEX.getDecendantHexVByNthList(cert.hex, 0, [0, 0, 0]), - sn = cert.getSerialNumberHex(), - algorithm = r.KJUR.asn1.x509.OID.oid2name(r.KJUR.asn1.ASN1Util.oidHexToInt(r.ASN1HEX.getDecendantHexVByNthList(cert.hex, 0, [0, 2, 0]))), - issuer = cert.getIssuerString(), - notBefore = cert.getNotBefore(), - notAfter = cert.getNotAfter(), - subject = cert.getSubjectString(), - pkAlgorithm = r.KJUR.asn1.x509.OID.oid2name(r.KJUR.asn1.ASN1Util.oidHexToInt(r.ASN1HEX.getDecendantHexVByNthList(cert.hex, 0, [0, 6, 0, 0]))), - pk = r.X509.getPublicKeyFromCertPEM(cert.pem), - pkFields = [], - pkStr = "", - certSigAlg = r.KJUR.asn1.x509.OID.oid2name(r.KJUR.asn1.ASN1Util.oidHexToInt(r.ASN1HEX.getDecendantHexVByNthList(cert.hex, 0, [1, 0]))), - certSig = r.ASN1HEX.getDecendantHexVByNthList(cert.hex, 0, [2]).substr(2), - sigStr = "", - extensions = r.ASN1HEX.dump(r.ASN1HEX.getDecendantHexVByNthList(cert.hex, 0, [0, 7])); - - // Public Key fields - if (pk.type === "EC") { // ECDSA - pkFields.push({ - key: "Curve Name", - value: pk.curveName - }); - pkFields.push({ - key: "Length", - value: (((new r.BigInteger(pk.pubKeyHex, 16)).bitLength()-3) /2) + " bits" - }); - pkFields.push({ - key: "pub", - value: PublicKey._formatByteStr(pk.pubKeyHex, 16, 18) - }); - } else if (pk.type === "DSA") { // DSA - pkFields.push({ - key: "pub", - value: PublicKey._formatByteStr(pk.y.toString(16), 16, 18) - }); - pkFields.push({ - key: "P", - value: PublicKey._formatByteStr(pk.p.toString(16), 16, 18) - }); - pkFields.push({ - key: "Q", - value: PublicKey._formatByteStr(pk.q.toString(16), 16, 18) - }); - pkFields.push({ - key: "G", - value: PublicKey._formatByteStr(pk.g.toString(16), 16, 18) - }); - } else if (pk.e) { // RSA - pkFields.push({ - key: "Length", - value: pk.n.bitLength() + " bits" - }); - pkFields.push({ - key: "Modulus", - value: PublicKey._formatByteStr(pk.n.toString(16), 16, 18) - }); - pkFields.push({ - key: "Exponent", - value: pk.e + " (0x" + pk.e.toString(16) + ")" - }); - } else { - pkFields.push({ - key: "Error", - value: "Unknown Public Key type" - }); - } - - // Signature fields - let breakoutSig = false; - try { - breakoutSig = r.ASN1HEX.dump(certSig).indexOf("SEQUENCE") === 0; - } catch (err) { - // Error processing signature, output without further breakout - } - - if (breakoutSig) { // DSA or ECDSA - sigStr = " r: " + PublicKey._formatByteStr(r.ASN1HEX.getDecendantHexVByNthList(certSig, 0, [0]), 16, 18) + "\n" + - " s: " + PublicKey._formatByteStr(r.ASN1HEX.getDecendantHexVByNthList(certSig, 0, [1]), 16, 18) + "\n"; - } else { // RSA or unknown - sigStr = " Signature: " + PublicKey._formatByteStr(certSig, 16, 18) + "\n"; - } - - // Format Public Key fields - for (let i = 0; i < pkFields.length; i++) { - pkStr += " " + pkFields[i].key + ":" + - Utils.padLeft( - pkFields[i].value + "\n", - 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, - " " - ); - } - - let issuerStr = PublicKey._formatDnStr(issuer, 2), - nbDate = PublicKey._formatDate(notBefore), - naDate = PublicKey._formatDate(notAfter), - subjectStr = PublicKey._formatDnStr(subject, 2); - - const output = "Version: " + (parseInt(version, 16) + 1) + " (0x" + version + ")\n" + - "Serial number: " + new r.BigInteger(sn, 16).toString() + " (0x" + sn + ")\n" + - "Algorithm ID: " + algorithm + "\n" + - "Validity\n" + - " Not Before: " + nbDate + " (dd-mm-yy hh:mm:ss) (" + notBefore + ")\n" + - " Not After: " + naDate + " (dd-mm-yy hh:mm:ss) (" + notAfter + ")\n" + - "Issuer\n" + - issuerStr + - "Subject\n" + - subjectStr + - "Public Key\n" + - " Algorithm: " + pkAlgorithm + "\n" + - pkStr + - "Certificate Signature\n" + - " Algorithm: " + certSigAlg + "\n" + - sigStr + - "\nExtensions (parsed ASN.1)\n" + - extensions; - - return output; - }, - - - /** - * PEM to Hex operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runPemToHex: function(input, args) { - if (input.indexOf("-----BEGIN") < 0) { - // Add header so that the KEYUTIL function works - input = "-----BEGIN CERTIFICATE-----" + input; - } - if (input.indexOf("-----END") < 0) { - // Add footer so that the KEYUTIL function works - input = input + "-----END CERTIFICATE-----"; - } - return r.KEYUTIL.getHexFromPEM(input); - }, - - - /** - * @constant - * @default - */ - PEM_HEADER_STRING: "CERTIFICATE", - - /** - * Hex to PEM operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runHexToPem: function(input, args) { - return r.KJUR.asn1.ASN1Util.getPEMStringFromHex(input.replace(/\s/g, ""), args[0]); - }, - - - /** - * Hex to Object Identifier operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runHexToObjectIdentifier: function(input, args) { - return r.KJUR.asn1.ASN1Util.oidHexToInt(input.replace(/\s/g, "")); - }, - - - /** - * Object Identifier to Hex operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runObjectIdentifierToHex: function(input, args) { - return r.KJUR.asn1.ASN1Util.oidIntToHex(input); - }, - - - /** - * @constant - * @default - */ - ASN1_TRUNCATE_LENGTH: 32, - - /** - * Parse ASN.1 hex string operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseAsn1HexString: function(input, args) { - let truncateLen = args[1], - index = args[0]; - return r.ASN1HEX.dump(input.replace(/\s/g, ""), { - "ommitLongOctet": truncateLen - }, index); - }, - - - /** - * Formats Distinguished Name (DN) strings. - * - * @private - * @param {string} dnStr - * @param {number} indent - * @returns {string} - */ - _formatDnStr: function(dnStr, indent) { - let output = "", - fields = dnStr.split(",/|"), - maxKeyLen = 0, - key, - value, - i, - str; - - for (i = 0; i < fields.length; i++) { - if (!fields[i].length) continue; - - key = fields[i].split("=")[0]; - - maxKeyLen = key.length > maxKeyLen ? key.length : maxKeyLen; - } - - for (i = 0; i < fields.length; i++) { - if (!fields[i].length) continue; - - key = fields[i].split("=")[0]; - value = fields[i].split("=")[1]; - str = Utils.padRight(key, maxKeyLen) + " = " + value + "\n"; - - output += Utils.padLeft(str, indent + str.length, " "); - } - - return output; - }, - - - /** - * Formats byte strings by adding line breaks and delimiters. - * - * @private - * @param {string} byteStr - * @param {number} length - Line width - * @param {number} indent - * @returns {string} - */ - _formatByteStr: function(byteStr, length, indent) { - byteStr = Utils.toHex(Utils.fromHex(byteStr), ":"); - length = length * 3; - let output = ""; - - for (let i = 0; i < byteStr.length; i += length) { - const str = byteStr.slice(i, i + length) + "\n"; - if (i === 0) { - output += str; - } else { - output += Utils.padLeft(str, indent + str.length, " "); - } - } - - return output.slice(0, output.length-1); - }, - - - /** - * Formats dates. - * - * @private - * @param {string} dateStr - * @returns {string} - */ - _formatDate: function(dateStr) { - return dateStr[4] + dateStr[5] + "/" + - dateStr[2] + dateStr[3] + "/" + - dateStr[0] + dateStr[1] + " " + - dateStr[6] + dateStr[7] + ":" + - dateStr[8] + dateStr[9] + ":" + - dateStr[10] + dateStr[11]; - }, - -}; - -export default PublicKey; - - -/** - * Overwrite X509.hex2dn function so as to join RDNs with a string which can be split on without - * causing problems later (I hope). - * - * @param {string} hDN - Hex DN string - * @returns {string} - */ -r.X509.hex2dn = function(hDN) { - let s = ""; - const a = r.ASN1HEX.getPosArrayOfChildren_AtObj(hDN, 0); - for (let i = 0; i < a.length; i++) { - const hRDN = r.ASN1HEX.getHexOfTLV_AtObj(hDN, a[i]); - s = s + ",/|" + r.X509.hex2rdn(hRDN); - } - return s; -}; - - -/** - * Overwrite DN attribute lookup in jsrasign library with a much more complete version from - * https://github.com/nfephp-org/nfephp/blob/master/libs/Common/Certificate/Oids.php - * - * Various duplicates commented out. - * - * @constant - */ -r.X509.DN_ATTRHEX = { - "0603550403" : "commonName", - "0603550404" : "surname", - "0603550406" : "countryName", - "0603550407" : "localityName", - "0603550408" : "stateOrProvinceName", - "0603550409" : "streetAddress", - "060355040a" : "organizationName", - "060355040b" : "organizationalUnitName", - "060355040c" : "title", - "0603550414" : "telephoneNumber", - "060355042a" : "givenName", - // "0603551d0e" : "id-ce-subjectKeyIdentifier", - // "0603551d0f" : "id-ce-keyUsage", - // "0603551d11" : "id-ce-subjectAltName", - // "0603551d13" : "id-ce-basicConstraints", - // "0603551d14" : "id-ce-cRLNumber", - // "0603551d1f" : "id-ce-CRLDistributionPoints", - // "0603551d20" : "id-ce-certificatePolicies", - // "0603551d23" : "id-ce-authorityKeyIdentifier", - // "0603551d25" : "id-ce-extKeyUsage", - // "06032a864886f70d010901" : "Email", - // "06032a864886f70d010101" : "RSAEncryption", - // "06032a864886f70d010102" : "md2WithRSAEncryption", - // "06032a864886f70d010104" : "md5withRSAEncryption", - // "06032a864886f70d010105" : "SHA-1WithRSAEncryption", - // "06032a8648ce380403" : "id-dsa-with-sha-1", - // "06032b06010505070302" : "idKpClientAuth", - // "06032b06010505070304" : "idKpSecurityemail", - "06032b06010505070201" : "idCertificatePolicies", - "06036086480186f8420101" : "netscape-cert-type", - "06036086480186f8420102" : "netscape-base-url", - "06036086480186f8420103" : "netscape-revocation-url", - "06036086480186f8420104" : "netscape-ca-revocation-url", - "06036086480186f8420107" : "netscape-cert-renewal-url", - "06036086480186f8420108" : "netscape-ca-policy-url", - "06036086480186f842010c" : "netscape-ssl-server-name", - "06036086480186f842010d" : "netscape-comment", - "0603604c010201" : "A1", - "0603604c010203" : "A3", - "0603604c01020110" : "Certification Practice Statement pointer", - "0603604c010301" : "Dados do cert parte 1", - "0603604c010305" : "Dados do cert parte 2", - "0603604c010306" : "Dados do cert parte 3", - "06030992268993f22c640119" : "domainComponent", - "06032a24a0f2a07d01010a" : "Signet pilot", - "06032a24a0f2a07d01010b" : "Signet intraNet", - "06032a24a0f2a07d010102" : "Signet personal", - "06032a24a0f2a07d010114" : "Signet securityPolicy", - "06032a24a0f2a07d010103" : "Signet business", - "06032a24a0f2a07d010104" : "Signet legal", - "06032a24a497a35301640101" : "Certificates Australia policyIdentifier", - "06032a85702201" : "seis-cp", - "06032a8570220101" : "SEIS certificatePolicy-s10", - "06032a85702202" : "SEIS pe", - "06032a85702203" : "SEIS at", - "06032a8570220301" : "SEIS at-personalIdentifier", - "06032a8648ce380201" : "holdinstruction-none", - "06032a8648ce380202" : "holdinstruction-callissuer", - "06032a8648ce380203" : "holdinstruction-reject", - "06032a8648ce380401" : "dsa", - "06032a8648ce380403" : "dsaWithSha1", - "06032a8648ce3d01" : "fieldType", - "06032a8648ce3d0101" : "prime-field", - "06032a8648ce3d0102" : "characteristic-two-field", - "06032a8648ce3d010201" : "ecPublicKey", - "06032a8648ce3d010203" : "characteristic-two-basis", - "06032a8648ce3d01020301" : "onBasis", - "06032a8648ce3d01020302" : "tpBasis", - "06032a8648ce3d01020303" : "ppBasis", - "06032a8648ce3d02" : "publicKeyType", - "06032a8648ce3d0201" : "ecPublicKey", - "06032a8648ce3e0201" : "dhPublicNumber", - "06032a864886f67d07" : "nsn", - "06032a864886f67d0741" : "nsn-ce", - "06032a864886f67d074100" : "entrustVersInfo", - "06032a864886f67d0742" : "nsn-alg", - "06032a864886f67d07420a" : "cast5CBC", - "06032a864886f67d07420b" : "cast5MAC", - "06032a864886f67d07420c" : "pbeWithMD5AndCAST5-CBC", - "06032a864886f67d07420d" : "passwordBasedMac", - "06032a864886f67d074203" : "cast3CBC", - "06032a864886f67d0743" : "nsn-oc", - "06032a864886f67d074300" : "entrustUser", - "06032a864886f67d0744" : "nsn-at", - "06032a864886f67d074400" : "entrustCAInfo", - "06032a864886f67d07440a" : "attributeCertificate", - "06032a864886f70d0101" : "pkcs-1", - "06032a864886f70d010101" : "rsaEncryption", - "06032a864886f70d010102" : "md2withRSAEncryption", - "06032a864886f70d010103" : "md4withRSAEncryption", - "06032a864886f70d010104" : "md5withRSAEncryption", - "06032a864886f70d010105" : "sha1withRSAEncryption", - "06032a864886f70d010106" : "rsaOAEPEncryptionSET", - "06032a864886f70d010910020b" : "SMIMEEncryptionKeyPreference", - "06032a864886f70d010c" : "pkcs-12", - "06032a864886f70d010c01" : "pkcs-12-PbeIds", - "06032a864886f70d010c0101" : "pbeWithSHAAnd128BitRC4", - "06032a864886f70d010c0102" : "pbeWithSHAAnd40BitRC4", - "06032a864886f70d010c0103" : "pbeWithSHAAnd3-KeyTripleDES-CBC", - "06032a864886f70d010c0104" : "pbeWithSHAAnd2-KeyTripleDES-CBC", - "06032a864886f70d010c0105" : "pbeWithSHAAnd128BitRC2-CBC", - "06032a864886f70d010c0106" : "pbeWithSHAAnd40BitRC2-CBC", - "06032a864886f70d010c0a" : "pkcs-12Version1", - "06032a864886f70d010c0a01" : "pkcs-12BadIds", - "06032a864886f70d010c0a0101" : "pkcs-12-keyBag", - "06032a864886f70d010c0a0102" : "pkcs-12-pkcs-8ShroudedKeyBag", - "06032a864886f70d010c0a0103" : "pkcs-12-certBag", - "06032a864886f70d010c0a0104" : "pkcs-12-crlBag", - "06032a864886f70d010c0a0105" : "pkcs-12-secretBag", - "06032a864886f70d010c0a0106" : "pkcs-12-safeContentsBag", - "06032a864886f70d010c02" : "pkcs-12-ESPVKID", - "06032a864886f70d010c0201" : "pkcs-12-PKCS8KeyShrouding", - "06032a864886f70d010c03" : "pkcs-12-BagIds", - "06032a864886f70d010c0301" : "pkcs-12-keyBagId", - "06032a864886f70d010c0302" : "pkcs-12-certAndCRLBagId", - "06032a864886f70d010c0303" : "pkcs-12-secretBagId", - "06032a864886f70d010c0304" : "pkcs-12-safeContentsId", - "06032a864886f70d010c0305" : "pkcs-12-pkcs-8ShroudedKeyBagId", - "06032a864886f70d010c04" : "pkcs-12-CertBagID", - "06032a864886f70d010c0401" : "pkcs-12-X509CertCRLBagID", - "06032a864886f70d010c0402" : "pkcs-12-SDSICertBagID", - "06032a864886f70d010c05" : "pkcs-12-OID", - "06032a864886f70d010c0501" : "pkcs-12-PBEID", - "06032a864886f70d010c050101" : "pkcs-12-PBEWithSha1And128BitRC4", - "06032a864886f70d010c050102" : "pkcs-12-PBEWithSha1And40BitRC4", - "06032a864886f70d010c050103" : "pkcs-12-PBEWithSha1AndTripleDESCBC", - "06032a864886f70d010c050104" : "pkcs-12-PBEWithSha1And128BitRC2CBC", - "06032a864886f70d010c050105" : "pkcs-12-PBEWithSha1And40BitRC2CBC", - "06032a864886f70d010c050106" : "pkcs-12-PBEWithSha1AndRC4", - "06032a864886f70d010c050107" : "pkcs-12-PBEWithSha1AndRC2CBC", - "06032a864886f70d010c0502" : "pkcs-12-EnvelopingID", - "06032a864886f70d010c050201" : "pkcs-12-RSAEncryptionWith128BitRC4", - "06032a864886f70d010c050202" : "pkcs-12-RSAEncryptionWith40BitRC4", - "06032a864886f70d010c050203" : "pkcs-12-RSAEncryptionWithTripleDES", - "06032a864886f70d010c0503" : "pkcs-12-SignatureID", - "06032a864886f70d010c050301" : "pkcs-12-RSASignatureWithSHA1Digest", - "06032a864886f70d0103" : "pkcs-3", - "06032a864886f70d010301" : "dhKeyAgreement", - "06032a864886f70d0105" : "pkcs-5", - "06032a864886f70d010501" : "pbeWithMD2AndDES-CBC", - "06032a864886f70d01050a" : "pbeWithSHAAndDES-CBC", - "06032a864886f70d010503" : "pbeWithMD5AndDES-CBC", - "06032a864886f70d010504" : "pbeWithMD2AndRC2-CBC", - "06032a864886f70d010506" : "pbeWithMD5AndRC2-CBC", - "06032a864886f70d010509" : "pbeWithMD5AndXOR", - "06032a864886f70d0107" : "pkcs-7", - "06032a864886f70d010701" : "data", - "06032a864886f70d010702" : "signedData", - "06032a864886f70d010703" : "envelopedData", - "06032a864886f70d010704" : "signedAndEnvelopedData", - "06032a864886f70d010705" : "digestData", - "06032a864886f70d010706" : "encryptedData", - "06032a864886f70d010707" : "dataWithAttributes", - "06032a864886f70d010708" : "encryptedPrivateKeyInfo", - "06032a864886f70d0109" : "pkcs-9", - "06032a864886f70d010901" : "emailAddress", - "06032a864886f70d01090a" : "issuerAndSerialNumber", - "06032a864886f70d01090b" : "passwordCheck", - "06032a864886f70d01090c" : "publicKey", - "06032a864886f70d01090d" : "signingDescription", - "06032a864886f70d01090e" : "extensionReq", - "06032a864886f70d01090f" : "sMIMECapabilities", - "06032a864886f70d01090f01" : "preferSignedData", - "06032a864886f70d01090f02" : "canNotDecryptAny", - "06032a864886f70d01090f03" : "receiptRequest", - "06032a864886f70d01090f04" : "receipt", - "06032a864886f70d01090f05" : "contentHints", - "06032a864886f70d01090f06" : "mlExpansionHistory", - "06032a864886f70d010910" : "id-sMIME", - "06032a864886f70d01091000" : "id-mod", - "06032a864886f70d0109100001" : "id-mod-cms", - "06032a864886f70d0109100002" : "id-mod-ess", - "06032a864886f70d01091001" : "id-ct", - "06032a864886f70d0109100101" : "id-ct-receipt", - "06032a864886f70d01091002" : "id-aa", - "06032a864886f70d0109100201" : "id-aa-receiptRequest", - "06032a864886f70d0109100202" : "id-aa-securityLabel", - "06032a864886f70d0109100203" : "id-aa-mlExpandHistory", - "06032a864886f70d0109100204" : "id-aa-contentHint", - "06032a864886f70d010902" : "unstructuredName", - "06032a864886f70d010914" : "friendlyName", - "06032a864886f70d010915" : "localKeyID", - "06032a864886f70d010916" : "certTypes", - "06032a864886f70d01091601" : "x509Certificate", - "06032a864886f70d01091602" : "sdsiCertificate", - "06032a864886f70d010917" : "crlTypes", - "06032a864886f70d01091701" : "x509Crl", - "06032a864886f70d010903" : "contentType", - "06032a864886f70d010904" : "messageDigest", - "06032a864886f70d010905" : "signingTime", - "06032a864886f70d010906" : "countersignature", - "06032a864886f70d010907" : "challengePassword", - "06032a864886f70d010908" : "unstructuredAddress", - "06032a864886f70d010909" : "extendedCertificateAttributes", - "06032a864886f70d02" : "digestAlgorithm", - "06032a864886f70d0202" : "md2", - "06032a864886f70d0204" : "md4", - "06032a864886f70d0205" : "md5", - "06032a864886f70d03" : "encryptionAlgorithm", - "06032a864886f70d030a" : "desCDMF", - "06032a864886f70d0302" : "rc2CBC", - "06032a864886f70d0303" : "rc2ECB", - "06032a864886f70d0304" : "rc4", - "06032a864886f70d0305" : "rc4WithMAC", - "06032a864886f70d0306" : "DESX-CBC", - "06032a864886f70d0307" : "DES-EDE3-CBC", - "06032a864886f70d0308" : "RC5CBC", - "06032a864886f70d0309" : "RC5-CBCPad", - "06032a864886f7140403" : "microsoftExcel", - "06032a864886f7140404" : "titledWithOID", - "06032a864886f7140405" : "microsoftPowerPoint", - "06032b81051086480954" : "x9-84", - "06032b8105108648095400" : "x9-84-Module", - "06032b810510864809540001" : "x9-84-Biometrics", - "06032b810510864809540002" : "x9-84-CMS", - "06032b810510864809540003" : "x9-84-Identifiers", - "06032b8105108648095401" : "biometric", - "06032b810510864809540100" : "id-unknown-Type", - "06032b810510864809540101" : "id-body-Odor", - "06032b81051086480954010a" : "id-palm", - "06032b81051086480954010b" : "id-retina", - "06032b81051086480954010c" : "id-signature", - "06032b81051086480954010d" : "id-speech-Pattern", - "06032b81051086480954010e" : "id-thermal-Image", - "06032b81051086480954010f" : "id-vein-Pattern", - "06032b810510864809540110" : "id-thermal-Face-Image", - "06032b810510864809540111" : "id-thermal-Hand-Image", - "06032b810510864809540112" : "id-lip-Movement", - "06032b810510864809540113" : "id-gait", - "06032b810510864809540102" : "id-dna", - "06032b810510864809540103" : "id-ear-Shape", - "06032b810510864809540104" : "id-facial-Features", - "06032b810510864809540105" : "id-finger-Image", - "06032b810510864809540106" : "id-finger-Geometry", - "06032b810510864809540107" : "id-hand-Geometry", - "06032b810510864809540108" : "id-iris-Features", - "06032b810510864809540109" : "id-keystroke-Dynamics", - "06032b8105108648095402" : "processing-algorithm", - "06032b8105108648095403" : "matching-method", - "06032b8105108648095404" : "format-Owner", - "06032b810510864809540400" : "cbeff-Owner", - "06032b810510864809540401" : "ibia-Owner", - "06032b81051086480954040101" : "id-ibia-SAFLINK", - "06032b8105108648095404010a" : "id-ibia-SecuGen", - "06032b8105108648095404010b" : "id-ibia-PreciseBiometric", - "06032b8105108648095404010c" : "id-ibia-Identix", - "06032b8105108648095404010d" : "id-ibia-DERMALOG", - "06032b8105108648095404010e" : "id-ibia-LOGICO", - "06032b8105108648095404010f" : "id-ibia-NIST", - "06032b81051086480954040110" : "id-ibia-A3Vision", - "06032b81051086480954040111" : "id-ibia-NEC", - "06032b81051086480954040112" : "id-ibia-STMicroelectronics", - "06032b81051086480954040102" : "id-ibia-Bioscrypt", - "06032b81051086480954040103" : "id-ibia-Visionics", - "06032b81051086480954040104" : "id-ibia-InfineonTechnologiesAG", - "06032b81051086480954040105" : "id-ibia-IridianTechnologies", - "06032b81051086480954040106" : "id-ibia-Veridicom", - "06032b81051086480954040107" : "id-ibia-CyberSIGN", - "06032b81051086480954040108" : "id-ibia-eCryp.", - "06032b81051086480954040109" : "id-ibia-FingerprintCardsAB", - "06032b810510864809540402" : "x9-Owner", - "06032b0e021a05" : "sha", - "06032b0e03020101" : "rsa", - "06032b0e03020a" : "desMAC", - "06032b0e03020b" : "rsaSignature", - "06032b0e03020c" : "dsa", - "06032b0e03020d" : "dsaWithSHA", - "06032b0e03020e" : "mdc2WithRSASignature", - "06032b0e03020f" : "shaWithRSASignature", - "06032b0e030210" : "dhWithCommonModulus", - "06032b0e030211" : "desEDE", - "06032b0e030212" : "sha", - "06032b0e030213" : "mdc-2", - "06032b0e030202" : "md4WitRSA", - "06032b0e03020201" : "sqmod-N", - "06032b0e030214" : "dsaCommon", - "06032b0e030215" : "dsaCommonWithSHA", - "06032b0e030216" : "rsaKeyTransport", - "06032b0e030217" : "keyed-hash-seal", - "06032b0e030218" : "md2WithRSASignature", - "06032b0e030219" : "md5WithRSASignature", - "06032b0e03021a" : "sha1", - "06032b0e03021b" : "dsaWithSHA1", - "06032b0e03021c" : "dsaWithCommonSHA1", - "06032b0e03021d" : "sha-1WithRSAEncryption", - "06032b0e030203" : "md5WithRSA", - "06032b0e03020301" : "sqmod-NwithRSA", - "06032b0e030204" : "md4WithRSAEncryption", - "06032b0e030206" : "desECB", - "06032b0e030207" : "desCBC", - "06032b0e030208" : "desOFB", - "06032b0e030209" : "desCFB", - "06032b0e030301" : "simple-strong-auth-mechanism", - "06032b0e07020101" : "ElGamal", - "06032b0e07020301" : "md2WithRSA", - "06032b0e07020302" : "md2WithElGamal", - "06032b2403" : "algorithm", - "06032b240301" : "encryptionAlgorithm", - "06032b24030101" : "des", - "06032b240301010101" : "desECBPad", - "06032b24030101010101" : "desECBPadISO", - "06032b240301010201" : "desCBCPad", - "06032b24030101020101" : "desCBCPadISO", - "06032b24030102" : "idea", - "06032b2403010201" : "ideaECB", - "06032b240301020101" : "ideaECBPad", - "06032b24030102010101" : "ideaECBPadISO", - "06032b2403010202" : "ideaCBC", - "06032b240301020201" : "ideaCBCPad", - "06032b24030102020101" : "ideaCBCPadISO", - "06032b2403010203" : "ideaOFB", - "06032b2403010204" : "ideaCFB", - "06032b24030103" : "des-3", - "06032b240301030101" : "des-3ECBPad", - "06032b24030103010101" : "des-3ECBPadISO", - "06032b240301030201" : "des-3CBCPad", - "06032b24030103020101" : "des-3CBCPadISO", - "06032b240302" : "hashAlgorithm", - "06032b24030201" : "ripemd160", - "06032b24030202" : "ripemd128", - "06032b24030203" : "ripemd256", - "06032b24030204" : "mdc2singleLength", - "06032b24030205" : "mdc2doubleLength", - "06032b240303" : "signatureAlgorithm", - "06032b24030301" : "rsa", - "06032b2403030101" : "rsaMitSHA-1", - "06032b2403030102" : "rsaMitRIPEMD160", - "06032b24030302" : "ellipticCurve", - "06032b240304" : "signatureScheme", - "06032b24030401" : "iso9796-1", - "06032b2403040201" : "iso9796-2", - "06032b2403040202" : "iso9796-2rsa", - "06032b2404" : "attribute", - "06032b2405" : "policy", - "06032b2406" : "api", - "06032b240601" : "manufacturerSpecific", - "06032b240602" : "functionalitySpecific", - "06032b2407" : "api", - "06032b240701" : "keyAgreement", - "06032b240702" : "keyTransport", - "06032b06010401927c0a0101" : "UNINETT policyIdentifier", - "06032b0601040195180a" : "ICE-TEL policyIdentifier", - "06032b0601040197552001" : "cryptlibEnvelope", - "06032b0601040197552002" : "cryptlibPrivateKey", - "060a2b060104018237" : "Microsoft OID", - "060a2b0601040182370a" : "Crypto 2.0", - "060a2b0601040182370a01" : "certTrustList", - "060a2b0601040182370a0101" : "szOID_SORTED_CTL", - "060a2b0601040182370a0a" : "Microsoft CMC OIDs", - "060a2b0601040182370a0a01" : "szOID_CMC_ADD_ATTRIBUTES", - "060a2b0601040182370a0b" : "Microsoft certificate property OIDs", - "060a2b0601040182370a0b01" : "szOID_CERT_PROP_ID_PREFIX", - "060a2b0601040182370a0c" : "CryptUI", - "060a2b0601040182370a0c01" : "szOID_ANY_APPLICATION_POLICY", - "060a2b0601040182370a02" : "nextUpdateLocation", - "060a2b0601040182370a0301" : "certTrustListSigning", - "060a2b0601040182370a030a" : "szOID_KP_QUALIFIED_SUBORDINATION", - "060a2b0601040182370a030b" : "szOID_KP_KEY_RECOVERY", - "060a2b0601040182370a030c" : "szOID_KP_DOCUMENT_SIGNING", - "060a2b0601040182370a0302" : "timeStampSigning", - "060a2b0601040182370a0303" : "serverGatedCrypto", - "060a2b0601040182370a030301" : "szOID_SERIALIZED", - "060a2b0601040182370a0304" : "encryptedFileSystem", - "060a2b0601040182370a030401" : "szOID_EFS_RECOVERY", - "060a2b0601040182370a0305" : "szOID_WHQL_CRYPTO", - "060a2b0601040182370a0306" : "szOID_NT5_CRYPTO", - "060a2b0601040182370a0307" : "szOID_OEM_WHQL_CRYPTO", - "060a2b0601040182370a0308" : "szOID_EMBEDDED_NT_CRYPTO", - "060a2b0601040182370a0309" : "szOID_ROOT_LIST_SIGNER", - "060a2b0601040182370a0401" : "yesnoTrustAttr", - "060a2b0601040182370a0501" : "szOID_DRM", - "060a2b0601040182370a0502" : "szOID_DRM_INDIVIDUALIZATION", - "060a2b0601040182370a0601" : "szOID_LICENSES", - "060a2b0601040182370a0602" : "szOID_LICENSE_SERVER", - "060a2b0601040182370a07" : "szOID_MICROSOFT_RDN_PREFIX", - "060a2b0601040182370a0701" : "szOID_KEYID_RDN", - "060a2b0601040182370a0801" : "szOID_REMOVE_CERTIFICATE", - "060a2b0601040182370a0901" : "szOID_CROSS_CERT_DIST_POINTS", - "060a2b0601040182370c" : "Catalog", - "060a2b0601040182370c0101" : "szOID_CATALOG_LIST", - "060a2b0601040182370c0102" : "szOID_CATALOG_LIST_MEMBER", - "060a2b0601040182370c0201" : "CAT_NAMEVALUE_OBJID", - "060a2b0601040182370c0202" : "CAT_MEMBERINFO_OBJID", - "060a2b0601040182370d" : "Microsoft PKCS10 OIDs", - "060a2b0601040182370d01" : "szOID_RENEWAL_CERTIFICATE", - "060a2b0601040182370d0201" : "szOID_ENROLLMENT_NAME_VALUE_PAIR", - "060a2b0601040182370d0202" : "szOID_ENROLLMENT_CSP_PROVIDER", - "060a2b0601040182370d0203" : "OS Version", - "060a2b0601040182370f" : "Microsoft Java", - "060a2b06010401823710" : "Microsoft Outlook/Exchange", - "060a2b0601040182371004" : "Outlook Express", - "060a2b06010401823711" : "Microsoft PKCS12 attributes", - "060a2b0601040182371101" : "szOID_LOCAL_MACHINE_KEYSET", - "060a2b06010401823712" : "Microsoft Hydra", - "060a2b06010401823713" : "Microsoft ISPU Test", - "060a2b06010401823702" : "Authenticode", - "060a2b06010401823702010a" : "spcAgencyInfo", - "060a2b06010401823702010b" : "spcStatementType", - "060a2b06010401823702010c" : "spcSpOpusInfo", - "060a2b06010401823702010e" : "certExtensions", - "060a2b06010401823702010f" : "spcPelmageData", - "060a2b060104018237020112" : "SPC_RAW_FILE_DATA_OBJID", - "060a2b060104018237020113" : "SPC_STRUCTURED_STORAGE_DATA_OBJID", - "060a2b060104018237020114" : "spcLink", - "060a2b060104018237020115" : "individualCodeSigning", - "060a2b060104018237020116" : "commercialCodeSigning", - "060a2b060104018237020119" : "spcLink", - "060a2b06010401823702011a" : "spcMinimalCriteriaInfo", - "060a2b06010401823702011b" : "spcFinancialCriteriaInfo", - "060a2b06010401823702011c" : "spcLink", - "060a2b06010401823702011d" : "SPC_HASH_INFO_OBJID", - "060a2b06010401823702011e" : "SPC_SIPINFO_OBJID", - "060a2b060104018237020104" : "spcIndirectDataContext", - "060a2b0601040182370202" : "CTL for Software Publishers Trusted CAs", - "060a2b060104018237020201" : "szOID_TRUSTED_CODESIGNING_CA_LIST", - "060a2b060104018237020202" : "szOID_TRUSTED_CLIENT_AUTH_CA_LIST", - "060a2b060104018237020203" : "szOID_TRUSTED_SERVER_AUTH_CA_LIST", - "060a2b06010401823714" : "Microsoft Enrollment Infrastructure", - "060a2b0601040182371401" : "szOID_AUTO_ENROLL_CTL_USAGE", - "060a2b0601040182371402" : "szOID_ENROLL_CERTTYPE_EXTENSION", - "060a2b060104018237140201" : "szOID_ENROLLMENT_AGENT", - "060a2b060104018237140202" : "szOID_KP_SMARTCARD_LOGON", - "060a2b060104018237140203" : "szOID_NT_PRINCIPAL_NAME", - "060a2b0601040182371403" : "szOID_CERT_MANIFOLD", - "06092b06010401823715" : "Microsoft CertSrv Infrastructure", - "06092b0601040182371501" : "szOID_CERTSRV_CA_VERSION", - "06092b0601040182371514" : "Client Information", - "060a2b06010401823719" : "Microsoft Directory Service", - "060a2b0601040182371901" : "szOID_NTDS_REPLICATION", - "060a2b06010401823703" : "Time Stamping", - "060a2b060104018237030201" : "SPC_TIME_STAMP_REQUEST_OBJID", - "060a2b0601040182371e" : "IIS", - "060a2b0601040182371f" : "Windows updates and service packs", - "060a2b0601040182371f01" : "szOID_PRODUCT_UPDATE", - "060a2b06010401823704" : "Permissions", - "060a2b06010401823728" : "Fonts", - "060a2b06010401823729" : "Microsoft Licensing and Registration", - "060a2b0601040182372a" : "Microsoft Corporate PKI (ITG)", - "060a2b06010401823758" : "CAPICOM", - "060a2b0601040182375801" : "szOID_CAPICOM_VERSION", - "060a2b0601040182375802" : "szOID_CAPICOM_ATTRIBUTE", - "060a2b060104018237580201" : "szOID_CAPICOM_DOCUMENT_NAME", - "060a2b060104018237580202" : "szOID_CAPICOM_DOCUMENT_DESCRIPTION", - "060a2b0601040182375803" : "szOID_CAPICOM_ENCRYPTED_DATA", - "060a2b060104018237580301" : "szOID_CAPICOM_ENCRYPTED_CONTENT", - "06032b0601050507" : "pkix", - "06032b060105050701" : "privateExtension", - "06032b06010505070101" : "authorityInfoAccess", - "06032b06010505070c02" : "CMC Data", - "06032b060105050702" : "policyQualifierIds", - // "06032b06010505070201" : "cps", - "06032b06010505070202" : "unotice", - "06032b060105050703" : "keyPurpose", - "06032b06010505070301" : "serverAuth", - "06032b06010505070302" : "clientAuth", - "06032b06010505070303" : "codeSigning", - "06032b06010505070304" : "emailProtection", - "06032b06010505070305" : "ipsecEndSystem", - "06032b06010505070306" : "ipsecTunnel", - "06032b06010505070307" : "ipsecUser", - "06032b06010505070308" : "timeStamping", - "06032b060105050704" : "cmpInformationTypes", - "06032b06010505070401" : "caProtEncCert", - "06032b06010505070402" : "signKeyPairTypes", - "06032b06010505070403" : "encKeyPairTypes", - "06032b06010505070404" : "preferredSymmAlg", - "06032b06010505070405" : "caKeyUpdateInfo", - "06032b06010505070406" : "currentCRL", - "06032b06010505073001" : "ocsp", - "06032b06010505073002" : "caIssuers", - "06032b06010505080101" : "HMAC-MD5", - "06032b06010505080102" : "HMAC-SHA", - "060360864801650201010a" : "mosaicKeyManagementAlgorithm", - "060360864801650201010b" : "sdnsKMandSigAlgorithm", - "060360864801650201010c" : "mosaicKMandSigAlgorithm", - "060360864801650201010d" : "SuiteASignatureAlgorithm", - "060360864801650201010e" : "SuiteAConfidentialityAlgorithm", - "060360864801650201010f" : "SuiteAIntegrityAlgorithm", - "06036086480186f84201" : "cert-extension", - // "06036086480186f8420101" : "netscape-cert-type", - "06036086480186f842010a" : "EntityLogo", - "06036086480186f842010b" : "UserPicture", - // "06036086480186f842010c" : "netscape-ssl-server-name", - // "06036086480186f842010d" : "netscape-comment", - // "06036086480186f8420102" : "netscape-base-url", - // "06036086480186f8420103" : "netscape-revocation-url", - // "06036086480186f8420104" : "netscape-ca-revocation-url", - // "06036086480186f8420107" : "netscape-cert-renewal-url", - // "06036086480186f8420108" : "netscape-ca-policy-url", - "06036086480186f8420109" : "HomePage-url", - "06036086480186f84202" : "data-type", - "06036086480186f8420201" : "GIF", - "06036086480186f8420202" : "JPEG", - "06036086480186f8420203" : "URL", - "06036086480186f8420204" : "HTML", - "06036086480186f8420205" : "netscape-cert-sequence", - "06036086480186f8420206" : "netscape-cert-url", - "06036086480186f84203" : "directory", - "06036086480186f8420401" : "serverGatedCrypto", - "06036086480186f845010603" : "Unknown Verisign extension", - "06036086480186f845010606" : "Unknown Verisign extension", - "06036086480186f84501070101" : "Verisign certificatePolicy", - "06036086480186f8450107010101" : "Unknown Verisign policy qualifier", - "06036086480186f8450107010102" : "Unknown Verisign policy qualifier", - "0603678105" : "TCPA", - "060367810501" : "tcpaSpecVersion", - "060367810502" : "tcpaAttribute", - "06036781050201" : "tcpaAtTpmManufacturer", - "0603678105020a" : "tcpaAtSecurityQualities", - "0603678105020b" : "tcpaAtTpmProtectionProfile", - "0603678105020c" : "tcpaAtTpmSecurityTarget", - "0603678105020d" : "tcpaAtFoundationProtectionProfile", - "0603678105020e" : "tcpaAtFoundationSecurityTarget", - "0603678105020f" : "tcpaAtTpmIdLabel", - "06036781050202" : "tcpaAtTpmModel", - "06036781050203" : "tcpaAtTpmVersion", - "06036781050204" : "tcpaAtPlatformManufacturer", - "06036781050205" : "tcpaAtPlatformModel", - "06036781050206" : "tcpaAtPlatformVersion", - "06036781050207" : "tcpaAtComponentManufacturer", - "06036781050208" : "tcpaAtComponentModel", - "06036781050209" : "tcpaAtComponentVersion", - "060367810503" : "tcpaProtocol", - "06036781050301" : "tcpaPrttTpmIdProtocol", - "0603672a00" : "contentType", - "0603672a0000" : "PANData", - "0603672a0001" : "PANToken", - "0603672a0002" : "PANOnly", - "0603672a01" : "msgExt", - "0603672a0a" : "national", - "0603672a0a8140" : "Japan", - "0603672a02" : "field", - "0603672a0200" : "fullName", - "0603672a0201" : "givenName", - "0603672a020a" : "amount", - "0603672a0202" : "familyName", - "0603672a0203" : "birthFamilyName", - "0603672a0204" : "placeName", - "0603672a0205" : "identificationNumber", - "0603672a0206" : "month", - "0603672a0207" : "date", - "0603672a02070b" : "accountNumber", - "0603672a02070c" : "passPhrase", - "0603672a0208" : "address", - "0603672a0209" : "telephone", - "0603672a03" : "attribute", - "0603672a0300" : "cert", - "0603672a030000" : "rootKeyThumb", - "0603672a030001" : "additionalPolicy", - "0603672a04" : "algorithm", - "0603672a05" : "policy", - "0603672a0500" : "root", - "0603672a06" : "module", - "0603672a07" : "certExt", - "0603672a0700" : "hashedRootKey", - "0603672a0701" : "certificateType", - "0603672a0702" : "merchantData", - "0603672a0703" : "cardCertRequired", - "0603672a0704" : "tunneling", - "0603672a0705" : "setExtensions", - "0603672a0706" : "setQualifier", - "0603672a08" : "brand", - "0603672a0801" : "IATA-ATA", - "0603672a081e" : "Diners", - "0603672a0822" : "AmericanExpress", - "0603672a0804" : "VISA", - "0603672a0805" : "MasterCard", - "0603672a08ae7b" : "Novus", - "0603672a09" : "vendor", - "0603672a0900" : "GlobeSet", - "0603672a0901" : "IBM", - "0603672a090a" : "Griffin", - "0603672a090b" : "Certicom", - "0603672a090c" : "OSS", - "0603672a090d" : "TenthMountain", - "0603672a090e" : "Antares", - "0603672a090f" : "ECC", - "0603672a0910" : "Maithean", - "0603672a0911" : "Netscape", - "0603672a0912" : "Verisign", - "0603672a0913" : "BlueMoney", - "0603672a0902" : "CyberCash", - "0603672a0914" : "Lacerte", - "0603672a0915" : "Fujitsu", - "0603672a0916" : "eLab", - "0603672a0917" : "Entrust", - "0603672a0918" : "VIAnet", - "0603672a0919" : "III", - "0603672a091a" : "OpenMarket", - "0603672a091b" : "Lexem", - "0603672a091c" : "Intertrader", - "0603672a091d" : "Persimmon", - "0603672a0903" : "Terisa", - "0603672a091e" : "NABLE", - "0603672a091f" : "espace-net", - "0603672a0920" : "Hitachi", - "0603672a0921" : "Microsoft", - "0603672a0922" : "NEC", - "0603672a0923" : "Mitsubishi", - "0603672a0924" : "NCR", - "0603672a0925" : "e-COMM", - "0603672a0926" : "Gemplus", - "0603672a0904" : "RSADSI", - "0603672a0905" : "VeriFone", - "0603672a0906" : "TrinTech", - "0603672a0907" : "BankGate", - "0603672a0908" : "GTE", - "0603672a0909" : "CompuSource", - "0603551d01" : "authorityKeyIdentifier", - "0603551d0a" : "basicConstraints", - "0603551d0b" : "nameConstraints", - "0603551d0c" : "policyConstraints", - "0603551d0d" : "basicConstraints", - "0603551d0e" : "subjectKeyIdentifier", - "0603551d0f" : "keyUsage", - "0603551d10" : "privateKeyUsagePeriod", - "0603551d11" : "subjectAltName", - "0603551d12" : "issuerAltName", - "0603551d13" : "basicConstraints", - "0603551d02" : "keyAttributes", - "0603551d14" : "cRLNumber", - "0603551d15" : "cRLReason", - "0603551d16" : "expirationDate", - "0603551d17" : "instructionCode", - "0603551d18" : "invalidityDate", - "0603551d1a" : "issuingDistributionPoint", - "0603551d1b" : "deltaCRLIndicator", - "0603551d1c" : "issuingDistributionPoint", - "0603551d1d" : "certificateIssuer", - "0603551d03" : "certificatePolicies", - "0603551d1e" : "nameConstraints", - "0603551d1f" : "cRLDistributionPoints", - "0603551d20" : "certificatePolicies", - "0603551d21" : "policyMappings", - "0603551d22" : "policyConstraints", - "0603551d23" : "authorityKeyIdentifier", - "0603551d24" : "policyConstraints", - "0603551d25" : "extKeyUsage", - "0603551d04" : "keyUsageRestriction", - "0603551d05" : "policyMapping", - "0603551d06" : "subtreesConstraint", - "0603551d07" : "subjectAltName", - "0603551d08" : "issuerAltName", - "0603551d09" : "subjectDirectoryAttributes", - "0603550400" : "objectClass", - "0603550401" : "aliasObjectName", - // "060355040c" : "title", - "060355040d" : "description", - "060355040e" : "searchGuide", - "060355040f" : "businessCategory", - "0603550410" : "postalAddress", - "0603550411" : "postalCode", - "0603550412" : "postOfficeBox", - "0603550413" : "physicalDeliveryOfficeName", - "0603550402" : "knowledgeInformation", - // "0603550414" : "telephoneNumber", - "0603550415" : "telexNumber", - "0603550416" : "teletexTerminalIdentifier", - "0603550417" : "facsimileTelephoneNumber", - "0603550418" : "x121Address", - "0603550419" : "internationalISDNNumber", - "060355041a" : "registeredAddress", - "060355041b" : "destinationIndicator", - "060355041c" : "preferredDeliveryMehtod", - "060355041d" : "presentationAddress", - "060355041e" : "supportedApplicationContext", - "060355041f" : "member", - "0603550420" : "owner", - "0603550421" : "roleOccupant", - "0603550422" : "seeAlso", - "0603550423" : "userPassword", - "0603550424" : "userCertificate", - "0603550425" : "caCertificate", - "0603550426" : "authorityRevocationList", - "0603550427" : "certificateRevocationList", - "0603550428" : "crossCertificatePair", - "0603550429" : "givenName", - // "060355042a" : "givenName", - "0603550405" : "serialNumber", - "0603550434" : "supportedAlgorithms", - "0603550435" : "deltaRevocationList", - "060355043a" : "crossCertificatePair", - // "0603550409" : "streetAddress", - "06035508" : "X.500-Algorithms", - "0603550801" : "X.500-Alg-Encryption", - "060355080101" : "rsa", - "0603604c0101" : "DPC" -}; diff --git a/src/core/operations/Punycode.js b/src/core/operations/Punycode.js deleted file mode 100755 index d3f94e69..00000000 --- a/src/core/operations/Punycode.js +++ /dev/null @@ -1,58 +0,0 @@ -import punycode from "punycode"; - - -/** - * Punycode operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Punycode = { - - /** - * @constant - * @default - */ - IDN: false, - - /** - * To Punycode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToAscii: function(input, args) { - const idn = args[0]; - - if (idn) { - return punycode.toASCII(input); - } else { - return punycode.encode(input); - } - }, - - - /** - * From Punycode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToUnicode: function(input, args) { - const idn = args[0]; - - if (idn) { - return punycode.toUnicode(input); - } else { - return punycode.decode(input); - } - }, - -}; - -export default Punycode; diff --git a/src/core/operations/RC2Decrypt.mjs b/src/core/operations/RC2Decrypt.mjs new file mode 100644 index 00000000..b0302cd7 --- /dev/null +++ b/src/core/operations/RC2Decrypt.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * RC2 Decrypt operation + */ +class RC2Decrypt extends Operation { + + /** + * RC2Decrypt constructor + */ + constructor() { + super(); + + this.name = "RC2 Decrypt"; + this.module = "Ciphers"; + this.description = "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.

Key: RC2 uses a variable size key.

IV: To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.

Padding: In both CBC and ECB mode, PKCS#7 padding will be used."; + this.infoURL = "https://wikipedia.org/wiki/RC2"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + [,, inputType, outputType] = args, + decipher = forge.rc2.createDecryptionCipher(key); + + input = Utils.convertToByteString(input, inputType); + + decipher.start(iv || null); + decipher.update(forge.util.createBuffer(input)); + decipher.finish(); + + return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); + } + +} + +export default RC2Decrypt; diff --git a/src/core/operations/RC2Encrypt.mjs b/src/core/operations/RC2Encrypt.mjs new file mode 100644 index 00000000..5c59c1ea --- /dev/null +++ b/src/core/operations/RC2Encrypt.mjs @@ -0,0 +1,77 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import forge from "node-forge/dist/forge.min.js"; + + +/** + * RC2 Encrypt operation + */ +class RC2Encrypt extends Operation { + + /** + * RC2Encrypt constructor + */ + constructor() { + super(); + + this.name = "RC2 Encrypt"; + this.module = "Ciphers"; + this.description = "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.

Key: RC2 uses a variable size key.

You can generate a password-based key using one of the KDF operations.

IV: To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.

Padding: In both CBC and ECB mode, PKCS#7 padding will be used."; + this.infoURL = "https://wikipedia.org/wiki/RC2"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + [,, inputType, outputType] = args, + cipher = forge.rc2.createEncryptionCipher(key); + + input = Utils.convertToByteString(input, inputType); + + cipher.start(iv || null); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); + } + +} + +export default RC2Encrypt; diff --git a/src/core/operations/RC4.mjs b/src/core/operations/RC4.mjs new file mode 100644 index 00000000..1446c972 --- /dev/null +++ b/src/core/operations/RC4.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import CryptoJS from "crypto-js"; +import { format } from "../lib/Ciphers"; + +/** + * RC4 operation + */ +class RC4 extends Operation { + + /** + * RC4 constructor + */ + constructor() { + super(); + + this.name = "RC4"; + this.module = "Ciphers"; + this.description = "RC4 (also known as ARC4) is a widely-used stream cipher designed by Ron Rivest. It is used in popular protocols such as SSL and WEP. Although remarkable for its simplicity and speed, the algorithm's history doesn't inspire confidence in its security."; + this.infoURL = "https://wikipedia.org/wiki/RC4"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Passphrase", + "type": "toggleString", + "value": "", + "toggleValues": ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"] + }, + { + "name": "Input format", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] + }, + { + "name": "Output format", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const message = format[args[1]].parse(input), + passphrase = format[args[0].option].parse(args[0].string), + encrypted = CryptoJS.RC4.encrypt(message, passphrase); + + return encrypted.ciphertext.toString(format[args[2]]); + } + + /** + * Highlight RC4 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight RC4 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default RC4; diff --git a/src/core/operations/RC4Drop.mjs b/src/core/operations/RC4Drop.mjs new file mode 100644 index 00000000..243d3a7a --- /dev/null +++ b/src/core/operations/RC4Drop.mjs @@ -0,0 +1,95 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { format } from "../lib/Ciphers"; +import CryptoJS from "crypto-js"; + +/** + * RC4 Drop operation + */ +class RC4Drop extends Operation { + + /** + * RC4Drop constructor + */ + constructor() { + super(); + + this.name = "RC4 Drop"; + this.module = "Ciphers"; + this.description = "It was discovered that the first few bytes of the RC4 keystream are strongly non-random and leak information about the key. We can defend against this attack by discarding the initial portion of the keystream. This modified algorithm is traditionally called RC4-drop."; + this.infoURL = "https://wikipedia.org/wiki/RC4#Fluhrer,_Mantin_and_Shamir_attack"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Passphrase", + "type": "toggleString", + "value": "", + "toggleValues": ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"] + }, + { + "name": "Input format", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] + }, + { + "name": "Output format", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] + }, + { + "name": "Number of bytes to drop", + "type": "number", + "value": 768 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const message = format[args[1]].parse(input), + passphrase = format[args[0].option].parse(args[0].string), + drop = args[3], + encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop }); + + return encrypted.ciphertext.toString(format[args[2]]); + } + + /** + * Highlight RC4 Drop + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight RC4 Drop in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default RC4Drop; diff --git a/src/core/operations/RIPEMD.mjs b/src/core/operations/RIPEMD.mjs new file mode 100644 index 00000000..00b613de --- /dev/null +++ b/src/core/operations/RIPEMD.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * RIPEMD operation + */ +class RIPEMD extends Operation { + + /** + * RIPEMD constructor + */ + constructor() { + super(); + + this.name = "RIPEMD"; + this.module = "Crypto"; + this.description = "RIPEMD (RACE Integrity Primitives Evaluation Message Digest) is a family of cryptographic hash functions developed in Leuven, Belgium, by Hans Dobbertin, Antoon Bosselaers and Bart Preneel at the COSIC research group at the Katholieke Universiteit Leuven, and first published in 1996.

RIPEMD was based upon the design principles used in MD4, and is similar in performance to the more popular SHA-1.

"; + this.infoURL = "https://wikipedia.org/wiki/RIPEMD"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Size", + "type": "option", + "value": ["320", "256", "160", "128"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = args[0]; + return runHash("ripemd" + size, input); + } + +} + +export default RIPEMD; diff --git a/src/core/operations/ROT13.mjs b/src/core/operations/ROT13.mjs new file mode 100644 index 00000000..f1dda5a8 --- /dev/null +++ b/src/core/operations/ROT13.mjs @@ -0,0 +1,104 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + + +/** + * ROT13 operation. + */ +class ROT13 extends Operation { + + /** + * ROT13 constructor + */ + constructor() { + super(); + + this.name = "ROT13"; + this.module = "Default"; + this.description = "A simple caesar substitution cipher which rotates alphabet characters by the specified amount (default 13)."; + this.infoURL = "https://wikipedia.org/wiki/ROT13"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Rotate lower case chars", + type: "boolean", + value: true + }, + { + name: "Rotate upper case chars", + type: "boolean", + value: true + }, + { + name: "Amount", + type: "number", + value: 13 + }, + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = input, + rot13Lowercase = args[0], + rot13Upperacse = args[1]; + let amount = args[2], + chr; + + if (amount) { + if (amount < 0) { + amount = 26 - (Math.abs(amount) % 26); + } + + for (let i = 0; i < input.length; i++) { + chr = input[i]; + if (rot13Upperacse && chr >= 65 && chr <= 90) { // Upper case + chr = (chr - 65 + amount) % 26; + output[i] = chr + 65; + } else if (rot13Lowercase && chr >= 97 && chr <= 122) { // Lower case + chr = (chr - 97 + amount) % 26; + output[i] = chr + 97; + } + } + } + return output; + } + + /** + * Highlight ROT13 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight ROT13 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } +} + +export default ROT13; diff --git a/src/core/operations/ROT47.mjs b/src/core/operations/ROT47.mjs new file mode 100644 index 00000000..1219b349 --- /dev/null +++ b/src/core/operations/ROT47.mjs @@ -0,0 +1,89 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + + +/** + * ROT47 operation. + */ +class ROT47 extends Operation { + + /** + * ROT47 constructor + */ + constructor() { + super(); + + this.name = "ROT47"; + this.module = "Default"; + this.description = "A slightly more complex variation of a caesar cipher, which includes ASCII characters from 33 '!' to 126 '~'. Default rotation: 47."; + this.infoURL = "https://wikipedia.org/wiki/ROT13#Variants"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Amount", + type: "number", + value: 47 + }, + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = input; + let amount = args[0], + chr; + + if (amount) { + if (amount < 0) { + amount = 94 - (Math.abs(amount) % 94); + } + + for (let i = 0; i < input.length; i++) { + chr = input[i]; + if (chr >= 33 && chr <= 126) { + chr = (chr - 33 + amount) % 94; + output[i] = chr + 33; + } + } + } + return output; + } + + /** + * Highlight ROT47 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight ROT47 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } +} + +export default ROT47; diff --git a/src/core/operations/RawDeflate.mjs b/src/core/operations/RawDeflate.mjs new file mode 100644 index 00000000..c1d6dd53 --- /dev/null +++ b/src/core/operations/RawDeflate.mjs @@ -0,0 +1,59 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {COMPRESSION_TYPE} from "../lib/Zlib"; +import rawdeflate from "zlibjs/bin/rawdeflate.min"; + +const Zlib = rawdeflate.Zlib; + +const RAW_COMPRESSION_TYPE_LOOKUP = { + "Fixed Huffman Coding": Zlib.RawDeflate.CompressionType.FIXED, + "Dynamic Huffman Coding": Zlib.RawDeflate.CompressionType.DYNAMIC, + "None (Store)": Zlib.RawDeflate.CompressionType.NONE, +}; + +/** + * Raw Deflate operation + */ +class RawDeflate extends Operation { + + /** + * RawDeflate constructor + */ + constructor() { + super(); + + this.name = "Raw Deflate"; + this.module = "Compression"; + this.description = "Compresses data using the deflate algorithm with no headers."; + this.infoURL = "https://wikipedia.org/wiki/DEFLATE"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Compression type", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const deflate = new Zlib.RawDeflate(new Uint8Array(input), { + compressionType: RAW_COMPRESSION_TYPE_LOOKUP[args[0]] + }); + return new Uint8Array(deflate.compress()).buffer; + } + +} + +export default RawDeflate; diff --git a/src/core/operations/RawInflate.mjs b/src/core/operations/RawInflate.mjs new file mode 100644 index 00000000..99066d79 --- /dev/null +++ b/src/core/operations/RawInflate.mjs @@ -0,0 +1,104 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {INFLATE_BUFFER_TYPE} from "../lib/Zlib"; +import rawinflate from "zlibjs/bin/rawinflate.min"; +import OperationError from "../errors/OperationError"; + +const Zlib = rawinflate.Zlib; + +const RAW_BUFFER_TYPE_LOOKUP = { + "Adaptive": Zlib.RawInflate.BufferType.ADAPTIVE, + "Block": Zlib.RawInflate.BufferType.BLOCK, +}; + +/** + * Raw Inflate operation + */ +class RawInflate extends Operation { + + /** + * RawInflate constructor + */ + constructor() { + super(); + + this.name = "Raw Inflate"; + this.module = "Compression"; + this.description = "Decompresses data which has been compressed using the deflate algorithm with no headers."; + this.infoURL = "https://wikipedia.org/wiki/DEFLATE"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Start index", + type: "number", + value: 0 + }, + { + name: "Initial output buffer size", + type: "number", + value: 0 + }, + { + name: "Buffer expansion type", + type: "option", + value: INFLATE_BUFFER_TYPE + }, + { + name: "Resize buffer after decompression", + type: "boolean", + value: false + }, + { + name: "Verify result", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inflate = new Zlib.RawInflate(new Uint8Array(input), { + index: args[0], + bufferSize: args[1], + bufferType: RAW_BUFFER_TYPE_LOOKUP[args[2]], + resize: args[3], + verify: args[4] + }), + result = new Uint8Array(inflate.decompress()); + + // Raw Inflate somethimes messes up and returns nonsense like this: + // ]....]....]....]....]....]....]....]....]....]....]....]....]....]... + // e.g. Input data of [8b, 1d, dc, 44] + // Look for the first two square brackets: + if (result.length > 158 && result[0] === 93 && result[5] === 93) { + // If the first two square brackets are there, check that the others + // are also there. If they are, throw an error. If not, continue. + let valid = false; + for (let i = 0; i < 155; i += 5) { + if (result[i] !== 93) { + valid = true; + } + } + + if (!valid) { + throw new OperationError("Error: Unable to inflate data"); + } + } + // This seems to be the easiest way... + return result.buffer; + } + +} + +export default RawInflate; diff --git a/src/core/operations/Register.mjs b/src/core/operations/Register.mjs new file mode 100644 index 00000000..3b0d7479 --- /dev/null +++ b/src/core/operations/Register.mjs @@ -0,0 +1,119 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Dish from "../Dish"; +import XRegExp from "xregexp"; + +/** + * Register operation + */ +class Register extends Operation { + + /** + * Register constructor + */ + constructor() { + super(); + + this.name = "Register"; + this.flowControl = true; + this.module = "Regex"; + this.description = "Extract data from the input and store it in registers which can then be passed into subsequent operations as arguments. Regular expression capture groups are used to select the data to extract.

To use registers in arguments, refer to them using the notation $Rn where n is the register number, starting at 0.

For example:
Input: Test
Extractor: (.*)
Argument: $R0 becomes Test

Registers can be escaped in arguments using a backslash. e.g. \\$R0 would become $R0 rather than Test."; + this.infoURL = "https://wikipedia.org/wiki/Regular_expression#Syntax"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Extractor", + "type": "binaryString", + "value": "([\\s\\S]*)" + }, + { + "name": "Case insensitive", + "type": "boolean", + "value": true + }, + { + "name": "Multiline matching", + "type": "boolean", + "value": false + }, + { + "name": "Dot matches all", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + async run(state) { + const ings = state.opList[state.progress].ingValues; + const [extractorStr, i, m, s] = ings; + + let modifiers = ""; + if (i) modifiers += "i"; + if (m) modifiers += "m"; + if (s) modifiers += "s"; + + const extractor = new XRegExp(extractorStr, modifiers), + input = await state.dish.get(Dish.STRING), + registers = input.match(extractor); + + if (!registers) return state; + + if (ENVIRONMENT_IS_WORKER()) { + self.setRegisters(state.forkOffset + state.progress, state.numRegisters, registers.slice(1)); + } + + /** + * Replaces references to registers (e.g. $R0) with the contents of those registers. + * + * @param {string} str + * @returns {string} + */ + function replaceRegister(str) { + // Replace references to registers ($Rn) with contents of registers + return str.replace(/(\\*)\$R(\d{1,2})/g, (match, slashes, regNum) => { + const index = parseInt(regNum, 10) + 1; + if (index <= state.numRegisters || index >= state.numRegisters + registers.length) + return match; + if (slashes.length % 2 !== 0) return match.slice(1); // Remove escape + return slashes + registers[index - state.numRegisters]; + }); + } + + // Step through all subsequent ops and replace registers in args with extracted content + for (let i = state.progress + 1; i < state.opList.length; i++) { + if (state.opList[i].disabled) continue; + + let args = state.opList[i].ingValues; + args = args.map(arg => { + if (typeof arg !== "string" && typeof arg !== "object") return arg; + + if (typeof arg === "object" && arg.hasOwnProperty("string")) { + arg.string = replaceRegister(arg.string); + return arg; + } + return replaceRegister(arg); + }); + state.opList[i].ingValues = args; + } + + state.numRegisters += registers.length - 1; + return state; + } + +} + +export default Register; diff --git a/src/core/operations/RegularExpression.mjs b/src/core/operations/RegularExpression.mjs new file mode 100644 index 00000000..d8411683 --- /dev/null +++ b/src/core/operations/RegularExpression.mjs @@ -0,0 +1,261 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import XRegExp from "xregexp"; +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; + +/** + * Regular expression operation + */ +class RegularExpression extends Operation { + + /** + * RegularExpression constructor + */ + constructor() { + super(); + + this.name = "Regular expression"; + this.module = "Regex"; + this.description = "Define your own regular expression (regex) to search the input data with, optionally choosing from a list of pre-defined patterns.

Supports extended regex syntax including the 'dot matches all' flag, named capture groups, full unicode coverage (including \\p{} categories and scripts as well as astral codes) and recursive matching."; + this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Built in regexes", + "type": "populateOption", + "value": [ + { + name: "User defined", + value: "" + }, + { + name: "IPv4 address", + value: "(?:(?:\\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})?" + }, + { + name: "IPv6 address", + value: "((?=.*::)(?!.*::.+::)(::)?([\\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})" + }, + { + name: "Email address", + value: "\\b(\\w[-.\\w]*)@([-\\w]+(?:\\.[-\\w]+)*)\\.([A-Za-z]{2,4})\\b" + }, + { + name: "URL", + value: "([A-Za-z]+://)([-\\w]+(?:\\.\\w[-\\w]*)+)(:\\d+)?(/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*)?" + }, + { + name: "Domain", + value: "\\b((?=[a-z0-9-]{1,63}\\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,63}\\b" + }, + { + name: "Windows file path", + value: "([A-Za-z]):\\\\((?:[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)~]{0,61}\\\\?)*[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)]{0,61})(\\.[A-Za-z\\d]{1,6})?" + }, + { + name: "UNIX file path", + value: "(?:/[A-Za-z\\d.][A-Za-z\\d\\-.]{0,61})+" + }, + { + name: "MAC address", + value: "[A-Fa-f\\d]{2}(?:[:-][A-Fa-f\\d]{2}){5}" + }, + { + name: "Date (yyyy-mm-dd)", + value: "((?:19|20)\\d\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])" + }, + { + name: "Date (dd/mm/yyyy)", + value: "(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.]((?:19|20)\\d\\d)" + }, + { + name: "Date (mm/dd/yyyy)", + value: "(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.]((?:19|20)\\d\\d)" + }, + { + name: "Strings", + value: "[A-Za-z\\d/\\-:.,_$%\\x27\"()<>= !\\[\\]{}@]{4,}" + }, + ], + "target": 1 + }, + { + "name": "Regex", + "type": "text", + "value": "" + }, + { + "name": "Case insensitive", + "type": "boolean", + "value": true + }, + { + "name": "^ and $ match at newlines", + "type": "boolean", + "value": true + }, + { + "name": "Dot matches all", + "type": "boolean", + "value": false + }, + { + "name": "Unicode support", + "type": "boolean", + "value": false + }, + { + "name": "Astral support", + "type": "boolean", + "value": false + }, + { + "name": "Display total", + "type": "boolean", + "value": false + }, + { + "name": "Output format", + "type": "option", + "value": ["Highlight matches", "List matches", "List capture groups", "List matches with capture groups"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [, + userRegex, + i, m, s, u, a, + displayTotal, + outputFormat + ] = args; + let modifiers = "g"; + + if (i) modifiers += "i"; + if (m) modifiers += "m"; + if (s) modifiers += "s"; + if (u) modifiers += "u"; + if (a) modifiers += "A"; + + if (userRegex && userRegex !== "^" && userRegex !== "$") { + try { + const regex = new XRegExp(userRegex, modifiers); + + switch (outputFormat) { + case "Highlight matches": + return regexHighlight(input, regex, displayTotal); + case "List matches": + return Utils.escapeHtml(regexList(input, regex, displayTotal, true, false)); + case "List capture groups": + return Utils.escapeHtml(regexList(input, regex, displayTotal, false, true)); + case "List matches with capture groups": + return Utils.escapeHtml(regexList(input, regex, displayTotal, true, true)); + default: + return "Error: Invalid output format"; + } + } catch (err) { + throw new OperationError("Invalid regex. Details: " + err.message); + } + } else { + return Utils.escapeHtml(input); + } + } + +} + +/** + * Creates a string listing the matches within a string. + * + * @param {string} input + * @param {RegExp} regex + * @param {boolean} displayTotal + * @param {boolean} matches - Display full match + * @param {boolean} captureGroups - Display each of the capture groups separately + * @returns {string} + */ +function regexList (input, regex, displayTotal, matches, captureGroups) { + let output = "", + total = 0, + match; + + while ((match = regex.exec(input))) { + // Moves pointer when an empty string is matched (prevents infinite loop) + if (match.index === regex.lastIndex) { + regex.lastIndex++; + } + + total++; + if (matches) { + output += match[0] + "\n"; + } + if (captureGroups) { + for (let i = 1; i < match.length; i++) { + if (matches) { + output += " Group " + i + ": "; + } + output += match[i] + "\n"; + } + } + } + + if (displayTotal) + output = "Total found: " + total + "\n\n" + output; + + return output.slice(0, -1); +} + +/** + * Adds HTML highlights to matches within a string. + * + * @private + * @param {string} input + * @param {RegExp} regex + * @param {boolean} displayTotal + * @returns {string} + */ +function regexHighlight (input, regex, displayTotal) { + let output = "", + title = "", + hl = 1, + total = 0; + + output = input.replace(regex, (match, ...args) => { + args.pop(); // Throw away full string + const offset = args.pop(), + groups = args; + + title = `Offset: ${offset}\n`; + if (groups.length) { + title += "Groups:\n"; + for (let i = 0; i < groups.length; i++) { + title += `\t${i+1}: ${Utils.escapeHtml(groups[i] || "")}\n`; + } + } + + // Switch highlight + hl = hl === 1 ? 2 : 1; + + total++; + + return `${Utils.escapeHtml(match)}`; + }); + + if (displayTotal) + output = "Total found: " + total + "\n\n" + output; + + return output; +} + +export default RegularExpression; diff --git a/src/core/operations/RemoveDiacritics.mjs b/src/core/operations/RemoveDiacritics.mjs new file mode 100644 index 00000000..217fafe1 --- /dev/null +++ b/src/core/operations/RemoveDiacritics.mjs @@ -0,0 +1,41 @@ +/** + * @author Klaxon [klaxon@veyr.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Remove Diacritics operation + */ +class RemoveDiacritics extends Operation { + + /** + * RemoveDiacritics constructor + */ + constructor() { + super(); + + this.name = "Remove Diacritics"; + this.module = "Default"; + this.description = "Replaces accented characters with their latin character equivalent."; + this.infoURL = "https://wikipedia.org/wiki/Diacritic"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + // reference: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463 + return input.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + } + +} + +export default RemoveDiacritics; diff --git a/src/core/operations/RemoveEXIF.mjs b/src/core/operations/RemoveEXIF.mjs new file mode 100644 index 00000000..9c5cf49f --- /dev/null +++ b/src/core/operations/RemoveEXIF.mjs @@ -0,0 +1,55 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import { removeEXIF } from "../vendor/remove-exif"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Remove EXIF operation + */ +class RemoveEXIF extends Operation { + + /** + * RemoveEXIF constructor + */ + constructor() { + super(); + + this.name = "Remove EXIF"; + this.module = "Image"; + this.description = [ + "Removes EXIF data from a JPEG image.", + "

", + "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.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + // Do nothing if input is empty + if (input.length === 0) return input; + + try { + return removeEXIF(input); + } catch (err) { + // Simply return input if no EXIF data is found + if (err === "Exif not found.") return input; + throw new OperationError(`Could not remove EXIF data from image: ${err}`); + } + } + +} + +export default RemoveEXIF; diff --git a/src/core/operations/RemoveLineNumbers.mjs b/src/core/operations/RemoveLineNumbers.mjs new file mode 100644 index 00000000..d7c615f7 --- /dev/null +++ b/src/core/operations/RemoveLineNumbers.mjs @@ -0,0 +1,39 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Remove line numbers operation + */ +class RemoveLineNumbers extends Operation { + + /** + * RemoveLineNumbers constructor + */ + constructor() { + super(); + + this.name = "Remove line numbers"; + this.module = "Default"; + this.description = "Removes line numbers from the output if they can be trivially detected."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.replace(/^[ \t]{0,5}\d+[\s:|\-,.)\]]/gm, ""); + } + +} + +export default RemoveLineNumbers; diff --git a/src/core/operations/RemoveNullBytes.mjs b/src/core/operations/RemoveNullBytes.mjs new file mode 100644 index 00000000..dcbf9251 --- /dev/null +++ b/src/core/operations/RemoveNullBytes.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Remove null bytes operation + */ +class RemoveNullBytes extends Operation { + + /** + * RemoveNullBytes constructor + */ + constructor() { + super(); + + this.name = "Remove null bytes"; + this.module = "Default"; + this.description = "Removes all null bytes (0x00) from the input."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = []; + for (let i = 0; i < input.length; i++) { + if (input[i] !== 0) output.push(input[i]); + } + return output; + } + +} + +export default RemoveNullBytes; diff --git a/src/core/operations/RemoveWhitespace.mjs b/src/core/operations/RemoveWhitespace.mjs new file mode 100644 index 00000000..a6564809 --- /dev/null +++ b/src/core/operations/RemoveWhitespace.mjs @@ -0,0 +1,86 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Remove whitespace operation + */ +class RemoveWhitespace extends Operation { + + /** + * RemoveWhitespace constructor + */ + constructor() { + super(); + + this.name = "Remove whitespace"; + this.module = "Default"; + this.description = "Optionally removes all spaces, carriage returns, line feeds, tabs and form feeds from the input data.

This operation also supports the removal of full stops which are sometimes used to represent non-printable bytes in ASCII output."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Spaces", + "type": "boolean", + "value": true + }, + { + "name": "Carriage returns (\\r)", + "type": "boolean", + "value": true + }, + { + "name": "Line feeds (\\n)", + "type": "boolean", + "value": true + }, + { + "name": "Tabs", + "type": "boolean", + "value": true + }, + { + "name": "Form feeds (\\f)", + "type": "boolean", + "value": true + }, + { + "name": "Full stops", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [ + removeSpaces, + removeCariageReturns, + removeLineFeeds, + removeTabs, + removeFormFeeds, + removeFullStops + ] = args; + let data = input; + + if (removeSpaces) data = data.replace(/ /g, ""); + if (removeCariageReturns) data = data.replace(/\r/g, ""); + if (removeLineFeeds) data = data.replace(/\n/g, ""); + if (removeTabs) data = data.replace(/\t/g, ""); + if (removeFormFeeds) data = data.replace(/\f/g, ""); + if (removeFullStops) data = data.replace(/\./g, ""); + return data; + } + +} + +export default RemoveWhitespace; diff --git a/src/core/operations/RenderImage.mjs b/src/core/operations/RenderImage.mjs new file mode 100644 index 00000000..7edd2072 --- /dev/null +++ b/src/core/operations/RenderImage.mjs @@ -0,0 +1,110 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import { fromBase64, toBase64 } from "../lib/Base64"; +import { fromHex } from "../lib/Hex"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; +import Magic from "../lib/Magic"; + +/** + * Render Image operation + */ +class RenderImage extends Operation { + + /** + * RenderImage constructor + */ + constructor() { + super(); + + this.name = "Render Image"; + this.module = "Image"; + this.description = "Displays the input as an image. Supports the following formats:

  • jpg/jpeg
  • png
  • gif
  • webp
  • bmp
  • ico
"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.presentType = "html"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["Raw", "Base64", "Hex"] + } + ]; + this.patterns = [ + { + "match": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)", + "flags": "", + "args": ["Raw"], + "useful": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const inputFormat = args[0]; + + if (!input.length) return []; + + // Convert input to raw bytes + switch (inputFormat) { + case "Hex": + input = fromHex(input); + break; + case "Base64": + // Don't trust the Base64 entered by the user. + // Unwrap it first, then re-encode later. + input = fromBase64(input, undefined, "byteArray"); + break; + case "Raw": + default: + input = Utils.strToByteArray(input); + break; + } + + // Determine file type + const type = Magic.magicFileType(input); + if (!(type && type.mime.indexOf("image") === 0)) { + throw new OperationError("Invalid file type"); + } + + return input; + } + + /** + * Displays the image using HTML for web apps. + * + * @param {byteArray} data + * @returns {html} + */ + async present(data) { + if (!data.length) return ""; + + let dataURI = "data:"; + + // Determine file type + const type = Magic.magicFileType(data); + if (type && type.mime.indexOf("image") === 0) { + dataURI += type.mime + ";"; + } else { + throw new OperationError("Invalid file type"); + } + + // Add image data to URI + dataURI += "base64," + toBase64(data); + + return ""; + } + +} + +export default RenderImage; diff --git a/src/core/operations/Return.mjs b/src/core/operations/Return.mjs new file mode 100644 index 00000000..cc83bff8 --- /dev/null +++ b/src/core/operations/Return.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Return operation + */ +class Return extends Operation { + + /** + * Return constructor + */ + constructor() { + super(); + + this.name = "Return"; + this.flowControl = true; + this.module = "Default"; + this.description = "End execution of operations at this point in the recipe."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + run(state) { + state.progress = state.opList.length; + return state; + } + +} + +export default Return; diff --git a/src/core/operations/Reverse.mjs b/src/core/operations/Reverse.mjs new file mode 100644 index 00000000..c88bb275 --- /dev/null +++ b/src/core/operations/Reverse.mjs @@ -0,0 +1,67 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Reverse operation + */ +class Reverse extends Operation { + + /** + * Reverse constructor + */ + constructor() { + super(); + + this.name = "Reverse"; + this.module = "Default"; + this.description = "Reverses the input string."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "By", + "type": "option", + "value": ["Character", "Line"] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + let i; + if (args[0] === "Line") { + const lines = []; + let line = [], + result = []; + for (i = 0; i < input.length; i++) { + if (input[i] === 0x0a) { + lines.push(line); + line = []; + } else { + line.push(input[i]); + } + } + lines.push(line); + lines.reverse(); + for (i = 0; i < lines.length; i++) { + result = result.concat(lines[i]); + result.push(0x0a); + } + return result.slice(0, input.length); + } else { + return input.reverse(); + } + } + +} + +export default Reverse; diff --git a/src/core/operations/Rotate.js b/src/core/operations/Rotate.js deleted file mode 100755 index f046d1fa..00000000 --- a/src/core/operations/Rotate.js +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Bit rotation operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - * - * @todo Support for UTF16 - */ -const Rotate = { - - /** - * @constant - * @default - */ - ROTATE_AMOUNT: 1, - /** - * @constant - * @default - */ - ROTATE_WHOLE: false, - - /** - * Runs rotation operations across the input data. - * - * @private - * @param {byteArray} data - * @param {number} amount - * @param {function} algo - The rotation operation to carry out - * @returns {byteArray} - */ - _rot: function(data, amount, algo) { - const result = []; - for (let i = 0; i < data.length; i++) { - let b = data[i]; - for (let j = 0; j < amount; j++) { - b = algo(b); - } - result.push(b); - } - return result; - }, - - - /** - * Rotate right operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRotr: function(input, args) { - if (args[1]) { - return Rotate._rotrWhole(input, args[0]); - } else { - return Rotate._rot(input, args[0], Rotate._rotr); - } - }, - - - /** - * Rotate left operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRotl: function(input, args) { - if (args[1]) { - return Rotate._rotlWhole(input, args[0]); - } else { - return Rotate._rot(input, args[0], Rotate._rotl); - } - }, - - - /** - * @constant - * @default - */ - ROT13_AMOUNT: 13, - /** - * @constant - * @default - */ - ROT13_LOWERCASE: true, - /** - * @constant - * @default - */ - ROT13_UPPERCASE: true, - - /** - * ROT13 operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRot13: function(input, args) { - let amount = args[2], - output = input, - chr, - rot13Lowercase = args[0], - rot13Upperacse = args[1]; - - if (amount) { - if (amount < 0) { - amount = 26 - (Math.abs(amount) % 26); - } - - for (let i = 0; i < input.length; i++) { - chr = input[i]; - if (rot13Upperacse && chr >= 65 && chr <= 90) { // Upper case - chr = (chr - 65 + amount) % 26; - output[i] = chr + 65; - } else if (rot13Lowercase && chr >= 97 && chr <= 122) { // Lower case - chr = (chr - 97 + amount) % 26; - output[i] = chr + 97; - } - } - } - return output; - }, - - - /** - * @constant - * @default - */ - ROT47_AMOUNT: 47, - - /** - * ROT47 operation. - * - * @author Matt C [matt@artemisbot.pw] - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRot47: function(input, args) { - let amount = args[0], - output = input, - chr; - - if (amount) { - if (amount < 0) { - amount = 94 - (Math.abs(amount) % 94); - } - - for (let i = 0; i < input.length; i++) { - chr = input[i]; - if (chr >= 33 && chr <= 126) { - chr = (chr - 33 + amount) % 94; - output[i] = chr + 33; - } - } - } - return output; - }, - - - /** - * Rotate right bitwise op. - * - * @private - * @param {byte} b - * @returns {byte} - */ - _rotr: function(b) { - const bit = (b & 1) << 7; - return (b >> 1) | bit; - }, - - - /** - * Rotate left bitwise op. - * - * @private - * @param {byte} b - * @returns {byte} - */ - _rotl: function(b) { - const bit = (b >> 7) & 1; - return ((b << 1) | bit) & 0xFF; - }, - - - /** - * Rotates a byte array to the right by a specific amount as a whole, so that bits are wrapped - * from the end of the array to the beginning. - * - * @private - * @param {byteArray} data - * @param {number} amount - * @returns {byteArray} - */ - _rotrWhole: function(data, amount) { - let carryBits = 0, - newByte, - result = []; - - amount = amount % 8; - for (let i = 0; i < data.length; i++) { - const oldByte = data[i] >>> 0; - newByte = (oldByte >> amount) | carryBits; - carryBits = (oldByte & (Math.pow(2, amount)-1)) << (8-amount); - result.push(newByte); - } - result[0] |= carryBits; - return result; - }, - - - /** - * Rotates a byte array to the left by a specific amount as a whole, so that bits are wrapped - * from the beginning of the array to the end. - * - * @private - * @param {byteArray} data - * @param {number} amount - * @returns {byteArray} - */ - _rotlWhole: function(data, amount) { - let carryBits = 0, - newByte, - result = []; - - amount = amount % 8; - for (let i = data.length-1; i >= 0; i--) { - const oldByte = data[i]; - newByte = ((oldByte << amount) | carryBits) & 0xFF; - carryBits = (oldByte >> (8-amount)) & (Math.pow(2, amount)-1); - result[i] = (newByte); - } - result[data.length-1] = result[data.length-1] | carryBits; - return result; - }, - -}; - -export default Rotate; diff --git a/src/core/operations/RotateLeft.mjs b/src/core/operations/RotateLeft.mjs new file mode 100644 index 00000000..4f73345d --- /dev/null +++ b/src/core/operations/RotateLeft.mjs @@ -0,0 +1,82 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {rot, rotl, rotlCarry} from "../lib/Rotate"; + + +/** + * Rotate left operation. + */ +class RotateLeft extends Operation { + + /** + * RotateLeft constructor + */ + constructor() { + super(); + + this.name = "Rotate left"; + this.module = "Default"; + this.description = "Rotates each byte to the left by the number of bits specified, optionally carrying the excess bits over to the next byte. Currently only supports 8-bit values."; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Amount", + type: "number", + value: 1 + }, + { + name: "Carry through", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (args[1]) { + return rotlCarry(input, args[0]); + } else { + return rot(input, args[0], rotl); + } + } + + /** + * Highlight rotate left + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight rotate left in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } +} + +export default RotateLeft; diff --git a/src/core/operations/RotateRight.mjs b/src/core/operations/RotateRight.mjs new file mode 100644 index 00000000..455369b0 --- /dev/null +++ b/src/core/operations/RotateRight.mjs @@ -0,0 +1,82 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {rot, rotr, rotrCarry} from "../lib/Rotate"; + + +/** + * Rotate right operation. + */ +class RotateRight extends Operation { + + /** + * RotateRight constructor + */ + constructor() { + super(); + + this.name = "Rotate right"; + this.module = "Default"; + this.description = "Rotates each byte to the right by the number of bits specified, optionally carrying the excess bits over to the next byte. Currently only supports 8-bit values."; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Amount", + type: "number", + value: 1 + }, + { + name: "Carry through", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (args[1]) { + return rotrCarry(input, args[0]); + } else { + return rot(input, args[0], rotr); + } + } + + /** + * Highlight rotate right + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight rotate right in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } +} + +export default RotateRight; diff --git a/src/core/operations/SHA0.mjs b/src/core/operations/SHA0.mjs new file mode 100644 index 00000000..b83a47d7 --- /dev/null +++ b/src/core/operations/SHA0.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * SHA0 operation + */ +class SHA0 extends Operation { + + /** + * SHA0 constructor + */ + constructor() { + super(); + + this.name = "SHA0"; + this.module = "Crypto"; + this.description = "SHA-0 is a retronym applied to the original version of the 160-bit hash function published in 1993 under the name 'SHA'. It was withdrawn shortly after publication due to an undisclosed 'significant flaw' and replaced by the slightly revised version SHA-1."; + this.infoURL = "https://wikipedia.org/wiki/SHA-1#SHA-0"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("sha0", input); + } + +} + +export default SHA0; diff --git a/src/core/operations/SHA1.mjs b/src/core/operations/SHA1.mjs new file mode 100644 index 00000000..2dc5ce5c --- /dev/null +++ b/src/core/operations/SHA1.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * SHA1 operation + */ +class SHA1 extends Operation { + + /** + * SHA1 constructor + */ + constructor() { + super(); + + this.name = "SHA1"; + this.module = "Crypto"; + this.description = "The SHA (Secure Hash Algorithm) hash functions were designed by the NSA. SHA-1 is the most established of the existing SHA hash functions and it is used in a variety of security applications and protocols.

However, SHA-1's collision resistance has been weakening as new attacks are discovered or improved."; + this.infoURL = "https://wikipedia.org/wiki/SHA-1"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("sha1", input); + } + +} + +export default SHA1; diff --git a/src/core/operations/SHA2.mjs b/src/core/operations/SHA2.mjs new file mode 100644 index 00000000..05d4c253 --- /dev/null +++ b/src/core/operations/SHA2.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * SHA2 operation + */ +class SHA2 extends Operation { + + /** + * SHA2 constructor + */ + constructor() { + super(); + + this.name = "SHA2"; + this.module = "Crypto"; + this.description = "The SHA-2 (Secure Hash Algorithm 2) hash functions were designed by the NSA. SHA-2 includes significant changes from its predecessor, SHA-1. The SHA-2 family consists of hash functions with digests (hash values) that are 224, 256, 384 or 512 bits: SHA224, SHA256, SHA384, SHA512.

  • SHA-512 operates on 64-bit words.
  • SHA-256 operates on 32-bit words.
  • SHA-384 is largely identical to SHA-512 but is truncated to 384 bytes.
  • SHA-224 is largely identical to SHA-256 but is truncated to 224 bytes.
  • SHA-512/224 and SHA-512/256 are truncated versions of SHA-512, but the initial values are generated using the method described in Federal Information Processing Standards (FIPS) PUB 180-4.
"; + this.infoURL = "https://wikipedia.org/wiki/SHA-2"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Size", + "type": "option", + "value": ["512", "256", "384", "224", "512/256", "512/224"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = args[0]; + return runHash("sha" + size, input); + } + +} + +export default SHA2; diff --git a/src/core/operations/SHA3.mjs b/src/core/operations/SHA3.mjs new file mode 100644 index 00000000..cebb8e23 --- /dev/null +++ b/src/core/operations/SHA3.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import JSSHA3 from "js-sha3"; +import OperationError from "../errors/OperationError"; + +/** + * SHA3 operation + */ +class SHA3 extends Operation { + + /** + * SHA3 constructor + */ + constructor() { + super(); + + this.name = "SHA3"; + this.module = "Crypto"; + this.description = "The SHA-3 (Secure Hash Algorithm 3) hash functions were released by NIST on August 5, 2015. Although part of the same series of standards, SHA-3 is internally quite different from the MD5-like structure of SHA-1 and SHA-2.

SHA-3 is a subset of the broader cryptographic primitive family Keccak designed by Guido Bertoni, Joan Daemen, Micha\xebl Peeters, and Gilles Van Assche, building upon RadioGat\xfan."; + this.infoURL = "https://wikipedia.org/wiki/SHA-3"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Size", + "type": "option", + "value": ["512", "384", "256", "224"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = parseInt(args[0], 10); + let algo; + + switch (size) { + case 224: + algo = JSSHA3.sha3_224; + break; + case 384: + algo = JSSHA3.sha3_384; + break; + case 256: + algo = JSSHA3.sha3_256; + break; + case 512: + algo = JSSHA3.sha3_512; + break; + default: + throw new OperationError("Invalid size"); + } + + return algo(input); + } + +} + +export default SHA3; diff --git a/src/core/operations/SQLBeautify.mjs b/src/core/operations/SQLBeautify.mjs new file mode 100644 index 00000000..1862972a --- /dev/null +++ b/src/core/operations/SQLBeautify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * SQL Beautify operation + */ +class SQLBeautify extends Operation { + + /** + * SQLBeautify constructor + */ + constructor() { + super(); + + this.name = "SQL Beautify"; + this.module = "Code"; + this.description = "Indents and prettifies Structured Query Language (SQL) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Indent string", + "type": "binaryShortString", + "value": "\\t" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const indentStr = args[0]; + return vkbeautify.sql(input, indentStr); + } + +} + +export default SQLBeautify; diff --git a/src/core/operations/SQLMinify.mjs b/src/core/operations/SQLMinify.mjs new file mode 100644 index 00000000..d81e29ad --- /dev/null +++ b/src/core/operations/SQLMinify.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * SQL Minify operation + */ +class SQLMinify extends Operation { + + /** + * SQLMinify constructor + */ + constructor() { + super(); + + this.name = "SQL Minify"; + this.module = "Code"; + this.description = "Compresses Structured Query Language (SQL) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return vkbeautify.sqlmin(input); + } + +} + +export default SQLMinify; diff --git a/src/core/operations/SSDEEP.mjs b/src/core/operations/SSDEEP.mjs new file mode 100644 index 00000000..4ae5a650 --- /dev/null +++ b/src/core/operations/SSDEEP.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import ssdeepjs from "ssdeep.js"; + +/** + * SSDEEP operation + */ +class SSDEEP extends Operation { + + /** + * SSDEEP constructor + */ + constructor() { + super(); + + this.name = "SSDEEP"; + this.module = "Crypto"; + this.description = "SSDEEP is a program for computing context triggered piecewise hashes (CTPH). Also called fuzzy hashes, CTPH can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.

SSDEEP hashes are now widely used for simple identification purposes (e.g. the 'Basic Properties' section in VirusTotal). Although 'better' fuzzy hashes are available, SSDEEP is still one of the primary choices because of its speed and being a de facto standard.

This operation is fundamentally the same as the CTPH operation, however their outputs differ in format."; + this.infoURL = "https://forensicswiki.org/wiki/Ssdeep"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return ssdeepjs.digest(input); + } + +} + +export default SSDEEP; diff --git a/src/core/operations/SUB.mjs b/src/core/operations/SUB.mjs new file mode 100644 index 00000000..f2374488 --- /dev/null +++ b/src/core/operations/SUB.mjs @@ -0,0 +1,77 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import { bitOp, sub, BITWISE_OP_DELIMS } from "../lib/BitwiseOp"; + +/** + * SUB operation + */ +class SUB extends Operation { + + /** + * SUB constructor + */ + constructor() { + super(); + + this.name = "SUB"; + this.module = "Default"; + this.description = "SUB the input with the given key (e.g. fe023da5), MOD 255"; + this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bitwise_operators"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": BITWISE_OP_DELIMS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); + + return bitOp(input, key, sub); + } + + /** + * Highlight SUB + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight SUB in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default SUB; diff --git a/src/core/operations/ScanForEmbeddedFiles.mjs b/src/core/operations/ScanForEmbeddedFiles.mjs new file mode 100644 index 00000000..a38c477f --- /dev/null +++ b/src/core/operations/ScanForEmbeddedFiles.mjs @@ -0,0 +1,86 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import Magic from "../lib/Magic"; + +/** + * Scan for Embedded Files operation + */ +class ScanForEmbeddedFiles extends Operation { + + /** + * ScanForEmbeddedFiles constructor + */ + constructor() { + super(); + + this.name = "Scan for Embedded Files"; + this.module = "Default"; + this.description = "Scans the data for potential embedded files by looking for magic bytes at all offsets. This operation is prone to false positives.

WARNING: Files over about 100KB in size will take a VERY long time to process."; + this.infoURL = "https://wikipedia.org/wiki/List_of_file_signatures"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Ignore common byte sequences", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @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", + type, + numFound = 0, + numCommonFound = 0; + const ignoreCommon = args[0], + commonExts = ["ico", "ttf", ""], + data = new Uint8Array(input); + + for (let i = 0; i < data.length; i++) { + type = Magic.magicFileType(data.slice(i)); + if (type) { + if (ignoreCommon && commonExts.indexOf(type.ext) > -1) { + numCommonFound++; + continue; + } + numFound++; + output += "\nOffset " + i + " (0x" + Utils.hex(i) + "):\n" + + " File extension: " + type.ext + "\n" + + " MIME type: " + type.mime + "\n"; + + if (type.desc && type.desc.length) { + output += " Description: " + type.desc + "\n"; + } + } + } + + if (numFound === 0) { + output += "\nNo embedded files were found."; + } + + if (numCommonFound > 0) { + output += "\n\n" + numCommonFound; + output += numCommonFound === 1 ? + " file type was detected that has a common byte sequence. This is likely to be a false positive." : + " file types were detected that have common byte sequences. These are likely to be false positives."; + output += " Run this operation with the 'Ignore common byte sequences' option unchecked to see details."; + } + + return output; + } + +} + +export default ScanForEmbeddedFiles; diff --git a/src/core/operations/Scrypt.mjs b/src/core/operations/Scrypt.mjs new file mode 100644 index 00000000..134a04fc --- /dev/null +++ b/src/core/operations/Scrypt.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import scryptsy from "scryptsy"; + +/** + * Scrypt operation + */ +class Scrypt extends Operation { + + /** + * Scrypt constructor + */ + constructor() { + super(); + + this.name = "Scrypt"; + this.module = "Crypto"; + this.description = "scrypt is a password-based key derivation function (PBKDF) created by Colin Percival. The algorithm was specifically designed to make it costly to perform large-scale custom hardware attacks by requiring large amounts of memory. In 2016, the scrypt algorithm was published by IETF as RFC 7914.

Enter the password in the input to generate its hash."; + this.infoURL = "https://wikipedia.org/wiki/Scrypt"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Salt", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "Base64", "UTF8", "Latin1"] + }, + { + "name": "Iterations (N)", + "type": "number", + "value": 16384 + }, + { + "name": "Memory factor (r)", + "type": "number", + "value": 8 + }, + { + "name": "Parallelization factor (p)", + "type": "number", + "value": 1 + }, + { + "name": "Key length", + "type": "number", + "value": 64 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const salt = Buffer.from(Utils.convertToByteArray(args[0].string || "", args[0].option)), + iterations = args[1], + memFactor = args[2], + parallelFactor = args[3], + keyLength = args[4]; + + try { + const data = scryptsy( + input, salt, iterations, memFactor, parallelFactor, keyLength, + p => { + // Progress callback + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage(`Progress: ${p.percent.toFixed(0)}%`); + } + ); + + return data.toString("hex"); + } catch (err) { + throw new OperationError("Error: " + err.toString()); + } + } + +} + +export default Scrypt; diff --git a/src/core/operations/SeqUtils.js b/src/core/operations/SeqUtils.js deleted file mode 100755 index 96fae0aa..00000000 --- a/src/core/operations/SeqUtils.js +++ /dev/null @@ -1,226 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Sequence utility operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const SeqUtils = { - - /** - * @constant - * @default - */ - DELIMITER_OPTIONS: ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"], - /** - * @constant - * @default - */ - SORT_REVERSE: false, - /** - * @constant - * @default - */ - SORT_ORDER: ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address"], - - /** - * Sort operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSort: function (input, args) { - let delim = Utils.charRep[args[0]], - sortReverse = args[1], - order = args[2], - sorted = input.split(delim); - - if (order === "Alphabetical (case sensitive)") { - sorted = sorted.sort(); - } else if (order === "Alphabetical (case insensitive)") { - sorted = sorted.sort(SeqUtils._caseInsensitiveSort); - } else if (order === "IP address") { - sorted = sorted.sort(SeqUtils._ipSort); - } - - if (sortReverse) sorted.reverse(); - return sorted.join(delim); - }, - - - /** - * Unique operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runUnique: function (input, args) { - const delim = Utils.charRep[args[0]]; - return input.split(delim).unique().join(delim); - }, - - - /** - * @constant - * @default - */ - SEARCH_TYPE: ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"], - - /** - * Count occurrences operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {number} - */ - runCount: function(input, args) { - let search = args[0].string, - type = args[0].option; - - if (type === "Regex" && search) { - try { - let regex = new RegExp(search, "gi"), - matches = input.match(regex); - return matches.length; - } catch (err) { - return 0; - } - } else if (search) { - if (type.indexOf("Extended") === 0) { - search = Utils.parseEscapedChars(search); - } - return input.count(search); - } else { - return 0; - } - }, - - - /** - * @constant - * @default - */ - REVERSE_BY: ["Character", "Line"], - - /** - * Reverse operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runReverse: function (input, args) { - let i; - if (args[0] === "Line") { - let lines = [], - line = [], - result = []; - for (i = 0; i < input.length; i++) { - if (input[i] === 0x0a) { - lines.push(line); - line = []; - } else { - line.push(input[i]); - } - } - lines.push(line); - lines.reverse(); - for (i = 0; i < lines.length; i++) { - result = result.concat(lines[i]); - result.push(0x0a); - } - return result.slice(0, input.length); - } else { - return input.reverse(); - } - }, - - - /** - * Add line numbers operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAddLineNumbers: function(input, args) { - let lines = input.split("\n"), - output = "", - width = lines.length.toString().length; - - for (let n = 0; n < lines.length; n++) { - output += Utils.pad((n+1).toString(), width, " ") + " " + lines[n] + "\n"; - } - return output.slice(0, output.length-1); - }, - - - /** - * Remove line numbers operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRemoveLineNumbers: function(input, args) { - return input.replace(/^[ \t]{0,5}\d+[\s:|\-,.)\]]/gm, ""); - }, - - - /** - * Expand alphabet range operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runExpandAlphRange: function(input, args) { - return Utils.expandAlphRange(input).join(args[0]); - }, - - - /** - * Comparison operation for sorting of strings ignoring case. - * - * @private - * @param {string} a - * @param {string} b - * @returns {number} - */ - _caseInsensitiveSort: function(a, b) { - return a.toLowerCase().localeCompare(b.toLowerCase()); - }, - - - /** - * Comparison operation for sorting of IPv4 addresses. - * - * @private - * @param {string} a - * @param {string} b - * @returns {number} - */ - _ipSort: function(a, b) { - let a_ = a.split("."), - b_ = b.split("."); - - a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1; - b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1; - - if (isNaN(a_) && !isNaN(b_)) return 1; - if (!isNaN(a_) && isNaN(b_)) return -1; - if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b); - - return a_ - b_; - }, - -}; - -export default SeqUtils; diff --git a/src/core/operations/SetDifference.mjs b/src/core/operations/SetDifference.mjs new file mode 100644 index 00000000..ce272880 --- /dev/null +++ b/src/core/operations/SetDifference.mjs @@ -0,0 +1,87 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Set Difference operation + */ +class SetDifference extends Operation { + + /** + * Set Difference constructor + */ + constructor() { + super(); + + this.name = "Set Difference"; + this.module = "Default"; + this.description = "Calculates the difference, or relative complement, of two sets."; + this.infoURL = "https://wikipedia.org/wiki/Complement_(set_theory)#Relative_complement"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); + } + } + + /** + * Run the difference operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runSetDifference(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get elements in set a that are not in set b + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runSetDifference(a, b) { + return a + .filter((item) => { + return b.indexOf(item) === -1; + }) + .join(this.itemDelimiter); + } + +} + +export default SetDifference; diff --git a/src/core/operations/SetIntersection.mjs b/src/core/operations/SetIntersection.mjs new file mode 100644 index 00000000..87cb2b3c --- /dev/null +++ b/src/core/operations/SetIntersection.mjs @@ -0,0 +1,87 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Set Intersection operation + */ +class SetIntersection extends Operation { + + /** + * Set Intersection constructor + */ + constructor() { + super(); + + this.name = "Set Intersection"; + this.module = "Default"; + this.description = "Calculates the intersection of two sets."; + this.infoURL = "https://wikipedia.org/wiki/Intersection_(set_theory)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); + } + } + + /** + * Run the intersection operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runIntersect(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get the intersection of the two sets. + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runIntersect(a, b) { + return a + .filter((item) => { + return b.indexOf(item) > -1; + }) + .join(this.itemDelimiter); + } + +} + +export default SetIntersection; diff --git a/src/core/operations/SetUnion.mjs b/src/core/operations/SetUnion.mjs new file mode 100644 index 00000000..b835605e --- /dev/null +++ b/src/core/operations/SetUnion.mjs @@ -0,0 +1,97 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Set Union operation + */ +class SetUnion extends Operation { + + /** + * Set Union constructor + */ + constructor() { + super(); + + this.name = "Set Union"; + this.module = "Default"; + this.description = "Calculates the union of two sets."; + this.infoURL = "https://wikipedia.org/wiki/Union_(set_theory)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); + } + } + + /** + * Run the union operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runUnion(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get the union of the two sets. + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runUnion(a, b) { + const result = {}; + + /** + * Only add non-existing items + * @param {Object} hash + */ + const addUnique = (hash) => (item) => { + if (!hash[item]) { + hash[item] = true; + } + }; + + a.map(addUnique(result)); + b.map(addUnique(result)); + + return Object.keys(result).join(this.itemDelimiter); + } +} + +export default SetUnion; diff --git a/src/core/operations/Shake.mjs b/src/core/operations/Shake.mjs new file mode 100644 index 00000000..e096ac31 --- /dev/null +++ b/src/core/operations/Shake.mjs @@ -0,0 +1,71 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import JSSHA3 from "js-sha3"; + +/** + * Shake operation + */ +class Shake extends Operation { + + /** + * Shake constructor + */ + constructor() { + super(); + + this.name = "Shake"; + this.module = "Crypto"; + this.description = "Shake is an Extendable Output Function (XOF) of the SHA-3 hash algorithm, part of the Keccak family, allowing for variable output length/size."; + this.infoURL = "https://wikipedia.org/wiki/SHA-3#Instances"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Capacity", + "type": "option", + "value": ["256", "128"] + }, + { + "name": "Size", + "type": "number", + "value": 512 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const capacity = parseInt(args[0], 10), + size = args[1]; + let algo; + + if (size < 0) + throw new OperationError("Size must be greater than 0"); + + switch (capacity) { + case 128: + algo = JSSHA3.shake128; + break; + case 256: + algo = JSSHA3.shake256; + break; + default: + throw new OperationError("Invalid size"); + } + + return algo(input, size); + } + +} + +export default Shake; diff --git a/src/core/operations/ShowBase64Offsets.mjs b/src/core/operations/ShowBase64Offsets.mjs new file mode 100644 index 00000000..d028400d --- /dev/null +++ b/src/core/operations/ShowBase64Offsets.mjs @@ -0,0 +1,173 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {fromBase64, toBase64} from "../lib/Base64"; +import OperationError from "../errors/OperationError"; + +/** + * Show Base64 offsets operation + */ +class ShowBase64Offsets extends Operation { + + /** + * ShowBase64Offsets constructor + */ + constructor() { + super(); + + this.name = "Show Base64 offsets"; + this.module = "Default"; + this.description = "When a string is within a block of data and the whole block is Base64'd, the string itself could be represented in Base64 in three distinct ways depending on its offset within the block.

This operation shows all possible offsets for a given string so that each possible encoding can be considered."; + this.infoURL = "https://wikipedia.org/wiki/Base64#Output_padding"; + this.inputType = "byteArray"; + this.outputType = "html"; + this.args = [ + { + name: "Alphabet", + type: "binaryString", + value: "A-Za-z0-9+/=" + }, + { + name: "Show variable chars and padding", + type: "boolean", + value: true + }, + { + name: "Input format", + type: "option", + value: ["Raw", "Base64"] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [alphabet, showVariable, format] = args; + + if (format === "Base64") { + input = fromBase64(Utils.byteArrayToUtf8(input), null, "byteArray"); + } + + let offset0 = toBase64(input, alphabet), + offset1 = toBase64([0].concat(input), alphabet), + offset2 = toBase64([0, 0].concat(input), alphabet), + staticSection = "", + padding = ""; + + const len0 = offset0.indexOf("="), + len1 = offset1.indexOf("="), + len2 = offset2.indexOf("="), + script = ""; + + if (input.length < 1) { + throw new OperationError("Please enter a string."); + } + + // Highlight offset 0 + if (len0 % 4 === 2) { + staticSection = offset0.slice(0, -3); + offset0 = "" + + staticSection + "" + + "" + offset0.substr(offset0.length - 3, 1) + "" + + "" + offset0.substr(offset0.length - 2) + ""; + } else if (len0 % 4 === 3) { + staticSection = offset0.slice(0, -2); + offset0 = "" + + staticSection + "" + + "" + offset0.substr(offset0.length - 2, 1) + "" + + "" + offset0.substr(offset0.length - 1) + ""; + } else { + staticSection = offset0; + offset0 = "" + + staticSection + ""; + } + + if (!showVariable) { + offset0 = staticSection; + } + + + // Highlight offset 1 + padding = "" + offset1.substr(0, 1) + "" + + "" + offset1.substr(1, 1) + ""; + offset1 = offset1.substr(2); + if (len1 % 4 === 2) { + staticSection = offset1.slice(0, -3); + offset1 = padding + "" + + staticSection + "" + + "" + offset1.substr(offset1.length - 3, 1) + "" + + "" + offset1.substr(offset1.length - 2) + ""; + } else if (len1 % 4 === 3) { + staticSection = offset1.slice(0, -2); + offset1 = padding + "" + + staticSection + "" + + "" + offset1.substr(offset1.length - 2, 1) + "" + + "" + offset1.substr(offset1.length - 1) + ""; + } else { + staticSection = offset1; + offset1 = padding + "" + + staticSection + ""; + } + + if (!showVariable) { + offset1 = staticSection; + } + + // Highlight offset 2 + padding = "" + offset2.substr(0, 2) + "" + + "" + offset2.substr(2, 1) + ""; + offset2 = offset2.substr(3); + if (len2 % 4 === 2) { + staticSection = offset2.slice(0, -3); + offset2 = padding + "" + + staticSection + "" + + "" + offset2.substr(offset2.length - 3, 1) + "" + + "" + offset2.substr(offset2.length - 2) + ""; + } else if (len2 % 4 === 3) { + staticSection = offset2.slice(0, -2); + offset2 = padding + "" + + staticSection + "" + + "" + offset2.substr(offset2.length - 2, 1) + "" + + "" + offset2.substr(offset2.length - 1) + ""; + } else { + staticSection = offset2; + offset2 = padding + "" + + staticSection + ""; + } + + if (!showVariable) { + offset2 = staticSection; + } + + return (showVariable ? "Characters highlighted in green could change if the input is surrounded by more data." + + "\nCharacters highlighted in red are for padding purposes only." + + "\nUnhighlighted characters are static." + + "\nHover over the static sections to see what they decode to on their own.\n" + + "\nOffset 0: " + offset0 + + "\nOffset 1: " + offset1 + + "\nOffset 2: " + offset2 + + script : + offset0 + "\n" + offset1 + "\n" + offset2); + } + +} + +export default ShowBase64Offsets; diff --git a/src/core/operations/Sleep.mjs b/src/core/operations/Sleep.mjs new file mode 100644 index 00000000..4cd71bfe --- /dev/null +++ b/src/core/operations/Sleep.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Sleep operation + */ +class Sleep extends Operation { + + /** + * Sleep constructor + */ + constructor() { + super(); + + this.name = "Sleep"; + this.module = "Default"; + this.description = "Sleep causes the recipe to wait for a specified number of milliseconds before continuing execution."; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Time (ms)", + "type": "number", + "value": 1000 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + const ms = args[0]; + await new Promise(r => setTimeout(r, ms)); + return input; + } + +} + +export default Sleep; diff --git a/src/core/operations/Snefru.mjs b/src/core/operations/Snefru.mjs new file mode 100644 index 00000000..b7cd65b5 --- /dev/null +++ b/src/core/operations/Snefru.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * Snefru operation + */ +class Snefru extends Operation { + + /** + * Snefru constructor + */ + constructor() { + super(); + + this.name = "Snefru"; + this.module = "Crypto"; + this.description = "Snefru is a cryptographic hash function invented by Ralph Merkle in 1990 while working at Xerox PARC. The function supports 128-bit and 256-bit output. It was named after the Egyptian Pharaoh Sneferu, continuing the tradition of the Khufu and Khafre block ciphers.

The original design of Snefru was shown to be insecure by Eli Biham and Adi Shamir who were able to use differential cryptanalysis to find hash collisions. The design was then modified by increasing the number of iterations of the main pass of the algorithm from two to eight."; + this.infoURL = "https://wikipedia.org/wiki/Snefru"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Rounds", + "type": "option", + "value": ["8", "4", "2"] + }, + { + "name": "Size", + "type": "option", + "value": ["256", "128"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("snefru", input, { + rounds: args[0], + length: args[1] + }); + } + +} + +export default Snefru; diff --git a/src/core/operations/Sort.mjs b/src/core/operations/Sort.mjs new file mode 100644 index 00000000..7e024d45 --- /dev/null +++ b/src/core/operations/Sort.mjs @@ -0,0 +1,176 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * Sort operation + */ +class Sort extends Operation { + + /** + * Sort constructor + */ + constructor() { + super(); + + this.name = "Sort"; + this.module = "Default"; + this.description = "Alphabetically sorts strings separated by the specified delimiter.

The IP address option supports IPv4 only."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "Reverse", + "type": "boolean", + "value": false + }, + { + "name": "Order", + "type": "option", + "value": ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric", "Numeric (hexadecimal)"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + sortReverse = args[1], + order = args[2]; + let sorted = input.split(delim); + + if (order === "Alphabetical (case sensitive)") { + sorted = sorted.sort(); + } else if (order === "Alphabetical (case insensitive)") { + sorted = sorted.sort(Sort._caseInsensitiveSort); + } else if (order === "IP address") { + sorted = sorted.sort(Sort._ipSort); + } else if (order === "Numeric") { + sorted = sorted.sort(Sort._numericSort); + } else if (order === "Numeric (hexadecimal)") { + sorted = sorted.sort(Sort._hexadecimalSort); + } + + if (sortReverse) sorted.reverse(); + return sorted.join(delim); + } + + /** + * Comparison operation for sorting of strings ignoring case. + * + * @private + * @param {string} a + * @param {string} b + * @returns {number} + */ + static _caseInsensitiveSort(a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()); + } + + + /** + * Comparison operation for sorting of IPv4 addresses. + * + * @private + * @param {string} a + * @param {string} b + * @returns {number} + */ + static _ipSort(a, b) { + let a_ = a.split("."), + b_ = b.split("."); + + a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1; + b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1; + + if (isNaN(a_) && !isNaN(b_)) return 1; + if (!isNaN(a_) && isNaN(b_)) return -1; + if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b); + + return a_ - b_; + } + + /** + * Comparison operation for sorting of numeric values. + * + * @author Chris van Marle + * @private + * @param {string} a + * @param {string} b + * @returns {number} + */ + static _numericSort(a, b) { + const a_ = a.split(/([^\d]+)/), + b_ = b.split(/([^\d]+)/); + + for (let i = 0; i < a_.length && i < b.length; ++i) { + if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers + if (!isNaN(a_[i]) && isNaN(b_[i])) return -1; + if (isNaN(a_[i]) && isNaN(b_[i])) { + const ret = a_[i].localeCompare(b_[i]); // Compare strings + if (ret !== 0) return ret; + } + if (!isNaN(a_[i]) && !isNaN(a_[i])) { // Compare numbers + if (a_[i] - b_[i] !== 0) return a_[i] - b_[i]; + } + } + + return a.localeCompare(b); + } + + /** + * Comparison operation for sorting of hexadecimal values. + * + * @author Chris van Marle + * @private + * @param {string} a + * @param {string} b + * @returns {number} + */ + static _hexadecimalSort(a, b) { + let a_ = a.split(/([^\da-f]+)/i), + b_ = b.split(/([^\da-f]+)/i); + + a_ = a_.map(v => { + const t = parseInt(v, 16); + return isNaN(t) ? v : t; + }); + + b_ = b_.map(v => { + const t = parseInt(v, 16); + return isNaN(t) ? v : t; + }); + + for (let i = 0; i < a_.length && i < b.length; ++i) { + if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers + if (!isNaN(a_[i]) && isNaN(b_[i])) return -1; + if (isNaN(a_[i]) && isNaN(b_[i])) { + const ret = a_[i].localeCompare(b_[i]); // Compare strings + if (ret !== 0) return ret; + } + if (!isNaN(a_[i]) && !isNaN(a_[i])) { // Compare numbers + if (a_[i] - b_[i] !== 0) return a_[i] - b_[i]; + } + } + + return a.localeCompare(b); + } + +} + +export default Sort; diff --git a/src/core/operations/Split.mjs b/src/core/operations/Split.mjs new file mode 100644 index 00000000..1340a383 --- /dev/null +++ b/src/core/operations/Split.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {SPLIT_DELIM_OPTIONS, JOIN_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * Split operation + */ +class Split extends Operation { + + /** + * Split constructor + */ + constructor() { + super(); + + this.name = "Split"; + this.module = "Default"; + this.description = "Splits a string into sections around a given delimiter."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Split delimiter", + "type": "editableOptionShort", + "value": SPLIT_DELIM_OPTIONS + }, + { + "name": "Join delimiter", + "type": "editableOptionShort", + "value": JOIN_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const splitDelim = args[0], + joinDelim = args[1], + sections = input.split(splitDelim); + + return sections.join(joinDelim); + } + +} + +export default Split; diff --git a/src/core/operations/SplitColourChannels.mjs b/src/core/operations/SplitColourChannels.mjs new file mode 100644 index 00000000..f11ca14e --- /dev/null +++ b/src/core/operations/SplitColourChannels.mjs @@ -0,0 +1,105 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; +import Magic from "../lib/Magic"; + +import jimp from "jimp"; + +/** + * Split Colour Channels operation + */ +class SplitColourChannels extends Operation { + + /** + * SplitColourChannels constructor + */ + constructor() { + super(); + + this.name = "Split Colour Channels"; + 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.outputType = "List"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {List} + */ + async run(input, args) { + const type = Magic.magicFileType(input); + // Make sure that the input is an image + if (type && type.mime.indexOf("image") === 0) { + const parsedImage = await jimp.read(Buffer.from(input)); + + const red = new Promise(async (resolve, reject) => { + try { + const split = parsedImage + .clone() + .color([ + {apply: "blue", params: [-255]}, + {apply: "green", params: [-255]} + ]) + .getBufferAsync(jimp.MIME_PNG); + resolve(new File([new Uint8Array((await split).values())], "red.png", {type: "image/png"})); + } catch (err) { + reject(new OperationError(`Could not split red channel: ${err}`)); + } + }); + + const green = new Promise(async (resolve, reject) => { + try { + const split = parsedImage.clone() + .color([ + {apply: "red", params: [-255]}, + {apply: "blue", params: [-255]}, + ]).getBufferAsync(jimp.MIME_PNG); + resolve(new File([new Uint8Array((await split).values())], "green.png", {type: "image/png"})); + } catch (err) { + reject(new OperationError(`Could not split green channel: ${err}`)); + } + }); + + const blue = new Promise(async (resolve, reject) => { + try { + const split = parsedImage + .color([ + {apply: "red", params: [-255]}, + {apply: "green", params: [-255]}, + ]).getBufferAsync(jimp.MIME_PNG); + resolve(new File([new Uint8Array((await split).values())], "blue.png", {type: "image/png"})); + } catch (err) { + reject(new OperationError(`Could not split blue channel: ${err}`)); + } + }); + + return await Promise.all([red, green, blue]); + } else { + throw new OperationError("Invalid file type."); + } + } + + /** + * Displays the files in HTML for web apps. + * + * @param {File[]} files + * @returns {html} + */ + async present(files) { + return await Utils.displayFilesAsHTML(files); + } + +} + +export default SplitColourChannels; diff --git a/src/core/operations/StandardDeviation.mjs b/src/core/operations/StandardDeviation.mjs new file mode 100644 index 00000000..d02bd93c --- /dev/null +++ b/src/core/operations/StandardDeviation.mjs @@ -0,0 +1,53 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { stdDev, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + + +/** + * Standard Deviation operation + */ +class StandardDeviation extends Operation { + + /** + * StandardDeviation constructor + */ + constructor() { + super(); + + this.name = "Standard Deviation"; + this.module = "Default"; + this.description = "Computes the standard deviation of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 4.089281382128433"; + this.infoURL = "https://wikipedia.org/wiki/Standard_deviation"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = stdDev(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + + } + +} + +export default StandardDeviation; diff --git a/src/core/operations/StrUtils.js b/src/core/operations/StrUtils.js deleted file mode 100755 index 78a0e838..00000000 --- a/src/core/operations/StrUtils.js +++ /dev/null @@ -1,596 +0,0 @@ -import Utils from "../Utils.js"; -import * as JsDiff from "diff"; - - -/** - * String utility operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const StrUtils = { - - /** - * @constant - * @default - */ - REGEX_PRE_POPULATE: [ - { - name: "User defined", - value: "" - }, - { - name: "IPv4 address", - value: "(?:(?:\\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})?" - }, - { - name: "IPv6 address", - value: "((?=.*::)(?!.*::.+::)(::)?([\\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})" - }, - { - name: "Email address", - value: "(\\w[-.\\w]*)@([-\\w]+(?:\\.[-\\w]+)*)\\.([A-Za-z]{2,4})" - }, - { - name: "URL", - value: "([A-Za-z]+://)([-\\w]+(?:\\.\\w[-\\w]*)+)(:\\d+)?(/[^.!,?;\"\\x27<>()\\[\\]{}\\s\\x7F-\\xFF]*(?:[.!,?]+[^.!,?;\"\\x27<>()\\[\\]{}\\s\\x7F-\\xFF]+)*)?" - }, - { - name: "Domain", - value: "(?:(https?):\\/\\/)?([-\\w.]+)\\.(com|net|org|biz|info|co|uk|onion|int|mobi|name|edu|gov|mil|eu|ac|ae|af|de|ca|ch|cn|cy|es|gb|hk|il|in|io|tv|me|nl|no|nz|ro|ru|tr|us|az|ir|kz|uz|pk)+" - }, - { - name: "Windows file path", - value: "([A-Za-z]):\\\\((?:[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)]{0,61}\\\\?)*[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)]{0,61})(\\.[A-Za-z\\d]{1,6})?" - }, - { - name: "UNIX file path", - value: "(?:/[A-Za-z\\d.][A-Za-z\\d\\-.]{0,61})+" - }, - { - name: "MAC address", - value: "[A-Fa-f\\d]{2}(?:[:-][A-Fa-f\\d]{2}){5}" - }, - { - name: "Date (yyyy-mm-dd)", - value: "((?:19|20)\\d\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])" - }, - { - name: "Date (dd/mm/yyyy)", - value: "(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.]((?:19|20)\\d\\d)" - }, - { - name: "Date (mm/dd/yyyy)", - value: "(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.]((?:19|20)\\d\\d)" - }, - { - name: "Strings", - value: "[A-Za-z\\d/\\-:.,_$%\\x27\"()<>= !\\[\\]{}@]{4,}" - }, - ], - /** - * @constant - * @default - */ - REGEX_CASE_INSENSITIVE: true, - /** - * @constant - * @default - */ - REGEX_MULTILINE_MATCHING: true, - /** - * @constant - * @default - */ - OUTPUT_FORMAT: ["Highlight matches", "List matches", "List capture groups", "List matches with capture groups"], - /** - * @constant - * @default - */ - DISPLAY_TOTAL: false, - - /** - * Regular expression operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runRegex: function(input, args) { - let userRegex = args[1], - i = args[2], - m = args[3], - displayTotal = args[4], - outputFormat = args[5], - modifiers = "g"; - - if (i) modifiers += "i"; - if (m) modifiers += "m"; - - if (userRegex && userRegex !== "^" && userRegex !== "$") { - try { - const regex = new RegExp(userRegex, modifiers); - - switch (outputFormat) { - case "Highlight matches": - return StrUtils._regexHighlight(input, regex, displayTotal); - case "List matches": - return Utils.escapeHtml(StrUtils._regexList(input, regex, displayTotal, true, false)); - case "List capture groups": - return Utils.escapeHtml(StrUtils._regexList(input, regex, displayTotal, false, true)); - case "List matches with capture groups": - return Utils.escapeHtml(StrUtils._regexList(input, regex, displayTotal, true, true)); - default: - return "Error: Invalid output format"; - } - } catch (err) { - return "Invalid regex. Details: " + err.message; - } - } else { - return Utils.escapeHtml(input); - } - }, - - - /** - * @constant - * @default - */ - CASE_SCOPE: ["All", "Word", "Sentence", "Paragraph"], - - /** - * To Upper case operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runUpper: function (input, args) { - const scope = args[0]; - - switch (scope) { - case "Word": - return input.replace(/(\b\w)/gi, function(m) { - return m.toUpperCase(); - }); - case "Sentence": - return input.replace(/(?:\.|^)\s*(\b\w)/gi, function(m) { - return m.toUpperCase(); - }); - case "Paragraph": - return input.replace(/(?:\n|^)\s*(\b\w)/gi, function(m) { - return m.toUpperCase(); - }); - case "All": - /* falls through */ - default: - return input.toUpperCase(); - } - }, - - - /** - * To Upper case operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runLower: function (input, args) { - return input.toLowerCase(); - }, - - - /** - * @constant - * @default - */ - SEARCH_TYPE: ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"], - /** - * @constant - * @default - */ - FIND_REPLACE_GLOBAL : true, - /** - * @constant - * @default - */ - FIND_REPLACE_CASE : false, - /** - * @constant - * @default - */ - FIND_REPLACE_MULTILINE : true, - - /** - * Find / Replace operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFindReplace: function(input, args) { - let find = args[0].string, - type = args[0].option, - replace = args[1], - g = args[2], - i = args[3], - m = args[4], - modifiers = ""; - - if (g) modifiers += "g"; - if (i) modifiers += "i"; - if (m) modifiers += "m"; - - if (type === "Regex") { - find = new RegExp(find, modifiers); - } else if (type.indexOf("Extended") === 0) { - find = Utils.parseEscapedChars(find); - } - - return input.replace(find, replace, modifiers); - // Non-standard addition of flags in the third argument. This will work in Firefox but - // probably nowhere else. The purpose is to allow global matching when the `find` parameter - // is just a string. - }, - - - /** - * @constant - * @default - */ - SPLIT_DELIM: ",", - /** - * @constant - * @default - */ - DELIMITER_OPTIONS: ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"], - - /** - * Split operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSplit: function(input, args) { - let splitDelim = args[0] || StrUtils.SPLIT_DELIM, - joinDelim = Utils.charRep[args[1]], - sections = input.split(splitDelim); - - return sections.join(joinDelim); - }, - - - /** - * Filter operation. - * - * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFilter: function(input, args) { - let delim = Utils.charRep[args[0]], - regex, - reverse = args[2]; - - try { - regex = new RegExp(args[1]); - } catch (err) { - return "Invalid regex. Details: " + err.message; - } - - const regexFilter = function(value) { - return reverse ^ regex.test(value); - }; - - return input.split(delim).filter(regexFilter).join(delim); - }, - - - /** - * @constant - * @default - */ - DIFF_SAMPLE_DELIMITER: "\\n\\n", - /** - * @constant - * @default - */ - DIFF_BY: ["Character", "Word", "Line", "Sentence", "CSS", "JSON"], - - /** - * Diff operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runDiff: function(input, args) { - let sampleDelim = args[0], - diffBy = args[1], - showAdded = args[2], - showRemoved = args[3], - ignoreWhitespace = args[4], - samples = input.split(sampleDelim), - output = "", - diff; - - if (!samples || samples.length !== 2) { - return "Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?"; - } - - switch (diffBy) { - case "Character": - diff = JsDiff.diffChars(samples[0], samples[1]); - break; - case "Word": - if (ignoreWhitespace) { - diff = JsDiff.diffWords(samples[0], samples[1]); - } else { - diff = JsDiff.diffWordsWithSpace(samples[0], samples[1]); - } - break; - case "Line": - if (ignoreWhitespace) { - diff = JsDiff.diffTrimmedLines(samples[0], samples[1]); - } else { - diff = JsDiff.diffLines(samples[0], samples[1]); - } - break; - case "Sentence": - diff = JsDiff.diffSentences(samples[0], samples[1]); - break; - case "CSS": - diff = JsDiff.diffCss(samples[0], samples[1]); - break; - case "JSON": - diff = JsDiff.diffJson(samples[0], samples[1]); - break; - default: - return "Invalid 'Diff by' option."; - } - - for (let i = 0; i < diff.length; i++) { - if (diff[i].added) { - if (showAdded) output += "" + Utils.escapeHtml(diff[i].value) + ""; - } else if (diff[i].removed) { - if (showRemoved) output += "" + Utils.escapeHtml(diff[i].value) + ""; - } else { - output += Utils.escapeHtml(diff[i].value); - } - } - - return output; - }, - - - /** - * @constant - * @default - */ - OFF_CHK_SAMPLE_DELIMITER: "\\n\\n", - - /** - * Offset checker operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runOffsetChecker: function(input, args) { - let sampleDelim = args[0], - samples = input.split(sampleDelim), - outputs = new Array(samples.length), - i = 0, - s = 0, - match = false, - inMatch = false, - chr; - - if (!samples || samples.length < 2) { - return "Not enough samples, perhaps you need to modify the sample delimiter or add more data?"; - } - - // Initialise output strings - outputs.fill("", 0, samples.length); - - // Loop through each character in the first sample - for (i = 0; i < samples[0].length; i++) { - chr = samples[0][i]; - match = false; - - // Loop through each sample to see if the chars are the same - for (s = 1; s < samples.length; s++) { - if (samples[s][i] !== chr) { - match = false; - break; - } - match = true; - } - - // Write output for each sample - for (s = 0; s < samples.length; s++) { - if (samples[s].length <= i) { - if (inMatch) outputs[s] += ""; - if (s === samples.length - 1) inMatch = false; - continue; - } - - if (match && !inMatch) { - outputs[s] += "" + Utils.escapeHtml(samples[s][i]); - if (samples[s].length === i + 1) outputs[s] += ""; - if (s === samples.length - 1) inMatch = true; - } else if (!match && inMatch) { - outputs[s] += "" + Utils.escapeHtml(samples[s][i]); - if (s === samples.length - 1) inMatch = false; - } else { - outputs[s] += Utils.escapeHtml(samples[s][i]); - if (inMatch && samples[s].length === i + 1) { - outputs[s] += ""; - if (samples[s].length - 1 !== i) inMatch = false; - } - } - - if (samples[0].length - 1 === i) { - if (inMatch) outputs[s] += ""; - outputs[s] += Utils.escapeHtml(samples[s].substring(i + 1)); - } - } - } - - return outputs.join(sampleDelim); - }, - - - /** - * Parse escaped string operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseEscapedString: function(input, args) { - return Utils.parseEscapedChars(input); - }, - - - /** - * Head lines operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runHead: function(input, args) { - let delimiter = args[0], - number = args[1]; - - delimiter = Utils.charRep[delimiter]; - const splitInput = input.split(delimiter); - - return splitInput - .filter((line, lineIndex) => { - lineIndex += 1; - - if (number < 0) { - return lineIndex <= splitInput.length + number; - } else { - return lineIndex <= number; - } - }) - .join(delimiter); - }, - - - /** - * Tail lines operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runTail: function(input, args) { - let delimiter = args[0], - number = args[1]; - - delimiter = Utils.charRep[delimiter]; - const splitInput = input.split(delimiter); - - return splitInput - .filter((line, lineIndex) => { - lineIndex += 1; - - if (number < 0) { - return lineIndex > -number; - } else { - return lineIndex > splitInput.length - number; - } - }) - .join(delimiter); - }, - - - /** - * Adds HTML highlights to matches within a string. - * - * @private - * @param {string} input - * @param {RegExp} regex - * @param {boolean} displayTotal - * @returns {string} - */ - _regexHighlight: function(input, regex, displayTotal) { - let output = "", - m, - hl = 1, - i = 0, - total = 0; - - while ((m = regex.exec(input))) { - // Add up to match - output += Utils.escapeHtml(input.slice(i, m.index)); - - // Add match with highlighting - output += "" + Utils.escapeHtml(m[0]) + ""; - - // Switch highlight - hl = hl === 1 ? 2 : 1; - - i = regex.lastIndex; - total++; - } - - // Add all after final match - output += Utils.escapeHtml(input.slice(i, input.length)); - - if (displayTotal) - output = "Total found: " + total + "\n\n" + output; - - return output; - }, - - - /** - * Creates a string listing the matches within a string. - * - * @private - * @param {string} input - * @param {RegExp} regex - * @param {boolean} displayTotal - * @param {boolean} matches - Display full match - * @param {boolean} captureGroups - Display each of the capture groups separately - * @returns {string} - */ - _regexList: function(input, regex, displayTotal, matches, captureGroups) { - let output = "", - total = 0, - match; - - while ((match = regex.exec(input))) { - total++; - if (matches) { - output += match[0] + "\n"; - } - if (captureGroups) { - for (let i = 1; i < match.length; i++) { - if (matches) { - output += " Group " + i + ": "; - } - output += match[i] + "\n"; - } - } - } - - if (displayTotal) - output = "Total found: " + total + "\n\n" + output; - - return output; - }, -}; - -export default StrUtils; diff --git a/src/core/operations/Strings.mjs b/src/core/operations/Strings.mjs new file mode 100644 index 00000000..11ffd3af --- /dev/null +++ b/src/core/operations/Strings.mjs @@ -0,0 +1,117 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import XRegExp from "xregexp"; +import { search } from "../lib/Extract"; + +/** + * Strings operation + */ +class Strings extends Operation { + + /** + * Strings constructor + */ + constructor() { + super(); + + this.name = "Strings"; + this.module = "Regex"; + this.description = "Extracts all strings from the input."; + this.infoURL = "https://wikipedia.org/wiki/Strings_(Unix)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Encoding", + "type": "option", + "value": ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"] + }, + { + "name": "Minimum length", + "type": "number", + "value": 4 + }, + { + "name": "Match", + "type": "option", + "value": [ + "[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)", + "[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)" + ] + }, + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [encoding, minLen, matchType, displayTotal] = args, + alphanumeric = "A-Z\\d", + punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@", + printable = "\x20-\x7e", + uniAlphanumeric = "\\pL\\pN", + uniPunctuation = "\\pP\\pZ", + uniPrintable = "\\pL\\pM\\pZ\\pS\\pN\\pP"; + + let strings = ""; + + switch (matchType) { + case "Alphanumeric + punctuation (A)": + strings = `[${alphanumeric + punctuation}]`; + break; + case "All printable chars (A)": + case "Null-terminated strings (A)": + strings = `[${printable}]`; + break; + case "Alphanumeric + punctuation (U)": + strings = `[${uniAlphanumeric + uniPunctuation}]`; + break; + case "All printable chars (U)": + case "Null-terminated strings (U)": + strings = `[${uniPrintable}]`; + break; + } + + // UTF-16 support is hacked in by allowing null bytes on either side of the matched chars + switch (encoding) { + case "All": + strings = `(\x00?${strings}\x00?)`; + break; + case "16-bit littleendian": + strings = `(${strings}\x00)`; + break; + case "16-bit bigendian": + strings = `(\x00${strings})`; + break; + case "Single byte": + default: + break; + } + + strings = `${strings}{${minLen},}`; + + if (matchType.includes("Null-terminated")) { + strings += "\x00"; + } + + const regex = new XRegExp(strings, "ig"); + + return search(input, regex, null, displayTotal); + } + +} + +export default Strings; diff --git a/src/core/operations/StripHTMLTags.mjs b/src/core/operations/StripHTMLTags.mjs new file mode 100644 index 00000000..f1b7b08e --- /dev/null +++ b/src/core/operations/StripHTMLTags.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Strip HTML tags operation + */ +class StripHTMLTags extends Operation { + + /** + * StripHTMLTags constructor + */ + constructor() { + super(); + + this.name = "Strip HTML tags"; + this.module = "Default"; + this.description = "Removes all HTML tags from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Remove indentation", + "type": "boolean", + "value": true + }, + { + "name": "Remove excess line breaks", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [removeIndentation, removeLineBreaks] = args; + + input = Utils.stripHtmlTags(input); + + if (removeIndentation) { + input = input.replace(/\n[ \f\t]+/g, "\n"); + } + + if (removeLineBreaks) { + input = input + .replace(/^\s*\n/, "") // first line + .replace(/(\n\s*){2,}/g, "\n"); // all others + } + + return input; + } + +} + +export default StripHTMLTags; diff --git a/src/core/operations/StripHTTPHeaders.mjs b/src/core/operations/StripHTTPHeaders.mjs new file mode 100644 index 00000000..0473c2dd --- /dev/null +++ b/src/core/operations/StripHTTPHeaders.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Strip HTTP headers operation + */ +class StripHTTPHeaders extends Operation { + + /** + * StripHTTPHeaders constructor + */ + constructor() { + super(); + + this.name = "Strip HTTP headers"; + this.module = "Default"; + this.description = "Removes HTTP headers from a request or response by looking for the first instance of a double newline."; + this.infoURL = "https://wikipedia.org/wiki/Hypertext_Transfer_Protocol#Message_format"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let headerEnd = input.indexOf("\r\n\r\n"); + headerEnd = (headerEnd < 0) ? input.indexOf("\n\n") + 2 : headerEnd + 4; + + return (headerEnd < 2) ? input : input.slice(headerEnd, input.length); + } + +} + +export default StripHTTPHeaders; diff --git a/src/core/operations/Subsection.mjs b/src/core/operations/Subsection.mjs new file mode 100644 index 00000000..8133d31c --- /dev/null +++ b/src/core/operations/Subsection.mjs @@ -0,0 +1,153 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import XRegExp from "xregexp"; +import Operation from "../Operation"; +import Recipe from "../Recipe"; +import Dish from "../Dish"; + +/** + * Subsection operation + */ +class Subsection extends Operation { + + /** + * Subsection constructor + */ + constructor() { + super(); + + this.name = "Subsection"; + this.flowControl = true; + this.module = "Regex"; + this.description = "Select a part of the input data using a regular expression (regex), and run all subsequent operations on each match separately.

You can use up to one capture group, where the recipe will only be run on the data in the capture group. If there's more than one capture group, only the first one will be operated on."; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Section (regex)", + "type": "string", + "value": "" + }, + { + "name": "Case sensitive matching", + "type": "boolean", + "value": true + }, + { + "name": "Global matching", + "type": "boolean", + "value": true + }, + { + "name": "Ignore errors", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on + * @param {Operation[]} state.opList - The list of operations in the recipe + * @returns {Object} - The updated state of the recipe + */ + async run(state) { + const opList = state.opList, + inputType = opList[state.progress].inputType, + outputType = opList[state.progress].outputType, + input = await state.dish.get(inputType), + ings = opList[state.progress].ingValues, + [section, caseSensitive, global, ignoreErrors] = ings, + subOpList = []; + + if (input && section !== "") { + // Create subOpList for each tranche to operate on + // all remaining operations unless we encounter a Merge + for (let i = state.progress + 1; i < opList.length; i++) { + if (opList[i].name === "Merge" && !opList[i].disabled) { + break; + } else { + subOpList.push(opList[i]); + } + } + + let flags = "", + inOffset = 0, + output = "", + m, + progress = 0; + + if (!caseSensitive) flags += "i"; + if (global) flags += "g"; + + const regex = new XRegExp(section, flags), + recipe = new Recipe(); + + recipe.addOperations(subOpList); + state.forkOffset += state.progress + 1; + + // Take a deep(ish) copy of the ingredient values + const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues))); + let matched = false; + + // Run recipe over each match + while ((m = regex.exec(input))) { + matched = true; + // Add up to match + let matchStr = m[0]; + + if (m.length === 1) { // No capture groups + output += input.slice(inOffset, m.index); + inOffset = m.index + m[0].length; + } else if (m.length >= 2) { + matchStr = m[1]; + + // Need to add some of the matched string that isn't in the capture group + output += input.slice(inOffset, m.index + m[0].indexOf(m[1])); + // Set i to be after the end of the first capture group + inOffset = m.index + m[0].indexOf(m[1]) + m[1].length; + } + + // Baseline ing values for each tranche so that registers are reset + subOpList.forEach((op, i) => { + op.ingValues = JSON.parse(JSON.stringify(ingValues[i])); + }); + + const dish = new Dish(); + dish.set(matchStr, inputType); + + try { + progress = await recipe.execute(dish, 0, state); + } catch (err) { + if (!ignoreErrors) { + throw err; + } + progress = err.progress + 1; + } + output += await dish.get(outputType); + if (!regex.global) break; + } + + // If no matches were found, advance progress to after a Merge op + // Otherwise, the operations below Subsection will be run on all the input data + if (!matched) { + state.progress += subOpList.length + 1; + } + + output += input.slice(inOffset); + state.progress += progress; + state.dish.set(output, outputType); + } + return state; + } + +} + +export default Subsection; diff --git a/src/core/operations/Substitute.mjs b/src/core/operations/Substitute.mjs new file mode 100644 index 00000000..6b3a2e8b --- /dev/null +++ b/src/core/operations/Substitute.mjs @@ -0,0 +1,66 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Substitute operation + */ +class Substitute extends Operation { + + /** + * Substitute constructor + */ + constructor() { + super(); + + this.name = "Substitute"; + this.module = "Default"; + this.description = "A substitution cipher allowing you to specify bytes to replace with other byte values. This can be used to create Caesar ciphers but is more powerful as any byte value can be substituted, not just letters, and the substitution values need not be in order.

Enter the bytes you want to replace in the Plaintext field and the bytes to replace them with in the Ciphertext field.

Non-printable bytes can be specified using string escape notation. For example, a line feed character can be written as either \\n or \\x0a.

Byte ranges can be specified using a hyphen. For example, the sequence 0123456789 can be written as 0-9.

Note that blackslash characters are used to escape special characters, so will need to be escaped themselves if you want to use them on their own (e.g.\\\\)."; + this.infoURL = "https://wikipedia.org/wiki/Substitution_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Plaintext", + "type": "binaryString", + "value": "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + { + "name": "Ciphertext", + "type": "binaryString", + "value": "XYZABCDEFGHIJKLMNOPQRSTUVW" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const plaintext = Utils.expandAlphRange(args[0]).join(""), + ciphertext = Utils.expandAlphRange(args[1]).join(""); + let output = "", + index = -1; + + if (plaintext.length !== ciphertext.length) { + output = "Warning: Plaintext and Ciphertext lengths differ\n\n"; + } + + for (let i = 0; i < input.length; i++) { + index = plaintext.indexOf(input[i]); + output += index > -1 && index < ciphertext.length ? ciphertext[index] : input[i]; + } + + return output; + } + +} + +export default Substitute; diff --git a/src/core/operations/Subtract.mjs b/src/core/operations/Subtract.mjs new file mode 100644 index 00000000..10de142b --- /dev/null +++ b/src/core/operations/Subtract.mjs @@ -0,0 +1,52 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { sub, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + + +/** + * Subtract operation + */ +class Subtract extends Operation { + + /** + * Subtract constructor + */ + constructor() { + super(); + + this.name = "Subtract"; + this.module = "Default"; + this.description = "Subtracts a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 1.5"; + this.infoURL = "https://wikipedia.org/wiki/Subtraction"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = sub(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Subtract; diff --git a/src/core/operations/Sum.mjs b/src/core/operations/Sum.mjs new file mode 100644 index 00000000..18504106 --- /dev/null +++ b/src/core/operations/Sum.mjs @@ -0,0 +1,52 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { sum, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + + +/** + * Sum operation + */ +class Sum extends Operation { + + /** + * Sum constructor + */ + constructor() { + super(); + + this.name = "Sum"; + this.module = "Default"; + this.description = "Adds together a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 18.5"; + this.infoURL = "https://wikipedia.org/wiki/Summation"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = sum(createNumArray(input, args[0])); + return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN); + } + +} + +export default Sum; diff --git a/src/core/operations/SwapEndianness.mjs b/src/core/operations/SwapEndianness.mjs new file mode 100644 index 00000000..02958414 --- /dev/null +++ b/src/core/operations/SwapEndianness.mjs @@ -0,0 +1,138 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {toHex, fromHex} from "../lib/Hex"; +import OperationError from "../errors/OperationError"; + +/** + * Swap endianness operation + */ +class SwapEndianness extends Operation { + + /** + * SwapEndianness constructor + */ + constructor() { + super(); + + this.name = "Swap endianness"; + this.module = "Default"; + this.description = "Switches the data from big-endian to little-endian or vice-versa. Data can be read in as hexadecimal or raw bytes. It will be returned in the same format as it is entered."; + this.infoURL = "https://wikipedia.org/wiki/Endianness"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Data format", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Word length (bytes)", + "type": "number", + "value": 4 + }, + { + "name": "Pad incomplete words", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [dataFormat, wordLength, padIncompleteWords] = args, + result = [], + words = []; + let i = 0, + j = 0, + data = []; + + if (wordLength <= 0) { + throw new OperationError("Word length must be greater than 0"); + } + + // Convert input to raw data based on specified data format + switch (dataFormat) { + case "Hex": + data = fromHex(input); + break; + case "Raw": + data = Utils.strToByteArray(input); + break; + default: + data = input; + } + + // Split up into words + for (i = 0; i < data.length; i += wordLength) { + const word = data.slice(i, i + wordLength); + + // Pad word if too short + if (padIncompleteWords && word.length < wordLength){ + for (j = word.length; j < wordLength; j++) { + word.push(0); + } + } + + words.push(word); + } + + // Swap endianness and flatten + for (i = 0; i < words.length; i++) { + j = words[i].length; + while (j--) { + result.push(words[i][j]); + } + } + + // Convert data back to specified data format + switch (dataFormat) { + case "Hex": + return toHex(result); + case "Raw": + return Utils.byteArrayToUtf8(result); + default: + return result; + } + } + + /** + * Highlight Swap endianness + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Swap endianness in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default SwapEndianness; diff --git a/src/core/operations/SymmetricDifference.mjs b/src/core/operations/SymmetricDifference.mjs new file mode 100644 index 00000000..249f8086 --- /dev/null +++ b/src/core/operations/SymmetricDifference.mjs @@ -0,0 +1,99 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Utils from "../Utils"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Set Symmetric Difference operation + */ +class SymmetricDifference extends Operation { + + /** + * Symmetric Difference constructor + */ + constructor() { + super(); + + this.name = "Symmetric Difference"; + this.module = "Default"; + this.description = "Calculates the symmetric difference of two sets."; + this.infoURL = "https://wikipedia.org/wiki/Symmetric_difference"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: Utils.escapeHtml("\\n\\n") + }, + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); + } + } + + /** + * Run the difference operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runSymmetricDifference(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get elements in set a that are not in set b + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runSetDifference(a, b) { + return a.filter((item) => { + return b.indexOf(item) === -1; + }); + } + + /** + * Get elements of each set that aren't in the other set. + * + * @param {Object[]} a + * @param {Object[]} b + * @return {Object[]} + */ + runSymmetricDifference(a, b) { + return this.runSetDifference(a, b) + .concat(this.runSetDifference(b, a)) + .join(this.itemDelimiter); + } + +} + +export default SymmetricDifference; diff --git a/src/core/operations/SyntaxHighlighter.mjs b/src/core/operations/SyntaxHighlighter.mjs new file mode 100644 index 00000000..a3f2d273 --- /dev/null +++ b/src/core/operations/SyntaxHighlighter.mjs @@ -0,0 +1,79 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import hljs from "highlight.js"; + +/** + * Syntax highlighter operation + */ +class SyntaxHighlighter extends Operation { + + /** + * SyntaxHighlighter constructor + */ + constructor() { + super(); + + this.name = "Syntax highlighter"; + this.module = "Code"; + this.description = "Adds syntax highlighting to a range of source code languages. Note that this will not indent the code. Use one of the 'Beautify' operations for that."; + this.infoURL = "https://wikipedia.org/wiki/Syntax_highlighting"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Language", + "type": "option", + "value": ["auto detect"].concat(hljs.listLanguages()) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const language = args[0]; + + if (language === "auto detect") { + return hljs.highlightAuto(input).value; + } + + return hljs.highlight(language, input, true).value; + } + + /** + * Highlight Syntax highlighter + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Syntax highlighter in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default SyntaxHighlighter; diff --git a/src/core/operations/TCPIPChecksum.mjs b/src/core/operations/TCPIPChecksum.mjs new file mode 100644 index 00000000..1da73359 --- /dev/null +++ b/src/core/operations/TCPIPChecksum.mjs @@ -0,0 +1,53 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * TCP/IP Checksum operation + */ +class TCPIPChecksum extends Operation { + + /** + * TCPIPChecksum constructor + */ + constructor() { + super(); + + this.name = "TCP/IP Checksum"; + 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.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let csum = 0; + + for (let i = 0; i < input.length; i++) { + if (i % 2 === 0) { + csum += (input[i] << 8); + } else { + csum += input[i]; + } + } + + csum = (csum >> 16) + (csum & 0xffff); + + return Utils.hex(0xffff - csum); + } + +} + +export default TCPIPChecksum; diff --git a/src/core/operations/Tail.mjs b/src/core/operations/Tail.mjs new file mode 100644 index 00000000..adbbbecc --- /dev/null +++ b/src/core/operations/Tail.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * Tail operation + */ +class Tail extends Operation { + + /** + * Tail constructor + */ + constructor() { + super(); + + this.name = "Tail"; + this.module = "Default"; + this.description = "Like the UNIX tail utility.
Gets the last n lines.
Optionally you can select all lines after line n by entering a negative value for n.
The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead."; + this.infoURL = "https://wikipedia.org/wiki/Tail_(Unix)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "Number", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let delimiter = args[0]; + const number = args[1]; + + delimiter = Utils.charRep(delimiter); + const splitInput = input.split(delimiter); + + return splitInput + .filter((line, lineIndex) => { + lineIndex += 1; + + if (number < 0) { + return lineIndex > -number; + } else { + return lineIndex > splitInput.length - number; + } + }) + .join(delimiter); + + } + +} + +export default Tail; diff --git a/src/core/operations/TakeBytes.mjs b/src/core/operations/TakeBytes.mjs new file mode 100644 index 00000000..4bf6d951 --- /dev/null +++ b/src/core/operations/TakeBytes.mjs @@ -0,0 +1,117 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Take bytes operation + */ +class TakeBytes extends Operation { + + /** + * TakeBytes constructor + */ + constructor() { + super(); + + this.name = "Take bytes"; + this.module = "Default"; + this.description = "Takes a slice of the specified number of bytes from the data. Negative values are allowed."; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Start", + "type": "number", + "value": 0 + }, + { + "name": "Length", + "type": "number", + "value": 5 + }, + { + "name": "Apply to each line", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + * + * @throws {OperationError} if invalid value + */ + run(input, args) { + let start = args[0], + length = args[1]; + const applyToEachLine = args[2]; + + if (!applyToEachLine) { + if (start < 0) { // Take from the end + start = input.byteLength + start; + } + + if (length < 0) { // Flip start point + start = start + length; + if (start < 0) { + start = input.byteLength + start; + length = start - length; + } else { + length = -length; + } + } + + return input.slice(start, start+length); + } + + // Split input into lines + const data = new Uint8Array(input); + const lines = []; + let line = [], + i; + + for (i = 0; i < data.length; i++) { + if (data[i] === 0x0a) { + lines.push(line); + line = []; + } else { + line.push(data[i]); + } + } + lines.push(line); + + let output = []; + let s = start, + l = length; + for (i = 0; i < lines.length; i++) { + if (s < 0) { // Take from the end + s = lines[i].length + s; + } + + if (l < 0) { // Flip start point + s = s + l; + if (s < 0) { + s = lines[i].length + s; + l = s - l; + } else { + l = -l; + } + } + output = output.concat(lines[i].slice(s, s+l)); + output.push(0x0a); + s = start; + l = length; + } + return new Uint8Array(output.slice(0, output.length-1)).buffer; + } + +} + +export default TakeBytes; diff --git a/src/core/operations/Tar.mjs b/src/core/operations/Tar.mjs new file mode 100644 index 00000000..84674bff --- /dev/null +++ b/src/core/operations/Tar.mjs @@ -0,0 +1,140 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Tar operation + */ +class Tar extends Operation { + + /** + * Tar constructor + */ + constructor() { + super(); + + this.name = "Tar"; + this.module = "Compression"; + this.description = "Packs the input into a tarball.

No support for multiple files at this time."; + this.infoURL = "https://wikipedia.org/wiki/Tar_(computing)"; + this.inputType = "byteArray"; + this.outputType = "File"; + this.args = [ + { + "name": "Filename", + "type": "string", + "value": "file.txt" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const Tarball = function() { + this.bytes = new Array(512); + this.position = 0; + }; + + Tarball.prototype.addEmptyBlock = function() { + const filler = new Array(512); + filler.fill(0); + this.bytes = this.bytes.concat(filler); + }; + + Tarball.prototype.writeBytes = function(bytes) { + const self = this; + + if (this.position + bytes.length > this.bytes.length) { + this.addEmptyBlock(); + } + + Array.prototype.forEach.call(bytes, function(b, i) { + if (typeof b.charCodeAt !== "undefined") { + b = b.charCodeAt(); + } + + self.bytes[self.position] = b; + self.position += 1; + }); + }; + + Tarball.prototype.writeEndBlocks = function() { + const numEmptyBlocks = 2; + for (let i = 0; i < numEmptyBlocks; i++) { + this.addEmptyBlock(); + } + }; + + const fileSize = input.length.toString(8).padStart(11, "0"); + const currentUnixTimestamp = Math.floor(Date.now() / 1000); + const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0"); + + const file = { + fileName: Utils.padBytesRight(args[0], 100), + fileMode: Utils.padBytesRight("0000664", 8), + ownerUID: Utils.padBytesRight("0", 8), + ownerGID: Utils.padBytesRight("0", 8), + size: Utils.padBytesRight(fileSize, 12), + lastModTime: Utils.padBytesRight(lastModTime, 12), + checksum: " ", + type: "0", + linkedFileName: Utils.padBytesRight("", 100), + USTARFormat: Utils.padBytesRight("ustar", 6), + version: "00", + ownerUserName: Utils.padBytesRight("", 32), + ownerGroupName: Utils.padBytesRight("", 32), + deviceMajor: Utils.padBytesRight("", 8), + deviceMinor: Utils.padBytesRight("", 8), + fileNamePrefix: Utils.padBytesRight("", 155), + }; + + let checksum = 0; + for (const key in file) { + const bytes = file[key]; + Array.prototype.forEach.call(bytes, function(b) { + if (typeof b.charCodeAt !== "undefined") { + checksum += b.charCodeAt(); + } else { + checksum += b; + } + }); + } + checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, "0"), 8); + file.checksum = checksum; + + const tarball = new Tarball(); + tarball.writeBytes(file.fileName); + tarball.writeBytes(file.fileMode); + tarball.writeBytes(file.ownerUID); + tarball.writeBytes(file.ownerGID); + tarball.writeBytes(file.size); + tarball.writeBytes(file.lastModTime); + tarball.writeBytes(file.checksum); + tarball.writeBytes(file.type); + tarball.writeBytes(file.linkedFileName); + tarball.writeBytes(file.USTARFormat); + tarball.writeBytes(file.version); + tarball.writeBytes(file.ownerUserName); + tarball.writeBytes(file.ownerGroupName); + tarball.writeBytes(file.deviceMajor); + tarball.writeBytes(file.deviceMinor); + tarball.writeBytes(file.fileNamePrefix); + tarball.writeBytes(Utils.padBytesRight("", 12)); + tarball.writeBytes(input); + tarball.writeEndBlocks(); + + return new File([new Uint8Array(tarball.bytes)], args[0]); + } + +} + +export default Tar; diff --git a/src/core/operations/TextEncodingBruteForce.mjs b/src/core/operations/TextEncodingBruteForce.mjs new file mode 100644 index 00000000..ee5f70d3 --- /dev/null +++ b/src/core/operations/TextEncodingBruteForce.mjs @@ -0,0 +1,92 @@ +/** + * @author Cynser + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import cptable from "../vendor/js-codepage/cptable.js"; +import {IO_FORMAT} from "../lib/ChrEnc"; + +/** + * Text Encoding Brute Force operation + */ +class TextEncodingBruteForce extends Operation { + + /** + * TextEncodingBruteForce constructor + */ + constructor() { + super(); + + this.name = "Text Encoding Brute Force"; + this.module = "Encodings"; + this.description = [ + "Enumerates all supported text encodings for the input, allowing you to quickly spot the correct one.", + "

", + "Supported charsets are:", + "
    ", + Object.keys(IO_FORMAT).map(e => `
  • ${e}
  • `).join("\n"), + "
" + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; + this.inputType = "string"; + this.outputType = "json"; + this.presentType = "html"; + this.args = [ + { + name: "Mode", + type: "option", + value: ["Encode", "Decode"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {json} + */ + run(input, args) { + const output = {}, + charsets = Object.keys(IO_FORMAT), + mode = args[0]; + + charsets.forEach(charset => { + try { + if (mode === "Decode") { + output[charset] = cptable.utils.decode(IO_FORMAT[charset], input); + } else { + output[charset] = Utils.arrayBufferToStr(cptable.utils.encode(IO_FORMAT[charset], input)); + } + } catch (err) { + output[charset] = "Could not decode."; + } + }); + + return output; + } + + /** + * Displays the encodings in an HTML table for web apps. + * + * @param {Object[]} encodings + * @returns {html} + */ + present(encodings) { + let table = ""; + + for (const enc in encodings) { + const value = Utils.printable(encodings[enc], true); + table += ``; + } + + table += "
EncodingValue
${enc}${value}
"; + return table; + } + +} + +export default TextEncodingBruteForce; diff --git a/src/core/operations/Tidy.js b/src/core/operations/Tidy.js deleted file mode 100755 index fed56730..00000000 --- a/src/core/operations/Tidy.js +++ /dev/null @@ -1,245 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Tidy operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Tidy = { - - /** - * @constant - * @default - */ - REMOVE_SPACES : true, - /** - * @constant - * @default - */ - REMOVE_CARIAGE_RETURNS : true, - /** - * @constant - * @default - */ - REMOVE_LINE_FEEDS : true, - /** - * @constant - * @default - */ - REMOVE_TABS : true, - /** - * @constant - * @default - */ - REMOVE_FORM_FEEDS : true, - /** - * @constant - * @default - */ - REMOVE_FULL_STOPS : false, - - /** - * Remove whitespace operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRemoveWhitespace: function (input, args) { - let removeSpaces = args[0], - removeCariageReturns = args[1], - removeLineFeeds = args[2], - removeTabs = args[3], - removeFormFeeds = args[4], - removeFullStops = args[5], - data = input; - - if (removeSpaces) data = data.replace(/ /g, ""); - if (removeCariageReturns) data = data.replace(/\r/g, ""); - if (removeLineFeeds) data = data.replace(/\n/g, ""); - if (removeTabs) data = data.replace(/\t/g, ""); - if (removeFormFeeds) data = data.replace(/\f/g, ""); - if (removeFullStops) data = data.replace(/\./g, ""); - return data; - }, - - - /** - * Remove null bytes operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRemoveNulls: function (input, args) { - const output = []; - for (let i = 0; i < input.length; i++) { - if (input[i] !== 0) output.push(input[i]); - } - return output; - }, - - - /** - * @constant - * @default - */ - APPLY_TO_EACH_LINE : false, - /** - * @constant - * @default - */ - DROP_START : 0, - /** - * @constant - * @default - */ - DROP_LENGTH : 5, - - /** - * Drop bytes operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runDropBytes: function(input, args) { - let start = args[0], - length = args[1], - applyToEachLine = args[2]; - - if (start < 0 || length < 0) - throw "Error: Invalid value"; - - if (!applyToEachLine) - return input.slice(0, start).concat(input.slice(start+length, input.length)); - - // Split input into lines - let lines = [], - line = [], - i; - - for (i = 0; i < input.length; i++) { - if (input[i] === 0x0a) { - lines.push(line); - line = []; - } else { - line.push(input[i]); - } - } - lines.push(line); - - let output = []; - for (i = 0; i < lines.length; i++) { - output = output.concat(lines[i].slice(0, start).concat(lines[i].slice(start+length, lines[i].length))); - output.push(0x0a); - } - return output.slice(0, output.length-1); - }, - - - /** - * @constant - * @default - */ - TAKE_START: 0, - /** - * @constant - * @default - */ - TAKE_LENGTH: 5, - - /** - * Take bytes operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runTakeBytes: function(input, args) { - let start = args[0], - length = args[1], - applyToEachLine = args[2]; - - if (start < 0 || length < 0) - throw "Error: Invalid value"; - - if (!applyToEachLine) - return input.slice(start, start+length); - - // Split input into lines - let lines = [], - line = []; - let i; - - for (i = 0; i < input.length; i++) { - if (input[i] === 0x0a) { - lines.push(line); - line = []; - } else { - line.push(input[i]); - } - } - lines.push(line); - - let output = []; - for (i = 0; i < lines.length; i++) { - output = output.concat(lines[i].slice(start, start+length)); - output.push(0x0a); - } - return output.slice(0, output.length-1); - }, - - - /** - * @constant - * @default - */ - PAD_POSITION : ["Start", "End"], - /** - * @constant - * @default - */ - PAD_LENGTH : 5, - /** - * @constant - * @default - */ - PAD_CHAR : " ", - - /** - * Pad lines operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runPad: function(input, args) { - let position = args[0], - len = args[1], - chr = args[2], - lines = input.split("\n"), - output = "", - i = 0; - - if (position === "Start") { - for (i = 0; i < lines.length; i++) { - output += Utils.padLeft(lines[i], lines[i].length+len, chr) + "\n"; - } - } else if (position === "End") { - for (i = 0; i < lines.length; i++) { - output += Utils.padRight(lines[i], lines[i].length+len, chr) + "\n"; - } - } - - return output.slice(0, output.length-1); - }, - -}; - -export default Tidy; diff --git a/src/core/operations/ToBCD.mjs b/src/core/operations/ToBCD.mjs new file mode 100644 index 00000000..c8fd0c89 --- /dev/null +++ b/src/core/operations/ToBCD.mjs @@ -0,0 +1,142 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD"; +import BigNumber from "bignumber.js"; + +/** + * To BCD operation + */ +class ToBCD extends Operation { + + /** + * ToBCD constructor + */ + constructor() { + super(); + + this.name = "To BCD"; + this.module = "Default"; + this.description = "Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign"; + this.infoURL = "https://wikipedia.org/wiki/Binary-coded_decimal"; + this.inputType = "BigNumber"; + this.outputType = "string"; + this.args = [ + { + "name": "Scheme", + "type": "option", + "value": ENCODING_SCHEME + }, + { + "name": "Packed", + "type": "boolean", + "value": true + }, + { + "name": "Signed", + "type": "boolean", + "value": false + }, + { + "name": "Output format", + "type": "option", + "value": FORMAT + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.isNaN()) + throw new OperationError("Invalid input"); + if (!input.integerValue(BigNumber.ROUND_DOWN).isEqualTo(input)) + throw new OperationError("Fractional values are not supported by BCD"); + + const encoding = ENCODING_LOOKUP[args[0]], + packed = args[1], + signed = args[2], + outputFormat = args[3]; + + // Split input number up into separate digits + const digits = input.toFixed().split(""); + + if (digits[0] === "-" || digits[0] === "+") { + digits.shift(); + } + + let nibbles = []; + + digits.forEach(d => { + const n = parseInt(d, 10); + nibbles.push(encoding[n]); + }); + + if (signed) { + if (packed && digits.length % 2 === 0) { + // If there are an even number of digits, we add a leading 0 so + // that the sign nibble doesn't sit in its own byte, leading to + // ambiguity around whether the number ends with a 0 or not. + nibbles.unshift(encoding[0]); + } + + nibbles.push(input > 0 ? 12 : 13); + // 12 ("C") for + (credit) + // 13 ("D") for - (debit) + } + + let bytes = []; + + if (packed) { + let encoded = 0, + little = false; + + nibbles.forEach(n => { + encoded ^= little ? n : (n << 4); + if (little) { + bytes.push(encoded); + encoded = 0; + } + little = !little; + }); + + if (little) bytes.push(encoded); + } else { + bytes = nibbles; + + // Add null high nibbles + nibbles = nibbles.map(n => { + return [0, n]; + }).reduce((a, b) => { + return a.concat(b); + }); + } + + // Output + switch (outputFormat) { + case "Nibbles": + return nibbles.map(n => { + return n.toString(2).padStart(4, "0"); + }).join(" "); + case "Bytes": + return bytes.map(b => { + return b.toString(2).padStart(8, "0"); + }).join(" "); + case "Raw": + default: + return Utils.byteArrayToChars(bytes); + } + } + +} + +export default ToBCD; diff --git a/src/core/operations/ToBase.mjs b/src/core/operations/ToBase.mjs new file mode 100644 index 00000000..93107903 --- /dev/null +++ b/src/core/operations/ToBase.mjs @@ -0,0 +1,54 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * To Base operation + */ +class ToBase extends Operation { + + /** + * ToBase constructor + */ + constructor() { + super(); + + this.name = "To Base"; + this.module = "Default"; + this.description = "Converts a decimal number to a given numerical base."; + this.infoURL = "https://wikipedia.org/wiki/Radix"; + this.inputType = "BigNumber"; + this.outputType = "string"; + this.args = [ + { + "name": "Radix", + "type": "number", + "value": 36 + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) { + throw new OperationError("Error: Input must be a number"); + } + const radix = args[0]; + if (radix < 2 || radix > 36) { + throw new OperationError("Error: Radix argument must be between 2 and 36"); + } + return input.toString(radix); + } + +} + +export default ToBase; diff --git a/src/core/operations/ToBase32.mjs b/src/core/operations/ToBase32.mjs new file mode 100644 index 00000000..fd0a6136 --- /dev/null +++ b/src/core/operations/ToBase32.mjs @@ -0,0 +1,86 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * To Base32 operation + */ +class ToBase32 extends Operation { + + /** + * ToBase32 constructor + */ + constructor() { + super(); + + this.name = "To Base32"; + 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.outputType = "string"; + this.args = [ + { + name: "Alphabet", + type: "binaryString", + value: "A-Z2-7=" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + + const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; + let output = "", + chr1, chr2, chr3, chr4, chr5, + enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8, + i = 0; + + while (i < input.length) { + chr1 = input[i++]; + chr2 = input[i++]; + chr3 = input[i++]; + chr4 = input[i++]; + chr5 = input[i++]; + + enc1 = chr1 >> 3; + enc2 = ((chr1 & 7) << 2) | (chr2 >> 6); + enc3 = (chr2 >> 1) & 31; + enc4 = ((chr2 & 1) << 4) | (chr3 >> 4); + enc5 = ((chr3 & 15) << 1) | (chr4 >> 7); + enc6 = (chr4 >> 2) & 31; + enc7 = ((chr4 & 3) << 3) | (chr5 >> 5); + enc8 = chr5 & 31; + + if (isNaN(chr2)) { + enc3 = enc4 = enc5 = enc6 = enc7 = enc8 = 32; + } else if (isNaN(chr3)) { + enc5 = enc6 = enc7 = enc8 = 32; + } else if (isNaN(chr4)) { + enc6 = enc7 = enc8 = 32; + } else if (isNaN(chr5)) { + enc8 = 32; + } + + output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) + + alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) + + alphabet.charAt(enc7) + alphabet.charAt(enc8); + } + + return output; + } + +} + +export default ToBase32; diff --git a/src/core/operations/ToBase58.mjs b/src/core/operations/ToBase58.mjs new file mode 100644 index 00000000..3e2c6a60 --- /dev/null +++ b/src/core/operations/ToBase58.mjs @@ -0,0 +1,91 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {ALPHABET_OPTIONS} from "../lib/Base58"; + +/** + * To Base58 operation + */ +class ToBase58 extends Operation { + + /** + * ToBase58 constructor + */ + constructor() { + super(); + + this.name = "To Base58"; + 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.

This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).

e.g. hello world becomes StV1DL6CwTryKyV

Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc)."; + this.infoURL = "https://wikipedia.org/wiki/Base58"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Alphabet", + "type": "editableOption", + "value": ALPHABET_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let alphabet = args[0] || ALPHABET_OPTIONS[0].value, + result = [0]; + + alphabet = Utils.expandAlphRange(alphabet).join(""); + + if (alphabet.length !== 58 || + [].unique.call(alphabet).length !== 58) { + throw new OperationError("Error: alphabet must be of length 58"); + } + + if (input.length === 0) return ""; + + let zeroPrefix = 0; + for (let i = 0; i < input.length && input[i] === 0; i++) { + zeroPrefix++; + } + + input.forEach(function(b) { + let carry = (result[0] << 8) + b; + result[0] = carry % 58; + carry = (carry / 58) | 0; + + for (let i = 1; i < result.length; i++) { + carry += result[i] << 8; + result[i] = carry % 58; + carry = (carry / 58) | 0; + } + + while (carry > 0) { + result.push(carry % 58); + carry = (carry / 58) | 0; + } + }); + + result = result.map(function(b) { + return alphabet[b]; + }).reverse().join(""); + + while (zeroPrefix--) { + result = alphabet[0] + result; + } + + return result; + } + +} + +export default ToBase58; diff --git a/src/core/operations/ToBase62.mjs b/src/core/operations/ToBase62.mjs new file mode 100644 index 00000000..51f89ecd --- /dev/null +++ b/src/core/operations/ToBase62.mjs @@ -0,0 +1,58 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import BigNumber from "bignumber.js"; +import Utils from "../Utils"; +import {toHexFast} from "../lib/Hex"; + +/** + * To Base62 operation + */ +class ToBase62 extends Operation { + + /** + * ToBase62 constructor + */ + constructor() { + super(); + + this.name = "To Base62"; + 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.outputType = "string"; + this.args = [ + { + name: "Alphabet", + type: "string", + value: "0-9A-Za-z" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.length < 1) return ""; + + const ALPHABET = Utils.expandAlphRange(args[0]).join(""); + const BN = BigNumber.clone({ ALPHABET }); + + input = toHexFast(input).toUpperCase(); + + const number = new BN(input, 16); + + return number.toString(62); + } + +} + +export default ToBase62; diff --git a/src/core/operations/ToBase64.mjs b/src/core/operations/ToBase64.mjs new file mode 100644 index 00000000..cffc3140 --- /dev/null +++ b/src/core/operations/ToBase64.mjs @@ -0,0 +1,77 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {toBase64, ALPHABET_OPTIONS} from "../lib/Base64"; + +/** + * To Base64 operation + */ +class ToBase64 extends Operation { + + /** + * ToBase64 constructor + */ + constructor() { + super(); + + this.name = "To Base64"; + this.module = "Default"; + this.description = "Base64 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.

This operation encodes raw data into an ASCII Base64 string.

e.g. hello becomes aGVsbG8="; + this.infoURL = "https://wikipedia.org/wiki/Base64"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Alphabet", + type: "editableOption", + value: ALPHABET_OPTIONS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const alphabet = args[0]; + return toBase64(new Uint8Array(input), alphabet); + } + + /** + * Highlight to Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + pos[0].start = Math.floor(pos[0].start / 3 * 4); + pos[0].end = Math.ceil(pos[0].end / 3 * 4); + return pos; + } + + /** + * Highlight from Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + pos[0].start = Math.ceil(pos[0].start / 4 * 3); + pos[0].end = Math.floor(pos[0].end / 4 * 3); + return pos; + } +} + +export default ToBase64; diff --git a/src/core/operations/ToBase85.mjs b/src/core/operations/ToBase85.mjs new file mode 100644 index 00000000..97cc2e72 --- /dev/null +++ b/src/core/operations/ToBase85.mjs @@ -0,0 +1,93 @@ +/** + * @author PenguinGeorge [george@penguingeorge.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; +import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85"; + +/** + * To Base85 operation + */ +class ToBase85 extends Operation { + + /** + * To Base85 constructor + */ + constructor() { + super(); + + this.name = "To Base85"; + this.module = "Default"; + this.description = "Base85 (also called Ascii85) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.

This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).

e.g. hello world becomes BOu!rD]j7BEbo7

Base85 is commonly used in Adobe's PostScript and PDF file formats.

Options
Alphabet
  • Standard - The standard alphabet, referred to as Ascii85
  • Z85 (ZeroMQ) - A string-safe variant of Base85, which avoids quote marks and backslash characters
  • IPv6 - A variant of Base85 suitable for encoding IPv6 addresses (RFC 1924)
Include delimiter
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.outputType = "string"; + this.args = [ + { + name: "Alphabet", + type: "editableOption", + value: ALPHABET_OPTIONS + }, + { + name: "Include delimeter", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const alphabet = Utils.expandAlphRange(args[0]).join(""), + encoding = alphabetName(alphabet), + includeDelim = args[1]; + let result = ""; + + if (alphabet.length !== 85 || + [].unique.call(alphabet).length !== 85) { + throw new OperationError("Error: Alphabet must be of length 85"); + } + + if (input.length === 0) return ""; + + let block; + for (let i = 0; i < input.length; i += 4) { + block = ( + ((input[i]) << 24) + + ((input[i + 1] || 0) << 16) + + ((input[i + 2] || 0) << 8) + + ((input[i + 3] || 0)) + ) >>> 0; + + if (encoding !== "Standard" || block > 0) { + let digits = []; + for (let j = 0; j < 5; j++) { + digits.push(block % 85); + block = Math.floor(block / 85); + } + + digits = digits.reverse(); + + if (input.length < i + 4) { + digits.splice(input.length - (i + 4), 4); + } + + result += digits.map(digit => alphabet[digit]).join(""); + } else { + result += (encoding === "Standard") ? "z" : null; + } + } + + return includeDelim ? `<~${result}~>` : result; + } +} + +export default ToBase85; diff --git a/src/core/operations/ToBinary.mjs b/src/core/operations/ToBinary.mjs new file mode 100644 index 00000000..af9087d1 --- /dev/null +++ b/src/core/operations/ToBinary.mjs @@ -0,0 +1,81 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {BIN_DELIM_OPTIONS} from "../lib/Delim"; +import {toBinary} from "../lib/Binary"; + +/** + * To Binary operation + */ +class ToBinary extends Operation { + + /** + * ToBinary constructor + */ + constructor() { + super(); + + this.name = "To Binary"; + this.module = "Default"; + this.description = "Displays the input data as a binary string.

e.g. Hi becomes 01001000 01101001"; + this.infoURL = "https://wikipedia.org/wiki/Binary_code"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": BIN_DELIM_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return toBinary(input, args[0]); + } + + /** + * Highlight To Binary + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const delim = Utils.charRep(args[0] || "Space"); + pos[0].start = pos[0].start * (8 + delim.length); + pos[0].end = pos[0].end * (8 + delim.length) - delim.length; + return pos; + } + + /** + * Highlight To Binary in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const delim = Utils.charRep(args[0] || "Space"); + pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length)); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length)); + return pos; + } + +} + +export default ToBinary; diff --git a/src/core/operations/ToBraille.mjs b/src/core/operations/ToBraille.mjs new file mode 100644 index 00000000..9530f9d3 --- /dev/null +++ b/src/core/operations/ToBraille.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {BRAILLE_LOOKUP} from "../lib/Braille"; + +/** + * To Braille operation + */ +class ToBraille extends Operation { + + /** + * ToBraille constructor + */ + constructor() { + super(); + + this.name = "To Braille"; + this.module = "Default"; + this.description = "Converts text to six-dot braille symbols."; + this.infoURL = "https://wikipedia.org/wiki/Braille"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.split("").map(c => { + const idx = BRAILLE_LOOKUP.ascii.indexOf(c.toUpperCase()); + return idx < 0 ? c : BRAILLE_LOOKUP.dot6[idx]; + }).join(""); + } + + /** + * Highlight To Braille + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight To Braille in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default ToBraille; diff --git a/src/core/operations/ToCamelCase.mjs b/src/core/operations/ToCamelCase.mjs new file mode 100644 index 00000000..3553810e --- /dev/null +++ b/src/core/operations/ToCamelCase.mjs @@ -0,0 +1,54 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import camelCase from "lodash/camelCase"; +import Operation from "../Operation"; +import { replaceVariableNames } from "../lib/Code"; + +/** + * To Camel case operation + */ +class ToCamelCase extends Operation { + + /** + * ToCamelCase constructor + */ + constructor() { + super(); + + this.name = "To Camel case"; + this.module = "Code"; + this.description = "Converts the input string to camel case.\n

\nCamel case is all lower case except letters after word boundaries which are uppercase.\n

\ne.g. thisIsCamelCase\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; + this.infoURL = "https://wikipedia.org/wiki/Camel_case"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Attempt to be context aware", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, camelCase); + } else { + return camelCase(input); + } + } + +} + +export default ToCamelCase; diff --git a/src/core/operations/ToCaseInsensitiveRegex.mjs b/src/core/operations/ToCaseInsensitiveRegex.mjs new file mode 100644 index 00000000..0d606a05 --- /dev/null +++ b/src/core/operations/ToCaseInsensitiveRegex.mjs @@ -0,0 +1,39 @@ +/** + * @author masq [github.cyberchef@masq.cc] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * To Case Insensitive Regex operation + */ +class ToCaseInsensitiveRegex extends Operation { + + /** + * ToCaseInsensitiveRegex constructor + */ + constructor() { + super(); + + this.name = "To Case Insensitive Regex"; + this.module = "Default"; + this.description = "Converts a case-sensitive regex string into a case-insensitive regex string in case the i flag is unavailable to you.

e.g. Mozilla/[0-9].[0-9] .* becomes [mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .*"; + this.infoURL = "https://wikipedia.org/wiki/Regular_expression"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.replace(/[a-z]/ig, m => `[${m.toLowerCase()}${m.toUpperCase()}]`); + } +} + +export default ToCaseInsensitiveRegex; diff --git a/src/core/operations/ToCharcode.mjs b/src/core/operations/ToCharcode.mjs new file mode 100644 index 00000000..11006685 --- /dev/null +++ b/src/core/operations/ToCharcode.mjs @@ -0,0 +1,86 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {DELIM_OPTIONS} from "../lib/Delim"; +import OperationError from "../errors/OperationError"; + +/** + * To Charcode operation + */ +class ToCharcode extends Operation { + + /** + * ToCharcode constructor + */ + constructor() { + super(); + + this.name = "To Charcode"; + this.module = "Default"; + this.description = "Converts text to its unicode character code equivalent.

e.g. Γειά σου becomes 0393 03b5 03b9 03ac 20 03c3 03bf 03c5"; + this.infoURL = "https://wikipedia.org/wiki/Plane_(Unicode)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "Base", + "type": "number", + "value": 16 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if base argument out of range + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"), + base = args[1]; + let output = "", + padding = 2, + ordinal; + + if (base < 2 || base > 36) { + throw new OperationError("Error: Base argument must be between 2 and 36"); + } + + const charcode = Utils.strToCharcode(input); + for (let i = 0; i < charcode.length; i++) { + ordinal = charcode[i]; + + if (base === 16) { + if (ordinal < 256) padding = 2; + else if (ordinal < 65536) padding = 4; + else if (ordinal < 16777216) padding = 6; + else if (ordinal < 4294967296) padding = 8; + else padding = 2; + + if (padding > 2 && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); + + output += Utils.hex(ordinal, padding) + delim; + } else { + if (ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); + output += ordinal.toString(base) + delim; + } + } + + return output.slice(0, -delim.length); + } + +} + +export default ToCharcode; diff --git a/src/core/operations/ToDecimal.mjs b/src/core/operations/ToDecimal.mjs new file mode 100644 index 00000000..6df51ae2 --- /dev/null +++ b/src/core/operations/ToDecimal.mjs @@ -0,0 +1,58 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {DELIM_OPTIONS} from "../lib/Delim"; + + +/** + * To Decimal operation + */ +class ToDecimal extends Operation { + + /** + * ToDecimal constructor + */ + constructor() { + super(); + + this.name = "To Decimal"; + this.module = "Default"; + this.description = "Converts the input data to an ordinal integer array.

e.g. Hello becomes 72 101 108 108 111"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "Support signed values", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + signed = args[1]; + if (signed) { + input = input.map(v => v > 0x7F ? v - 0xFF - 1 : v); + } + return input.join(delim); + } + +} + +export default ToDecimal; diff --git a/src/core/operations/ToHTMLEntity.mjs b/src/core/operations/ToHTMLEntity.mjs new file mode 100644 index 00000000..53ec4e34 --- /dev/null +++ b/src/core/operations/ToHTMLEntity.mjs @@ -0,0 +1,346 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * To HTML Entity operation + */ +class ToHTMLEntity extends Operation { + + /** + * ToHTMLEntity constructor + */ + constructor() { + super(); + + this.name = "To HTML Entity"; + this.module = "Default"; + this.description = "Converts characters to HTML entities

e.g. & becomes &amp;"; + this.infoURL = "https://wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Convert all characters", + "type": "boolean", + "value": false + }, + { + "name": "Convert to", + "type": "option", + "value": ["Named entities where possible", "Numeric entities", "Hex entities"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const convertAll = args[0], + numeric = args[1] === "Numeric entities", + hexa = args[1] === "Hex entities"; + + const charcodes = Utils.strToCharcode(input); + let output = ""; + + for (let i = 0; i < charcodes.length; i++) { + if (convertAll && numeric) { + output += "&#" + charcodes[i] + ";"; + } else if (convertAll && hexa) { + output += "&#x" + Utils.hex(charcodes[i]) + ";"; + } else if (convertAll) { + output += byteToEntity[charcodes[i]] || "&#" + charcodes[i] + ";"; + } else if (numeric) { + if (charcodes[i] > 255 || byteToEntity.hasOwnProperty(charcodes[i])) { + output += "&#" + charcodes[i] + ";"; + } else { + output += Utils.chr(charcodes[i]); + } + } else if (hexa) { + if (charcodes[i] > 255 || byteToEntity.hasOwnProperty(charcodes[i])) { + output += "&#x" + Utils.hex(charcodes[i]) + ";"; + } else { + output += Utils.chr(charcodes[i]); + } + } else { + output += byteToEntity[charcodes[i]] || ( + charcodes[i] > 255 ? + "&#" + charcodes[i] + ";" : + Utils.chr(charcodes[i]) + ); + } + } + return output; + } + +} + +/** + * Lookup table to translate byte values to their HTML entity codes. + */ +const byteToEntity = { + 34: """, + 38: "&", + 39: "'", + 60: "<", + 62: ">", + 160: " ", + 161: "¡", + 162: "¢", + 163: "£", + 164: "¤", + 165: "¥", + 166: "¦", + 167: "§", + 168: "¨", + 169: "©", + 170: "ª", + 171: "«", + 172: "¬", + 173: "­", + 174: "®", + 175: "¯", + 176: "°", + 177: "±", + 178: "²", + 179: "³", + 180: "´", + 181: "µ", + 182: "¶", + 183: "·", + 184: "¸", + 185: "¹", + 186: "º", + 187: "»", + 188: "¼", + 189: "½", + 190: "¾", + 191: "¿", + 192: "À", + 193: "Á", + 194: "Â", + 195: "Ã", + 196: "Ä", + 197: "Å", + 198: "Æ", + 199: "Ç", + 200: "È", + 201: "É", + 202: "Ê", + 203: "Ë", + 204: "Ì", + 205: "Í", + 206: "Î", + 207: "Ï", + 208: "Ð", + 209: "Ñ", + 210: "Ò", + 211: "Ó", + 212: "Ô", + 213: "Õ", + 214: "Ö", + 215: "×", + 216: "Ø", + 217: "Ù", + 218: "Ú", + 219: "Û", + 220: "Ü", + 221: "Ý", + 222: "Þ", + 223: "ß", + 224: "à", + 225: "á", + 226: "â", + 227: "ã", + 228: "ä", + 229: "å", + 230: "æ", + 231: "ç", + 232: "è", + 233: "é", + 234: "ê", + 235: "ë", + 236: "ì", + 237: "í", + 238: "î", + 239: "ï", + 240: "ð", + 241: "ñ", + 242: "ò", + 243: "ó", + 244: "ô", + 245: "õ", + 246: "ö", + 247: "÷", + 248: "ø", + 249: "ù", + 250: "ú", + 251: "û", + 252: "ü", + 253: "ý", + 254: "þ", + 255: "ÿ", + 338: "Œ", + 339: "œ", + 352: "Š", + 353: "š", + 376: "Ÿ", + 402: "ƒ", + 710: "ˆ", + 732: "˜", + 913: "Α", + 914: "Β", + 915: "Γ", + 916: "Δ", + 917: "Ε", + 918: "Ζ", + 919: "Η", + 920: "Θ", + 921: "Ι", + 922: "Κ", + 923: "Λ", + 924: "Μ", + 925: "Ν", + 926: "Ξ", + 927: "Ο", + 928: "Π", + 929: "Ρ", + 931: "Σ", + 932: "Τ", + 933: "Υ", + 934: "Φ", + 935: "Χ", + 936: "Ψ", + 937: "Ω", + 945: "α", + 946: "β", + 947: "γ", + 948: "δ", + 949: "ε", + 950: "ζ", + 951: "η", + 952: "θ", + 953: "ι", + 954: "κ", + 955: "λ", + 956: "μ", + 957: "ν", + 958: "ξ", + 959: "ο", + 960: "π", + 961: "ρ", + 962: "ς", + 963: "σ", + 964: "τ", + 965: "υ", + 966: "φ", + 967: "χ", + 968: "ψ", + 969: "ω", + 977: "ϑ", + 978: "ϒ", + 982: "ϖ", + 8194: " ", + 8195: " ", + 8201: " ", + 8204: "‌", + 8205: "‍", + 8206: "‎", + 8207: "‏", + 8211: "–", + 8212: "—", + 8216: "‘", + 8217: "’", + 8218: "‚", + 8220: "“", + 8221: "”", + 8222: "„", + 8224: "†", + 8225: "‡", + 8226: "•", + 8230: "…", + 8240: "‰", + 8242: "′", + 8243: "″", + 8249: "‹", + 8250: "›", + 8254: "‾", + 8260: "⁄", + 8364: "€", + 8465: "ℑ", + 8472: "℘", + 8476: "ℜ", + 8482: "™", + 8501: "ℵ", + 8592: "←", + 8593: "↑", + 8594: "→", + 8595: "↓", + 8596: "↔", + 8629: "↵", + 8656: "⇐", + 8657: "⇑", + 8658: "⇒", + 8659: "⇓", + 8660: "⇔", + 8704: "∀", + 8706: "∂", + 8707: "∃", + 8709: "∅", + 8711: "∇", + 8712: "∈", + 8713: "∉", + 8715: "∋", + 8719: "∏", + 8721: "∑", + 8722: "−", + 8727: "∗", + 8730: "√", + 8733: "∝", + 8734: "∞", + 8736: "∠", + 8743: "∧", + 8744: "∨", + 8745: "∩", + 8746: "∪", + 8747: "∫", + 8756: "∴", + 8764: "∼", + 8773: "≅", + 8776: "≈", + 8800: "≠", + 8801: "≡", + 8804: "≤", + 8805: "≥", + 8834: "⊂", + 8835: "⊃", + 8836: "⊄", + 8838: "⊆", + 8839: "⊇", + 8853: "⊕", + 8855: "⊗", + 8869: "⊥", + 8901: "⋅", + 8942: "⋮", + 8968: "⌈", + 8969: "⌉", + 8970: "⌊", + 8971: "⌋", + 9001: "⟨", + 9002: "⟩", + 9674: "◊", + 9824: "♠", + 9827: "♣", + 9829: "♥", + 9830: "♦", +}; + +export default ToHTMLEntity; diff --git a/src/core/operations/ToHex.mjs b/src/core/operations/ToHex.mjs new file mode 100644 index 00000000..a7035d4f --- /dev/null +++ b/src/core/operations/ToHex.mjs @@ -0,0 +1,99 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {toHex, TO_HEX_DELIM_OPTIONS} from "../lib/Hex"; +import Utils from "../Utils"; + +/** + * To Hex operation + */ +class ToHex extends Operation { + + /** + * ToHex constructor + */ + constructor() { + super(); + + this.name = "To Hex"; + this.module = "Default"; + this.description = "Converts the input string to hexadecimal bytes separated by the specified delimiter.

e.g. The UTF-8 encoded string Γειά σου becomes ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a"; + this.infoURL = "https://wikipedia.org/wiki/Hexadecimal"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: TO_HEX_DELIM_OPTIONS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"); + return toHex(new Uint8Array(input), delim, 2); + } + + /** + * Highlight to Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const delim = Utils.charRep(args[0] || "Space"), + len = delim === "\r\n" ? 1 : delim.length; + + pos[0].start = pos[0].start * (2 + len); + pos[0].end = pos[0].end * (2 + len) - len; + + // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly + if (delim === "0x" || delim === "\\x") { + pos[0].start += 2; + pos[0].end += 2; + } + return pos; + } + + /** + * Highlight from Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const delim = Utils.charRep(args[0] || "Space"), + len = delim === "\r\n" ? 1 : delim.length, + width = len + 2; + + // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly + if (delim === "0x" || delim === "\\x") { + if (pos[0].start > 1) pos[0].start -= 2; + else pos[0].start = 0; + if (pos[0].end > 1) pos[0].end -= 2; + else pos[0].end = 0; + } + + pos[0].start = pos[0].start === 0 ? 0 : Math.round(pos[0].start / width); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / width); + return pos; + } +} + +export default ToHex; diff --git a/src/core/operations/ToHexContent.mjs b/src/core/operations/ToHexContent.mjs new file mode 100644 index 00000000..653c2224 --- /dev/null +++ b/src/core/operations/ToHexContent.mjs @@ -0,0 +1,82 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {toHex} from "../lib/Hex"; + +/** + * To Hex Content operation + */ +class ToHexContent extends Operation { + + /** + * ToHexContent constructor + */ + constructor() { + super(); + + this.name = "To Hex Content"; + 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.

e.g. foo=bar becomes foo|3d|bar."; + this.infoURL = "http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node32.html#SECTION00451000000000000000"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Convert", + "type": "option", + "value": ["Only special chars", "Only special chars including spaces", "All chars"] + }, + { + "name": "Print spaces between bytes", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const convert = args[0]; + const spaces = args[1]; + if (convert === "All chars") { + let result = "|" + toHex(input) + "|"; + if (!spaces) result = result.replace(/ /g, ""); + return result; + } + + let output = "", + inHex = false, + b; + const convertSpaces = convert === "Only special chars including spaces"; + for (let i = 0; i < input.length; i++) { + b = input[i]; + if ((b === 32 && convertSpaces) || (b < 48 && b !== 32) || (b > 57 && b < 65) || (b > 90 && b < 97) || b > 122) { + if (!inHex) { + output += "|"; + inHex = true; + } else if (spaces) output += " "; + output += toHex([b]); + } else { + if (inHex) { + output += "|"; + inHex = false; + } + output += Utils.chr(input[i]); + } + } + if (inHex) output += "|"; + return output; + } + +} + +export default ToHexContent; diff --git a/src/core/operations/ToHexdump.mjs b/src/core/operations/ToHexdump.mjs new file mode 100644 index 00000000..0fbec5ce --- /dev/null +++ b/src/core/operations/ToHexdump.mjs @@ -0,0 +1,184 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * To Hexdump operation + */ +class ToHexdump extends Operation { + + /** + * ToHexdump constructor + */ + constructor() { + super(); + + this.name = "To Hexdump"; + this.module = "Default"; + this.description = "Creates a hexdump of the input data, displaying both the hexadecimal values of each byte and an ASCII representation alongside."; + this.infoURL = "https://wikipedia.org/wiki/Hex_dump"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Width", + "type": "number", + "value": 16 + }, + { + "name": "Upper case hex", + "type": "boolean", + "value": false + }, + { + "name": "Include final length", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const data = new Uint8Array(input); + const [length, upperCase, includeFinalLength] = args; + const padding = 2; + + let output = ""; + for (let i = 0; i < data.length; i += length) { + const buff = data.slice(i, i+length); + let hexa = ""; + for (let j = 0; j < buff.length; j++) { + hexa += Utils.hex(buff[j], padding) + " "; + } + + let lineNo = Utils.hex(i, 8); + + if (upperCase) { + hexa = hexa.toUpperCase(); + lineNo = lineNo.toUpperCase(); + } + + output += lineNo + " " + + hexa.padEnd(length*(padding+1), " ") + + " |" + Utils.printable(Utils.byteArrayToChars(buff)).padEnd(buff.length, " ") + "|\n"; + + if (includeFinalLength && i+buff.length === data.length) { + output += Utils.hex(i+buff.length, 8) + "\n"; + } + } + + return output.slice(0, -1); + } + + /** + * Highlight To Hexdump + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + // Calculate overall selection + const w = args[0] || 16, + width = 14 + (w*4); + let line = Math.floor(pos[0].start / w), + offset = pos[0].start % w, + start = 0, + end = 0; + + pos[0].start = line*width + 10 + offset*3; + + line = Math.floor(pos[0].end / w); + offset = pos[0].end % w; + if (offset === 0) { + line--; + offset = w; + } + pos[0].end = line*width + 10 + offset*3 - 1; + + // Set up multiple selections for bytes + let startLineNum = Math.floor(pos[0].start / width); + const endLineNum = Math.floor(pos[0].end / width); + + if (startLineNum === endLineNum) { + pos.push(pos[0]); + } else { + start = pos[0].start; + end = (startLineNum+1) * width - w - 5; + pos.push({ start: start, end: end }); + while (end < pos[0].end) { + startLineNum++; + start = startLineNum * width + 10; + end = (startLineNum+1) * width - w - 5; + if (end > pos[0].end) end = pos[0].end; + pos.push({ start: start, end: end }); + } + } + + // Set up multiple selections for ASCII + const len = pos.length; + let lineNum = 0; + start = 0; + end = 0; + for (let i = 1; i < len; i++) { + lineNum = Math.floor(pos[i].start / width); + start = (((pos[i].start - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); + end = (((pos[i].end + 1 - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); + pos.push({ start: start, end: end }); + } + return pos; + } + + /** + * Highlight To Hexdump in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const w = args[0] || 16; + const width = 14 + (w*4); + + let line = Math.floor(pos[0].start / width); + let offset = pos[0].start % width; + + if (offset < 10) { // In line number section + pos[0].start = line*w; + } else if (offset > 10+(w*3)) { // In ASCII section + pos[0].start = (line+1)*w; + } else { // In byte section + pos[0].start = line*w + Math.floor((offset-10)/3); + } + + line = Math.floor(pos[0].end / width); + offset = pos[0].end % width; + + if (offset < 10) { // In line number section + pos[0].end = line*w; + } else if (offset > 10+(w*3)) { // In ASCII section + pos[0].end = (line+1)*w; + } else { // In byte section + pos[0].end = line*w + Math.ceil((offset-10)/3); + } + + return pos; + } + +} + +export default ToHexdump; diff --git a/src/core/operations/ToKebabCase.mjs b/src/core/operations/ToKebabCase.mjs new file mode 100644 index 00000000..b7a15497 --- /dev/null +++ b/src/core/operations/ToKebabCase.mjs @@ -0,0 +1,54 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import kebabCase from "lodash/kebabCase"; +import Operation from "../Operation"; +import { replaceVariableNames } from "../lib/Code"; + +/** + * To Kebab case operation + */ +class ToKebabCase extends Operation { + + /** + * ToKebabCase constructor + */ + constructor() { + super(); + + this.name = "To Kebab case"; + this.module = "Code"; + this.description = "Converts the input string to kebab case.\n

\nKebab case is all lower case with dashes as word boundaries.\n

\ne.g. this-is-kebab-case\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; + this.infoURL = "https://wikipedia.org/wiki/Kebab_case"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Attempt to be context aware", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, kebabCase); + } else { + return kebabCase(input); + } + } + +} + +export default ToKebabCase; diff --git a/src/core/operations/ToLowerCase.mjs b/src/core/operations/ToLowerCase.mjs new file mode 100644 index 00000000..f28380bc --- /dev/null +++ b/src/core/operations/ToLowerCase.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * To Lower case operation + */ +class ToLowerCase extends Operation { + + /** + * ToLowerCase constructor + */ + constructor() { + super(); + + this.name = "To Lower case"; + this.module = "Default"; + this.description = "Converts every character in the input to lower case."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.toLowerCase(); + } + + /** + * Highlight To Lower case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight To Lower case in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default ToLowerCase; diff --git a/src/core/operations/ToMessagePack.mjs b/src/core/operations/ToMessagePack.mjs new file mode 100644 index 00000000..3de1daa7 --- /dev/null +++ b/src/core/operations/ToMessagePack.mjs @@ -0,0 +1,52 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import notepack from "notepack.io"; + +/** + * To MessagePack operation + */ +class ToMessagePack extends Operation { + + /** + * ToMessagePack constructor + */ + constructor() { + super(); + + this.name = "To MessagePack"; + this.module = "Code"; + this.description = "Converts JSON to MessagePack encoded byte buffer. MessagePack is a computer data interchange format. It is a binary form for representing simple data structures like arrays and associative arrays."; + this.infoURL = "https://wikipedia.org/wiki/MessagePack"; + this.inputType = "JSON"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + try { + if (ENVIRONMENT_IS_WORKER()) { + return notepack.encode(input); + } else { + const res = notepack.encode(input); + // Safely convert from Node Buffer to ArrayBuffer using the correct view of the data + return (new Uint8Array(res)).buffer; + } + } catch (err) { + throw new OperationError(`Could not encode JSON to MessagePack: ${err}`); + } + } + +} + +export default ToMessagePack; diff --git a/src/core/operations/ToMorseCode.mjs b/src/core/operations/ToMorseCode.mjs new file mode 100644 index 00000000..4770500b --- /dev/null +++ b/src/core/operations/ToMorseCode.mjs @@ -0,0 +1,154 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * To Morse Code operation + */ +class ToMorseCode extends Operation { + + /** + * ToMorseCode constructor + */ + constructor() { + super(); + + this.name = "To Morse Code"; + this.module = "Default"; + this.description = "Translates alphanumeric characters into International Morse Code.

Ignores non-Morse characters.

e.g. SOS becomes ... --- ..."; + this.infoURL = "https://wikipedia.org/wiki/Morse_code"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Format options", + "type": "option", + "value": ["-/.", "_/.", "Dash/Dot", "DASH/DOT", "dash/dot"] + }, + { + "name": "Letter delimiter", + "type": "option", + "value": LETTER_DELIM_OPTIONS + }, + { + "name": "Word delimiter", + "type": "option", + "value": WORD_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const format = args[0].split("/"); + const dash = format[0]; + const dot = format[1]; + + const letterDelim = Utils.charRep(args[1]); + const wordDelim = Utils.charRep(args[2]); + + input = input.split(/\r?\n/); + input = Array.prototype.map.call(input, function(line) { + let words = line.split(/ +/); + words = Array.prototype.map.call(words, function(word) { + const letters = Array.prototype.map.call(word, function(character) { + const letter = character.toUpperCase(); + if (typeof MORSE_TABLE[letter] == "undefined") { + return ""; + } + + return MORSE_TABLE[letter]; + }); + + return letters.join(""); + }); + line = words.join(""); + return line; + }); + input = input.join("\n"); + + input = input.replace( + /|||/g, + function(match) { + switch (match) { + case "": return dash; + case "": return dot; + case "": return letterDelim; + case "": return wordDelim; + } + } + ); + + return input; + } + +} + +const MORSE_TABLE = { + "A": "", + "B": "", + "C": "", + "D": "", + "E": "", + "F": "", + "G": "", + "H": "", + "I": "", + "J": "", + "K": "", + "L": "", + "M": "", + "N": "", + "O": "", + "P": "", + "Q": "", + "R": "", + "S": "", + "T": "", + "U": "", + "V": "", + "W": "", + "X": "", + "Y": "", + "Z": "", + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + "6": "", + "7": "", + "8": "", + "9": "", + "0": "", + ".": "", + ",": "", + ":": "", + ";": "", + "!": "", + "?": "", + "'": "", + "\"": "", + "/": "", + "-": "", + "+": "", + "(": "", + ")": "", + "@": "", + "=": "", + "&": "", + "_": "", + "$": "" +}; + +export default ToMorseCode; diff --git a/src/core/operations/ToOctal.mjs b/src/core/operations/ToOctal.mjs new file mode 100644 index 00000000..842b2b82 --- /dev/null +++ b/src/core/operations/ToOctal.mjs @@ -0,0 +1,50 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {DELIM_OPTIONS} from "../lib/Delim"; + + +/** + * To Octal operation + */ +class ToOctal extends Operation { + + /** + * ToOctal constructor + */ + constructor() { + super(); + + this.name = "To Octal"; + this.module = "Default"; + this.description = "Converts the input string to octal bytes separated by the specified delimiter.

e.g. The UTF-8 encoded string Γειά σου becomes 316 223 316 265 316 271 316 254 40 317 203 316 277 317 205"; + this.infoURL = "https://wikipedia.org/wiki/Octal"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"); + return input.map(val => val.toString(8)).join(delim); + } + +} + +export default ToOctal; diff --git a/src/core/operations/ToPunycode.mjs b/src/core/operations/ToPunycode.mjs new file mode 100644 index 00000000..6cf5096c --- /dev/null +++ b/src/core/operations/ToPunycode.mjs @@ -0,0 +1,53 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import punycode from "punycode"; + +/** + * To Punycode operation + */ +class ToPunycode extends Operation { + + /** + * ToPunycode constructor + */ + constructor() { + super(); + + this.name = "To Punycode"; + this.module = "Encodings"; + this.description = "Punycode is a way to represent Unicode with the limited character subset of ASCII supported by the Domain Name System.

e.g. m\xfcnchen encodes to mnchen-3ya"; + this.infoURL = "https://wikipedia.org/wiki/Punycode"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Internationalised domain name", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const idn = args[0]; + + if (idn) { + return punycode.toASCII(input); + } else { + return punycode.encode(input); + } + } + +} + +export default ToPunycode; diff --git a/src/core/operations/QuotedPrintable.js b/src/core/operations/ToQuotedPrintable.mjs old mode 100755 new mode 100644 similarity index 59% rename from src/core/operations/QuotedPrintable.js rename to src/core/operations/ToQuotedPrintable.mjs index 7636b524..51044021 --- a/src/core/operations/QuotedPrintable.js +++ b/src/core/operations/ToQuotedPrintable.mjs @@ -1,46 +1,42 @@ -/** @license -======================================================================== - mimelib: http://github.com/andris9/mimelib - Copyright (c) 2011-2012 Andris Reinman - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - /** - * Quoted Printable operations. * Some parts taken from mimelib (http://github.com/andris9/mimelib) - * * @author Andris Reinman + * @license MIT + * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 - * - * @namespace */ -const QuotedPrintable = { + +import Operation from "../Operation"; + +/** + * To Quoted Printable operation + */ +class ToQuotedPrintable extends Operation { + + /** + * ToQuotedPrintable constructor + */ + constructor() { + super(); + + this.name = "To Quoted Printable"; + 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.

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.outputType = "string"; + this.args = []; + } /** - * To Quoted Printable operation. - * * @param {byteArray} input * @param {Object[]} args * @returns {string} */ - runTo: function (input, args) { - let mimeEncodedStr = QuotedPrintable.mimeEncode(input); + run(input, args) { + let mimeEncodedStr = this.mimeEncode(input); // fix line breaks mimeEncodedStr = mimeEncodedStr.replace(/\r?\n|\r/g, function() { @@ -49,49 +45,30 @@ const QuotedPrintable = { return spaces.replace(/ /g, "=20").replace(/\t/g, "=09"); }); - return QuotedPrintable._addSoftLinebreaks(mimeEncodedStr, "qp"); - }, + return this._addSoftLinebreaks(mimeEncodedStr, "qp"); + } - /** - * From Quoted Printable operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFrom: function (input, args) { - const str = input.replace(/\=(?:\r?\n|$)/g, ""); - return QuotedPrintable.mimeDecode(str); - }, + /** @license + ======================================================================== + mimelib: http://github.com/andris9/mimelib + Copyright (c) 2011-2012 Andris Reinman + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - /** - * Decodes mime-encoded data. - * - * @param {string} str - * @returns {byteArray} - */ - mimeDecode: function(str) { - let encodedBytesCount = (str.match(/\=[\da-fA-F]{2}/g) || []).length, - bufferLength = str.length - encodedBytesCount * 2, - chr, hex, - buffer = new Array(bufferLength), - bufferPos = 0; - - for (let i = 0, len = str.length; i < len; i++) { - chr = str.charAt(i); - if (chr === "=" && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) { - buffer[bufferPos++] = parseInt(hex, 16); - i += 2; - continue; - } - buffer[bufferPos++] = chr.charCodeAt(0); - } - - return buffer; - }, - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ /** * Encodes mime data. @@ -99,19 +76,19 @@ const QuotedPrintable = { * @param {byteArray} buffer * @returns {string} */ - mimeEncode: function(buffer) { - let ranges = [ - [0x09], - [0x0A], - [0x0D], - [0x20], - [0x21], - [0x23, 0x3C], - [0x3E], - [0x40, 0x5E], - [0x60, 0x7E] - ], - result = ""; + mimeEncode(buffer) { + const ranges = [ + [0x09], + [0x0A], + [0x0D], + [0x20], + [0x21], + [0x23, 0x3C], + [0x3E], + [0x40, 0x5E], + [0x60, 0x7E] + ]; + let result = ""; for (let i = 0, len = buffer.length; i < len; i++) { if (this._checkRanges(buffer[i], ranges)) { @@ -122,8 +99,7 @@ const QuotedPrintable = { } return result; - }, - + } /** * Checks if a given number falls within a given set of ranges. @@ -133,7 +109,7 @@ const QuotedPrintable = { * @param {byteArray[]} ranges * @returns {bolean} */ - _checkRanges: function(nr, ranges) { + _checkRanges(nr, ranges) { for (let i = ranges.length - 1; i >= 0; i--) { if (!ranges[i].length) continue; @@ -143,8 +119,7 @@ const QuotedPrintable = { return true; } return false; - }, - + } /** * Adds soft line breaks to a string. @@ -156,7 +131,7 @@ const QuotedPrintable = { * @param {string} encoding * @returns {string} */ - _addSoftLinebreaks: function(str, encoding) { + _addSoftLinebreaks(str, encoding) { const lineLengthMax = 76; encoding = (encoding || "base64").toString().toLowerCase().trim(); @@ -166,8 +141,7 @@ const QuotedPrintable = { } else { return this._addBase64SoftLinebreaks(str, lineLengthMax); } - }, - + } /** * Adds soft line breaks to a base64 string. @@ -177,11 +151,10 @@ const QuotedPrintable = { * @param {number} lineLengthMax * @returns {string} */ - _addBase64SoftLinebreaks: function(base64EncodedStr, lineLengthMax) { + _addBase64SoftLinebreaks(base64EncodedStr, lineLengthMax) { base64EncodedStr = (base64EncodedStr || "").toString().trim(); return base64EncodedStr.replace(new RegExp(".{" + lineLengthMax + "}", "g"), "$&\r\n").trim(); - }, - + } /** * Adds soft line breaks to a quoted printable string. @@ -191,11 +164,11 @@ const QuotedPrintable = { * @param {number} lineLengthMax * @returns {string} */ - _addQPSoftLinebreaks: function(mimeEncodedStr, lineLengthMax) { + _addQPSoftLinebreaks(mimeEncodedStr, lineLengthMax) { + const len = mimeEncodedStr.length, + lineMargin = Math.floor(lineLengthMax / 3); let pos = 0, - len = mimeEncodedStr.length, match, code, line, - lineMargin = Math.floor(lineLengthMax / 3), result = ""; // insert soft linebreaks where needed @@ -219,21 +192,21 @@ const QuotedPrintable = { result += line; pos += line.length; continue; - } else if (line.length > lineLengthMax - lineMargin && (match = line.substr(-lineMargin).match(/[ \t\.,!\?][^ \t\.,!\?]*$/))) { + } else if (line.length > lineLengthMax - lineMargin && (match = line.substr(-lineMargin).match(/[ \t.,!?][^ \t.,!?]*$/))) { // truncate to nearest space line = line.substr(0, line.length - (match[0].length - 1)); } else if (line.substr(-1) === "\r") { line = line.substr(0, line.length - 1); } else { - if (line.match(/\=[\da-f]{0,2}$/i)) { + if (line.match(/=[\da-f]{0,2}$/i)) { // push incomplete encoding sequences to the next line - if ((match = line.match(/\=[\da-f]{0,1}$/i))) { + if ((match = line.match(/=[\da-f]{0,1}$/i))) { line = line.substr(0, line.length - match[0].length); } // ensure that utf-8 sequences are not split - while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/\=[\da-f]{2}$/ig))) { + while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/=[\da-f]{2}$/ig))) { code = parseInt(match[0].substr(1, 2), 16); if (code < 128) { break; @@ -250,7 +223,7 @@ const QuotedPrintable = { } if (pos + line.length < len && line.substr(-1) !== "\n") { - if (line.length === 76 && line.match(/\=[\da-f]{2}$/i)) { + if (line.length === 76 && line.match(/=[\da-f]{2}$/i)) { line = line.substr(0, line.length - 3); } else if (line.length === 76) { line = line.substr(0, line.length - 1); @@ -265,8 +238,8 @@ const QuotedPrintable = { } return result; - }, + } -}; +} -export default QuotedPrintable; +export default ToQuotedPrintable; diff --git a/src/core/operations/ToSnakeCase.mjs b/src/core/operations/ToSnakeCase.mjs new file mode 100644 index 00000000..0cef88fe --- /dev/null +++ b/src/core/operations/ToSnakeCase.mjs @@ -0,0 +1,53 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import snakeCase from "lodash/snakeCase"; +import Operation from "../Operation"; +import { replaceVariableNames } from "../lib/Code"; + +/** + * To Snake case operation + */ +class ToSnakeCase extends Operation { + + /** + * ToSnakeCase constructor + */ + constructor() { + super(); + + this.name = "To Snake case"; + this.module = "Code"; + this.description = "Converts the input string to snake case.\n

\nSnake case is all lower case with underscores as word boundaries.\n

\ne.g. this_is_snake_case\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; + this.infoURL = "https://wikipedia.org/wiki/Snake_case"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Attempt to be context aware", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, snakeCase); + } else { + return snakeCase(input); + } + } +} + +export default ToSnakeCase; diff --git a/src/core/operations/ToTable.mjs b/src/core/operations/ToTable.mjs new file mode 100644 index 00000000..6d10b340 --- /dev/null +++ b/src/core/operations/ToTable.mjs @@ -0,0 +1,190 @@ +/** + * @author Mark Jones [github.com/justanothermark] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * To Table operation + */ +class ToTable extends Operation { + + /** + * ToTable constructor + */ + constructor() { + super(); + + this.name = "To Table"; + this.module = "Default"; + this.description = "Data can be split on different characters and rendered as an HTML or ASCII table with an optional header row.

Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to \\t to support TSV (Tab Separated Values) or | for PSV (Pipe Separated Values).

You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter."; + this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Cell delimiters", + "type": "binaryShortString", + "value": "," + }, + { + "name": "Row delimiters", + "type": "binaryShortString", + "value": "\\r\\n" + }, + { + "name": "Make first row header", + "type": "boolean", + "value": false + }, + { + "name": "Format", + "type": "option", + "value": ["ASCII", "HTML"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [cellDelims, rowDelims, firstRowHeader, format] = args; + + // Process the input into a nested array of elements. + const tableData = Utils.parseCSV(Utils.escapeHtml(input), cellDelims.split(""), rowDelims.split("")); + + if (!tableData.length) return ""; + + // Render the data in the requested format. + switch (format) { + case "ASCII": + return asciiOutput(tableData); + case "HTML": + default: + return htmlOutput(tableData); + } + + /** + * Outputs an array of data as an ASCII table. + * + * @param {string[][]} tableData + * @returns {string} + */ + function asciiOutput(tableData) { + const horizontalBorder = "-"; + const verticalBorder = "|"; + const crossBorder = "+"; + + let output = ""; + const longestCells = []; + + // Find longestCells value per column to pad cells equally. + tableData.forEach(function(row, index) { + row.forEach(function(cell, cellIndex) { + if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) { + longestCells[cellIndex] = cell.length; + } + }); + }); + + // Add the top border of the table to the output. + output += outputHorizontalBorder(longestCells); + + // If the first row is a header, remove the row from the data and + // add it to the output with another horizontal border. + if (firstRowHeader) { + const row = tableData.shift(); + output += outputRow(row, longestCells); + output += outputHorizontalBorder(longestCells); + } + + // Add the rest of the table rows. + tableData.forEach(function(row, index) { + output += outputRow(row, longestCells); + }); + + // Close the table with a final horizontal border. + output += outputHorizontalBorder(longestCells); + + return output; + + /** + * Outputs a row of correctly padded cells. + */ + function outputRow(row, longestCells) { + let rowOutput = verticalBorder; + row.forEach(function(cell, index) { + rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder; + }); + rowOutput += "\n"; + return rowOutput; + } + + /** + * Outputs a horizontal border with a different character where + * the horizontal border meets a vertical border. + */ + function outputHorizontalBorder(longestCells) { + let rowOutput = crossBorder; + longestCells.forEach(function(cellLength) { + rowOutput += horizontalBorder.repeat(cellLength + 2) + crossBorder; + }); + rowOutput += "\n"; + return rowOutput; + } + } + + /** + * Outputs a table of data as a HTML table. + * + * @param {string[][]} tableData + * @returns {string} + */ + function htmlOutput(tableData) { + // Start the HTML output with suitable classes for styling. + let output = "
"; + + // If the first row is a header then put it in with "; + output += outputRow(row, "th"); + output += ""; + } + + // Output the rest of the rows in the . + output += ""; + tableData.forEach(function(row, index) { + output += outputRow(row, "td"); + }); + + // Close the body and table elements. + output += "
cells. + if (firstRowHeader) { + const row = tableData.shift(); + output += "
"; + return output; + + /** + * Outputs a table row. + * + * @param {string[]} row + * @param {string} cellType + */ + function outputRow(row, cellType) { + let output = ""; + row.forEach(function(cell) { + output += "<" + cellType + ">" + cell + ""; + }); + output += ""; + return output; + } + } + } + +} + +export default ToTable; diff --git a/src/core/operations/ToUNIXTimestamp.mjs b/src/core/operations/ToUNIXTimestamp.mjs new file mode 100644 index 00000000..d8cde848 --- /dev/null +++ b/src/core/operations/ToUNIXTimestamp.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import moment from "moment-timezone"; +import {UNITS} from "../lib/DateTime"; +import OperationError from "../errors/OperationError"; + +/** + * To UNIX Timestamp operation + */ +class ToUNIXTimestamp extends Operation { + + /** + * ToUNIXTimestamp constructor + */ + constructor() { + super(); + + this.name = "To UNIX Timestamp"; + this.module = "Default"; + this.description = "Parses a datetime string in UTC and returns the corresponding UNIX timestamp.

e.g. Mon 1 January 2001 11:00:00 becomes 978346800

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch)."; + this.infoURL = "https://wikipedia.org/wiki/Unix_time"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Units", + "type": "option", + "value": UNITS + }, + { + "name": "Treat as UTC", + "type": "boolean", + "value": true + }, + { + "name": "Show parsed datetime", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if unit unrecognised + */ + run(input, args) { + const [units, treatAsUTC, showDateTime] = args, + d = treatAsUTC ? moment.utc(input) : moment(input); + + let result = ""; + + if (units === "Seconds (s)") { + result = d.unix(); + } else if (units === "Milliseconds (ms)") { + result = d.valueOf(); + } else if (units === "Microseconds (μs)") { + result = d.valueOf() * 1000; + } else if (units === "Nanoseconds (ns)") { + result = d.valueOf() * 1000000; + } else { + throw new OperationError("Unrecognised unit"); + } + + return showDateTime ? `${result} (${d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss")} UTC)` : result.toString(); + } + +} + +export default ToUNIXTimestamp; diff --git a/src/core/operations/ToUpperCase.mjs b/src/core/operations/ToUpperCase.mjs new file mode 100644 index 00000000..d56aacff --- /dev/null +++ b/src/core/operations/ToUpperCase.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * To Upper case operation + */ +class ToUpperCase extends Operation { + + /** + * ToUpperCase constructor + */ + constructor() { + super(); + + this.name = "To Upper case"; + this.module = "Default"; + this.description = "Converts the input string to upper case, optionally limiting scope to only the first character in each word, sentence or paragraph."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Scope", + "type": "option", + "value": ["All", "Word", "Sentence", "Paragraph"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const scope = args[0]; + + switch (scope) { + case "Word": + return input.replace(/(\b\w)/gi, function(m) { + return m.toUpperCase(); + }); + case "Sentence": + return input.replace(/(?:\.|^)\s*(\b\w)/gi, function(m) { + return m.toUpperCase(); + }); + case "Paragraph": + return input.replace(/(?:\n|^)\s*(\b\w)/gi, function(m) { + return m.toUpperCase(); + }); + case "All": /* falls through */ + default: + return input.toUpperCase(); + } + } + + /** + * Highlight To Upper case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight To Upper case in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default ToUpperCase; diff --git a/src/core/operations/TranslateDateTimeFormat.mjs b/src/core/operations/TranslateDateTimeFormat.mjs new file mode 100644 index 00000000..08f2d646 --- /dev/null +++ b/src/core/operations/TranslateDateTimeFormat.mjs @@ -0,0 +1,79 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import moment from "moment-timezone"; +import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime"; + +/** + * Translate DateTime Format operation + */ +class TranslateDateTimeFormat extends Operation { + + /** + * TranslateDateTimeFormat constructor + */ + constructor() { + super(); + + this.name = "Translate DateTime Format"; + this.module = "Default"; + this.description = "Parses a datetime string in one format and re-writes it in another.

Run with no input to see the relevant format string examples."; + this.infoURL = "https://momentjs.com/docs/#/parsing/string-format/"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Built in formats", + "type": "populateOption", + "value": DATETIME_FORMATS, + "target": 1 + }, + { + "name": "Input format string", + "type": "binaryString", + "value": "DD/MM/YYYY HH:mm:ss" + }, + { + "name": "Input timezone", + "type": "option", + "value": ["UTC"].concat(moment.tz.names()) + }, + { + "name": "Output format string", + "type": "binaryString", + "value": "dddd Do MMMM YYYY HH:mm:ss Z z" + }, + { + "name": "Output timezone", + "type": "option", + "value": ["UTC"].concat(moment.tz.names()) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [inputFormat, inputTimezone, outputFormat, outputTimezone] = args.splice(1); + let date; + + try { + date = moment.tz(input, inputFormat, inputTimezone); + if (!date || date.format() === "Invalid date") throw Error; + } catch (err) { + return `Invalid format.\n\n${FORMAT_EXAMPLES}`; + } + + return date.tz(outputTimezone).format(outputFormat); + } + +} + +export default TranslateDateTimeFormat; diff --git a/src/core/operations/TripleDESDecrypt.mjs b/src/core/operations/TripleDESDecrypt.mjs new file mode 100644 index 00000000..47d1df05 --- /dev/null +++ b/src/core/operations/TripleDESDecrypt.mjs @@ -0,0 +1,95 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * Triple DES Decrypt operation + */ +class TripleDESDecrypt extends Operation { + + /** + * TripleDESDecrypt constructor + */ + constructor() { + super(); + + this.name = "Triple DES Decrypt"; + this.module = "Ciphers"; + this.description = "Triple DES applies DES three times to each block to increase key size.

Key: Triple DES uses a key length of 24 bytes (192 bits).
DES uses a key length of 8 bytes (64 bits).

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used."; + this.infoURL = "https://wikipedia.org/wiki/Triple_DES"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + mode = args[2], + inputType = args[3], + outputType = args[4]; + + if (key.length !== 24) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +Triple DES uses a key length of 24 bytes (192 bits). +DES uses a key length of 8 bytes (64 bits).`); + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = forge.cipher.createDecipher("3DES-" + mode, key); + decipher.start({iv: iv}); + decipher.update(forge.util.createBuffer(input)); + const result = decipher.finish(); + + if (result) { + return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); + } else { + throw new OperationError("Unable to decrypt input with these parameters."); + } + } + +} + +export default TripleDESDecrypt; diff --git a/src/core/operations/TripleDESEncrypt.mjs b/src/core/operations/TripleDESEncrypt.mjs new file mode 100644 index 00000000..e7eb6bca --- /dev/null +++ b/src/core/operations/TripleDESEncrypt.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * Triple DES Encrypt operation + */ +class TripleDESEncrypt extends Operation { + + /** + * TripleDESEncrypt constructor + */ + constructor() { + super(); + + this.name = "Triple DES Encrypt"; + this.module = "Ciphers"; + this.description = "Triple DES applies DES three times to each block to increase key size.

Key: Triple DES uses a key length of 24 bytes (192 bits).
DES uses a key length of 8 bytes (64 bits).

You can generate a password-based key using one of the KDF operations.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used."; + this.infoURL = "https://wikipedia.org/wiki/Triple_DES"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + mode = args[2], + inputType = args[3], + outputType = args[4]; + + if (key.length !== 24) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +Triple DES uses a key length of 24 bytes (192 bits). +DES uses a key length of 8 bytes (64 bits).`); + } + + input = Utils.convertToByteString(input, inputType); + + const cipher = forge.cipher.createCipher("3DES-" + mode, key); + cipher.start({iv: iv}); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); + } + +} + +export default TripleDESEncrypt; diff --git a/src/core/operations/UNIXTimestampToWindowsFiletime.mjs b/src/core/operations/UNIXTimestampToWindowsFiletime.mjs new file mode 100644 index 00000000..187b98ab --- /dev/null +++ b/src/core/operations/UNIXTimestampToWindowsFiletime.mjs @@ -0,0 +1,77 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError"; + +/** + * UNIX Timestamp to Windows Filetime operation + */ +class UNIXTimestampToWindowsFiletime extends Operation { + + /** + * UNIXTimestampToWindowsFiletime constructor + */ + constructor() { + super(); + + this.name = "UNIX Timestamp to Windows Filetime"; + this.module = "Default"; + this.description = "Converts a UNIX timestamp to a Windows Filetime value.

A Windows Filetime is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 UTC.

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).

This operation also supports UNIX timestamps in milliseconds, microseconds and nanoseconds."; + this.infoURL = "https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"] + }, + { + "name": "Output format", + "type": "option", + "value": ["Decimal", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [units, format] = args; + + if (!input) return ""; + + input = new BigNumber(input); + + if (units === "Seconds (s)"){ + input = input.multipliedBy(new BigNumber("10000000")); + } else if (units === "Milliseconds (ms)") { + input = input.multipliedBy(new BigNumber("10000")); + } else if (units === "Microseconds (μs)") { + input = input.multiplyiedBy(new BigNumber("10")); + } else if (units === "Nanoseconds (ns)") { + input = input.dividedBy(new BigNumber("100")); + } else { + throw new OperationError("Unrecognised unit"); + } + + input = input.plus(new BigNumber("116444736000000000")); + + if (format === "Hex"){ + return input.toString(16); + } else { + return input.toFixed(); + } + } + +} + +export default UNIXTimestampToWindowsFiletime; diff --git a/src/core/operations/URL.js b/src/core/operations/URL.js deleted file mode 100755 index cff59d23..00000000 --- a/src/core/operations/URL.js +++ /dev/null @@ -1,138 +0,0 @@ -/* globals unescape */ -import Utils from "../Utils.js"; - - -/** - * URL operations. - * Namespace is appended with an underscore to prevent overwriting the global URL object. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const URL_ = { - - /** - * @constant - * @default - */ - ENCODE_ALL: false, - - /** - * URL Encode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runTo: function(input, args) { - const encodeAll = args[0]; - return encodeAll ? URL_._encodeAllChars(input) : encodeURI(input); - }, - - - /** - * URL Decode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFrom: function(input, args) { - const data = input.replace(/\+/g, "%20"); - try { - return decodeURIComponent(data); - } catch (err) { - return unescape(data); - } - }, - - - /** - * Parse URI operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParse: function(input, args) { - if (!document) { - throw "This operation only works in a browser."; - } - - const a = document.createElement("a"); - - // Overwrite base href which will be the current CyberChef URL to reduce confusion. - a.href = "http://example.com/"; - a.href = input; - - if (a.protocol) { - let output = ""; - if (a.hostname !== window.location.hostname) { - output = "Protocol:\t" + a.protocol + "\n"; - if (a.hostname) output += "Hostname:\t" + a.hostname + "\n"; - if (a.port) output += "Port:\t\t" + a.port + "\n"; - } - - if (a.pathname && a.pathname !== window.location.pathname) { - let pathname = a.pathname; - if (pathname.indexOf(window.location.pathname) === 0) - pathname = pathname.replace(window.location.pathname, ""); - if (pathname) - output += "Path name:\t" + pathname + "\n"; - } - - if (a.hash && a.hash !== window.location.hash) { - output += "Hash:\t\t" + a.hash + "\n"; - } - - if (a.search && a.search !== window.location.search) { - output += "Arguments:\n"; - const args_ = (a.search.slice(1, a.search.length)).split("&"); - let splitArgs = [], padding = 0, i; - for (i = 0; i < args_.length; i++) { - splitArgs.push(args_[i].split("=")); - padding = (splitArgs[i][0].length > padding) ? splitArgs[i][0].length : padding; - } - for (i = 0; i < splitArgs.length; i++) { - output += "\t" + Utils.padRight(splitArgs[i][0], padding); - if (splitArgs[i].length > 1 && splitArgs[i][1].length) - output += " = " + splitArgs[i][1] + "\n"; - else output += "\n"; - } - } - - return output; - } - - return "Invalid URI"; - }, - - - /** - * URL encodes additional special characters beyond the standard set. - * - * @private - * @param {string} str - * @returns {string} - */ - _encodeAllChars: function(str) { - //TODO Do this programatically - return encodeURIComponent(str) - .replace(/!/g, "%21") - .replace(/#/g, "%23") - .replace(/'/g, "%27") - .replace(/\(/g, "%28") - .replace(/\)/g, "%29") - .replace(/\*/g, "%2A") - .replace(/\-/g, "%2D") - .replace(/\./g, "%2E") - .replace(/_/g, "%5F") - .replace(/~/g, "%7E"); - }, - -}; - -export default URL_; diff --git a/src/core/operations/URLDecode.mjs b/src/core/operations/URLDecode.mjs new file mode 100644 index 00000000..dbbee20d --- /dev/null +++ b/src/core/operations/URLDecode.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * URL Decode operation + */ +class URLDecode extends Operation { + + /** + * URLDecode constructor + */ + constructor() { + super(); + + this.name = "URL Decode"; + this.module = "URL"; + this.description = "Converts URI/URL percent-encoded characters back to their raw values.

e.g. %3d becomes ="; + this.infoURL = "https://wikipedia.org/wiki/Percent-encoding"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.patterns = [ + { + match: ".*(?:%[\\da-f]{2}.*){4}", + flags: "i", + args: [] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const data = input.replace(/\+/g, "%20"); + try { + return decodeURIComponent(data); + } catch (err) { + return unescape(data); + } + } + +} + +export default URLDecode; diff --git a/src/core/operations/URLEncode.mjs b/src/core/operations/URLEncode.mjs new file mode 100644 index 00000000..2eb5b621 --- /dev/null +++ b/src/core/operations/URLEncode.mjs @@ -0,0 +1,69 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * URL Encode operation + */ +class URLEncode extends Operation { + + /** + * URLEncode constructor + */ + constructor() { + super(); + + this.name = "URL Encode"; + this.module = "URL"; + this.description = "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.

e.g. = becomes %3d"; + this.infoURL = "https://wikipedia.org/wiki/Percent-encoding"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Encode all special chars", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const encodeAll = args[0]; + return encodeAll ? this.encodeAllChars(input) : encodeURI(input); + } + + /** + * Encode characters in URL outside of encodeURI() function spec + * + * @param {string} str + * @returns {string} + */ + encodeAllChars (str) { + // TODO Do this programatically + return encodeURIComponent(str) + .replace(/!/g, "%21") + .replace(/#/g, "%23") + .replace(/'/g, "%27") + .replace(/\(/g, "%28") + .replace(/\)/g, "%29") + .replace(/\*/g, "%2A") + .replace(/-/g, "%2D") + .replace(/\./g, "%2E") + .replace(/_/g, "%5F") + .replace(/~/g, "%7E"); + } + +} + + +export default URLEncode; diff --git a/src/core/operations/UUID.js b/src/core/operations/UUID.js deleted file mode 100755 index 761f245a..00000000 --- a/src/core/operations/UUID.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * UUID operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const UUID = { - - /** - * Generate UUID operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runGenerateV4: function(input, args) { - if (window && typeof(window.crypto) !== "undefined" && typeof(window.crypto.getRandomValues) !== "undefined") { - let buf = new Uint32Array(4), - i = 0; - window.crypto.getRandomValues(buf); - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - let r = (buf[i >> 3] >> ((i % 8) * 4)) & 0xf, - v = c === "x" ? r : (r & 0x3 | 0x8); - i++; - return v.toString(16); - }); - } else { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - let r = Math.random() * 16 | 0, - v = c === "x" ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - }, - -}; - -export default UUID; diff --git a/src/core/operations/UnescapeString.mjs b/src/core/operations/UnescapeString.mjs new file mode 100644 index 00000000..62eab48e --- /dev/null +++ b/src/core/operations/UnescapeString.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Unescape string operation + */ +class UnescapeString extends Operation { + + /** + * UnescapeString constructor + */ + constructor() { + super(); + + this.name = "Unescape string"; + this.module = "Default"; + this.description = "Unescapes characters in a string that have been escaped. For example, Don\\'t stop me now becomes Don't stop me now.

Supports the following escape sequences:
  • \\n (Line feed/newline)
  • \\r (Carriage return)
  • \\t (Horizontal tab)
  • \\b (Backspace)
  • \\f (Form feed)
  • \\xnn (Hex, where n is 0-f)
  • \\\\ (Backslash)
  • \\' (Single quote)
  • \\" (Double quote)
  • \\unnnn (Unicode character)
  • \\u{nnnnnn} (Unicode code point)
"; + this.infoURL = "https://wikipedia.org/wiki/Escape_sequence"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return Utils.parseEscapedChars(input); + } + +} + +export default UnescapeString; diff --git a/src/core/operations/UnescapeUnicodeCharacters.mjs b/src/core/operations/UnescapeUnicodeCharacters.mjs new file mode 100644 index 00000000..ab8af2a8 --- /dev/null +++ b/src/core/operations/UnescapeUnicodeCharacters.mjs @@ -0,0 +1,75 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Unescape Unicode Characters operation + */ +class UnescapeUnicodeCharacters extends Operation { + + /** + * UnescapeUnicodeCharacters constructor + */ + constructor() { + super(); + + this.name = "Unescape Unicode Characters"; + this.module = "Default"; + this.description = "Converts unicode-escaped character notation back into raw characters.

Supports the prefixes:
  • \\u
  • %u
  • U+
e.g. \\u03c3\\u03bf\\u03c5 becomes σου"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Prefix", + "type": "option", + "value": ["\\u", "%u", "U+"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const prefix = prefixToRegex[args[0]], + regex = new RegExp(prefix+"([a-f\\d]{4})", "ig"); + let output = "", + m, + i = 0; + + while ((m = regex.exec(input))) { + // Add up to match + output += input.slice(i, m.index); + i = m.index; + + // Add match + output += Utils.chr(parseInt(m[1], 16)); + + i = regex.lastIndex; + } + + // Add all after final match + output += input.slice(i, input.length); + + return output; + } + +} + +/** + * Lookup table to add prefixes to unicode delimiters so that they can be used in a regex. + */ +const prefixToRegex = { + "\\u": "\\\\u", + "%u": "%u", + "U+": "U\\+" +}; + +export default UnescapeUnicodeCharacters; diff --git a/src/core/operations/Unicode.js b/src/core/operations/Unicode.js deleted file mode 100755 index 34bffd4d..00000000 --- a/src/core/operations/Unicode.js +++ /dev/null @@ -1,67 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Unicode operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Unicode = { - - /** - * @constant - * @default - */ - PREFIXES: ["\\u", "%u", "U+"], - - /** - * Unescape Unicode Characters operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runUnescape: function(input, args) { - let prefix = Unicode._prefixToRegex[args[0]], - regex = new RegExp(prefix+"([a-f\\d]{4,6})", "ig"), - output = "", - m, - i = 0; - - while ((m = regex.exec(input))) { - // Add up to match - output += input.slice(i, m.index); - i = m.index; - - // Add match - output += Utils.chr(parseInt(m[1], 16)); - - i = regex.lastIndex; - } - - // Add all after final match - output += input.slice(i, input.length); - - return output; - }, - - - /** - * Lookup table to add prefixes to unicode delimiters so that they can be used in a regex. - * - * @private - * @constant - */ - _prefixToRegex: { - "\\u": "\\\\u", - "%u": "%u", - "U+": "U\\+" - }, - -}; - -export default Unicode; diff --git a/src/core/operations/Unique.mjs b/src/core/operations/Unique.mjs new file mode 100644 index 00000000..6848968b --- /dev/null +++ b/src/core/operations/Unique.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * Unique operation + */ +class Unique extends Operation { + + /** + * Unique constructor + */ + constructor() { + super(); + + this.name = "Unique"; + this.module = "Default"; + this.description = "Removes duplicate strings from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": INPUT_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]); + return input.split(delim).unique().join(delim); + } + +} + +export default Unique; diff --git a/src/core/operations/Untar.mjs b/src/core/operations/Untar.mjs new file mode 100644 index 00000000..af029184 --- /dev/null +++ b/src/core/operations/Untar.mjs @@ -0,0 +1,139 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Untar operation + */ +class Untar extends Operation { + + /** + * Untar constructor + */ + constructor() { + super(); + + this.name = "Untar"; + 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.outputType = "List"; + this.presentType = "html"; + this.args = []; + this.patterns = [ + { + "match": "^.{257}\\x75\\x73\\x74\\x61\\x72", + "flags": "", + "args": [] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {List} + */ + run(input, args) { + const Stream = function(input) { + this.bytes = input; + this.position = 0; + }; + + Stream.prototype.getBytes = function(bytesToGet) { + const newPosition = this.position + bytesToGet; + const bytes = this.bytes.slice(this.position, newPosition); + this.position = newPosition; + return bytes; + }; + + Stream.prototype.readString = function(numBytes) { + let result = ""; + for (let i = this.position; i < this.position + numBytes; i++) { + const currentByte = this.bytes[i]; + if (currentByte === 0) break; + result += String.fromCharCode(currentByte); + } + this.position += numBytes; + return result; + }; + + Stream.prototype.readInt = function(numBytes, base) { + const string = this.readString(numBytes); + return parseInt(string, base); + }; + + Stream.prototype.hasMore = function() { + return this.position < this.bytes.length; + }; + + const stream = new Stream(input), + files = []; + + while (stream.hasMore()) { + const dataPosition = stream.position + 512; + + const file = { + fileName: stream.readString(100), + fileMode: stream.readString(8), + ownerUID: stream.readString(8), + ownerGID: stream.readString(8), + size: parseInt(stream.readString(12), 8), // Octal + lastModTime: new Date(1000 * stream.readInt(12, 8)), // Octal + checksum: stream.readString(8), + type: stream.readString(1), + linkedFileName: stream.readString(100), + USTARFormat: stream.readString(6).indexOf("ustar") >= 0, + }; + + if (file.USTARFormat) { + file.version = stream.readString(2); + file.ownerUserName = stream.readString(32); + file.ownerGroupName = stream.readString(32); + file.deviceMajor = stream.readString(8); + file.deviceMinor = stream.readString(8); + file.filenamePrefix = stream.readString(155); + } + + stream.position = dataPosition; + + if (file.type === "0") { + // File + let endPosition = stream.position + file.size; + if (file.size % 512 !== 0) { + endPosition += 512 - (file.size % 512); + } + + file.bytes = stream.getBytes(file.size); + files.push(new File([new Uint8Array(file.bytes)], file.fileName)); + stream.position = endPosition; + } else if (file.type === "5") { + // Directory + files.push(new File([new Uint8Array(file.bytes)], file.fileName)); + } else { + // Symlink or empty bytes + } + } + + return files; + } + + /** + * Displays the files in HTML for web apps. + * + * @param {File[]} files + * @returns {html} + */ + async present(files) { + return await Utils.displayFilesAsHTML(files); + } + +} + +export default Untar; diff --git a/src/core/operations/Unzip.mjs b/src/core/operations/Unzip.mjs new file mode 100644 index 00000000..2766c215 --- /dev/null +++ b/src/core/operations/Unzip.mjs @@ -0,0 +1,83 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import unzip from "zlibjs/bin/unzip.min"; + +const Zlib = unzip.Zlib; + +/** + * Unzip operation + */ +class Unzip extends Operation { + + /** + * Unzip constructor + */ + constructor() { + super(); + + this.name = "Unzip"; + this.module = "Compression"; + this.description = "Decompresses data using the PKZIP algorithm and displays it per file, with support for passwords."; + this.infoURL = "https://wikipedia.org/wiki/Zip_(file_format)"; + this.inputType = "ArrayBuffer"; + this.outputType = "List"; + this.presentType = "html"; + this.args = [ + { + name: "Password", + type: "binaryString", + value: "" + }, + { + name: "Verify result", + type: "boolean", + value: false + } + ]; + this.patterns = [ + { + match: "^\\x50\\x4b(?:\\x03|\\x05|\\x07)(?:\\x04|\\x06|\\x08)", + flags: "", + args: ["", false] + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {File[]} + */ + run(input, args) { + const options = { + password: Utils.strToByteArray(args[0]), + verify: args[1] + }, + unzip = new Zlib.Unzip(new Uint8Array(input), options), + filenames = unzip.getFilenames(); + + return filenames.map(fileName => { + const bytes = unzip.decompress(fileName); + return new File([bytes], fileName); + }); + } + + /** + * Displays the files in HTML for web apps. + * + * @param {File[]} files + * @returns {html} + */ + async present(files) { + return await Utils.displayFilesAsHTML(files); + } + +} + +export default Unzip; diff --git a/src/core/operations/VigenèreDecode.mjs b/src/core/operations/VigenèreDecode.mjs new file mode 100644 index 00000000..98aef876 --- /dev/null +++ b/src/core/operations/VigenèreDecode.mjs @@ -0,0 +1,102 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +/** + * Vigenère Decode operation + */ +class VigenèreDecode extends Operation { + + /** + * VigenèreDecode constructor + */ + constructor() { + super(); + + this.name = "Vigenère Decode"; + this.module = "Ciphers"; + this.description = "The Vigenere cipher is a method of encrypting alphabetic text by using a series of different Caesar ciphers based on the letters of a keyword. It is a simple form of polyalphabetic substitution."; + this.infoURL = "https://wikipedia.org/wiki/Vigenère_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const alphabet = "abcdefghijklmnopqrstuvwxyz", + key = args[0].toLowerCase(); + let output = "", + fail = 0, + keyIndex, + msgIndex, + chr; + + if (!key) throw new OperationError("No key entered"); + if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError("The key must consist only of letters"); + + for (let i = 0; i < input.length; i++) { + if (alphabet.indexOf(input[i]) >= 0) { + chr = key[(i - fail) % key.length]; + keyIndex = alphabet.indexOf(chr); + msgIndex = alphabet.indexOf(input[i]); + // Subtract indexes from each other, add 26 just in case the value is negative, + // modulo to remove if neccessary + output += alphabet[(msgIndex - keyIndex + alphabet.length) % 26]; + } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { + chr = key[(i - fail) % key.length].toLowerCase(); + keyIndex = alphabet.indexOf(chr); + msgIndex = alphabet.indexOf(input[i].toLowerCase()); + output += alphabet[(msgIndex + alphabet.length - keyIndex) % 26].toUpperCase(); + } else { + output += input[i]; + fail++; + } + } + + return output; + } + + /** + * Highlight Vigenère Decode + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Vigenère Decode in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default VigenèreDecode; diff --git a/src/core/operations/VigenèreEncode.mjs b/src/core/operations/VigenèreEncode.mjs new file mode 100644 index 00000000..2e02339c --- /dev/null +++ b/src/core/operations/VigenèreEncode.mjs @@ -0,0 +1,107 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Vigenère Encode operation + */ +class VigenèreEncode extends Operation { + + /** + * VigenèreEncode constructor + */ + constructor() { + super(); + + this.name = "Vigenère Encode"; + this.module = "Ciphers"; + this.description = "The Vigenere cipher is a method of encrypting alphabetic text by using a series of different Caesar ciphers based on the letters of a keyword. It is a simple form of polyalphabetic substitution."; + this.infoURL = "https://wikipedia.org/wiki/Vigenère_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const alphabet = "abcdefghijklmnopqrstuvwxyz", + key = args[0].toLowerCase(); + let output = "", + fail = 0, + keyIndex, + msgIndex, + chr; + + if (!key) throw new OperationError("No key entered"); + if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError("The key must consist only of letters"); + + for (let i = 0; i < input.length; i++) { + if (alphabet.indexOf(input[i]) >= 0) { + // Get the corresponding character of key for the current letter, accounting + // for chars not in alphabet + chr = key[(i - fail) % key.length]; + // Get the location in the vigenere square of the key char + keyIndex = alphabet.indexOf(chr); + // Get the location in the vigenere square of the message char + msgIndex = alphabet.indexOf(input[i]); + // Get the encoded letter by finding the sum of indexes modulo 26 and finding + // the letter corresponding to that + output += alphabet[(keyIndex + msgIndex) % 26]; + } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { + chr = key[(i - fail) % key.length].toLowerCase(); + keyIndex = alphabet.indexOf(chr); + msgIndex = alphabet.indexOf(input[i].toLowerCase()); + output += alphabet[(keyIndex + msgIndex) % 26].toUpperCase(); + } else { + output += input[i]; + fail++; + } + } + + return output; + } + + /** + * Highlight Vigenère Encode + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Vigenère Encode in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default VigenèreEncode; diff --git a/src/core/operations/Whirlpool.mjs b/src/core/operations/Whirlpool.mjs new file mode 100644 index 00000000..1ef8a11c --- /dev/null +++ b/src/core/operations/Whirlpool.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * Whirlpool operation + */ +class Whirlpool extends Operation { + + /** + * Whirlpool constructor + */ + constructor() { + super(); + + this.name = "Whirlpool"; + this.module = "Crypto"; + this.description = "Whirlpool is a cryptographic hash function designed by Vincent Rijmen (co-creator of AES) and Paulo S. L. M. Barreto, who first described it in 2000.

Several variants exist:
  • Whirlpool-0 is the original version released in 2000.
  • Whirlpool-T is the first revision, released in 2001, improving the generation of the s-box.
  • Whirlpool is the latest revision, released in 2003, fixing a flaw in the difusion matrix.
"; + this.infoURL = "https://wikipedia.org/wiki/Whirlpool_(cryptography)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Variant", + "type": "option", + "value": ["Whirlpool", "Whirlpool-T", "Whirlpool-0"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const variant = args[0].toLowerCase(); + return runHash(variant, input); + } + +} + +export default Whirlpool; diff --git a/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs b/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs new file mode 100644 index 00000000..f5b59037 --- /dev/null +++ b/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs @@ -0,0 +1,77 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError"; + +/** + * Windows Filetime to UNIX Timestamp operation + */ +class WindowsFiletimeToUNIXTimestamp extends Operation { + + /** + * WindowsFiletimeToUNIXTimestamp constructor + */ + constructor() { + super(); + + this.name = "Windows Filetime to UNIX Timestamp"; + this.module = "Default"; + this.description = "Converts a Windows Filetime value to a UNIX timestamp.

A Windows Filetime is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 UTC.

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).

This operation also supports UNIX timestamps in milliseconds, microseconds and nanoseconds."; + this.infoURL = "https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Output units", + "type": "option", + "value": ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"] + }, + { + "name": "Input format", + "type": "option", + "value": ["Decimal", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [units, format] = args; + + if (!input) return ""; + + if (format === "Hex") { + input = new BigNumber(input, 16); + } else { + input = new BigNumber(input); + } + + input = input.minus(new BigNumber("116444736000000000")); + + if (units === "Seconds (s)"){ + input = input.dividedBy(new BigNumber("10000000")); + } else if (units === "Milliseconds (ms)") { + input = input.dividedBy(new BigNumber("10000")); + } else if (units === "Microseconds (μs)") { + input = input.dividedBy(new BigNumber("10")); + } else if (units === "Nanoseconds (ns)") { + input = input.multipliedBy(new BigNumber("100")); + } else { + throw new OperationError("Unrecognised unit"); + } + + return input.toFixed(); + } + +} + +export default WindowsFiletimeToUNIXTimestamp; diff --git a/src/core/operations/XKCDRandomNumber.mjs b/src/core/operations/XKCDRandomNumber.mjs new file mode 100644 index 00000000..e5811549 --- /dev/null +++ b/src/core/operations/XKCDRandomNumber.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * XKCD Random Number operation + */ +class XKCDRandomNumber extends Operation { + + /** + * XKCDRandomNumber constructor + */ + constructor() { + super(); + + this.name = "XKCD Random Number"; + this.module = "Default"; + this.description = "RFC 1149.5 specifies 4 as the standard IEEE-vetted random number."; + this.infoURL = "https://xkcd.com/221/"; + this.inputType = "string"; + this.outputType = "number"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + return 4; // chosen by fair dice roll. + // guaranteed to be random. + } + +} + +export default XKCDRandomNumber; diff --git a/src/core/operations/XMLBeautify.mjs b/src/core/operations/XMLBeautify.mjs new file mode 100644 index 00000000..4c059411 --- /dev/null +++ b/src/core/operations/XMLBeautify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * XML Beautify operation + */ +class XMLBeautify extends Operation { + + /** + * XMLBeautify constructor + */ + constructor() { + super(); + + this.name = "XML Beautify"; + this.module = "Code"; + this.description = "Indents and prettifies eXtensible Markup Language (XML) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Indent string", + "type": "binaryShortString", + "value": "\\t" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const indentStr = args[0]; + return vkbeautify.xml(input, indentStr); + } + +} + +export default XMLBeautify; diff --git a/src/core/operations/XMLMinify.mjs b/src/core/operations/XMLMinify.mjs new file mode 100644 index 00000000..9c4fb2f6 --- /dev/null +++ b/src/core/operations/XMLMinify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * XML Minify operation + */ +class XMLMinify extends Operation { + + /** + * XMLMinify constructor + */ + constructor() { + super(); + + this.name = "XML Minify"; + this.module = "Code"; + this.description = "Compresses eXtensible Markup Language (XML) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Preserve comments", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const preserveComments = args[0]; + return vkbeautify.xmlmin(input, preserveComments); + } + +} + +export default XMLMinify; diff --git a/src/core/operations/XOR.mjs b/src/core/operations/XOR.mjs new file mode 100644 index 00000000..3bd508fc --- /dev/null +++ b/src/core/operations/XOR.mjs @@ -0,0 +1,88 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import { bitOp, xor, BITWISE_OP_DELIMS } from "../lib/BitwiseOp"; + +/** + * XOR operation + */ +class XOR extends Operation { + + /** + * XOR constructor + */ + constructor() { + super(); + + this.name = "XOR"; + this.module = "Default"; + this.description = "XOR the input with the given key.
e.g. fe023da5

Options
Null preserving: If the current byte is 0x00 or the same as the key, skip it.

Scheme:
  • Standard - key is unchanged after each round
  • Input differential - key is set to the value of the previous unprocessed byte
  • Output differential - key is set to the value of the previous processed byte
  • Cascade - key is set to the input byte shifted by one
"; + this.infoURL = "https://wikipedia.org/wiki/XOR"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": BITWISE_OP_DELIMS + }, + { + "name": "Scheme", + "type": "option", + "value": ["Standard", "Input differential", "Output differential", "Cascade"] + }, + { + "name": "Null preserving", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string || "", args[0].option), + [, scheme, nullPreserving] = args; + + return bitOp(input, key, xor, nullPreserving, scheme); + } + + /** + * Highlight XOR + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight XOR in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default XOR; diff --git a/src/core/operations/XORBruteForce.mjs b/src/core/operations/XORBruteForce.mjs new file mode 100644 index 00000000..6284793f --- /dev/null +++ b/src/core/operations/XORBruteForce.mjs @@ -0,0 +1,141 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import { bitOp, xor } from "../lib/BitwiseOp"; +import { toHex } from "../lib/Hex"; + +/** + * XOR Brute Force operation + */ +class XORBruteForce extends Operation { + + /** + * XORBruteForce constructor + */ + constructor() { + super(); + + this.name = "XOR Brute Force"; + this.module = "Default"; + this.description = "Enumerate all possible XOR solutions. Current maximum key length is 2 due to browser performance.

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.outputType = "string"; + this.args = [ + { + "name": "Key length", + "type": "number", + "value": 1 + }, + { + "name": "Sample length", + "type": "number", + "value": 100 + }, + { + "name": "Sample offset", + "type": "number", + "value": 0 + }, + { + "name": "Scheme", + "type": "option", + "value": ["Standard", "Input differential", "Output differential"] + }, + { + "name": "Null preserving", + "type": "boolean", + "value": false + }, + { + "name": "Print key", + "type": "boolean", + "value": true + }, + { + "name": "Output as hex", + "type": "boolean", + "value": false + }, + { + "name": "Crib (known plaintext string)", + "type": "binaryString", + "value": "" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [ + keyLength, + sampleLength, + sampleOffset, + scheme, + nullPreserving, + printKey, + outputHex, + rawCrib + ] = args, + crib = rawCrib.toLowerCase(), + output = []; + let result, + resultUtf8, + record = ""; + + input = input.slice(sampleOffset, sampleOffset + sampleLength); + + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage("Calculating " + Math.pow(256, keyLength) + " values..."); + + /** + * Converts an integer to an array of bytes expressing that number. + * + * @param {number} int + * @param {number} len - Length of the resulting array + * @returns {array} + */ + const intToByteArray = (int, len) => { + const res = Array(len).fill(0); + for (let i = len - 1; i >= 0; i--) { + res[i] = int & 0xff; + int = int >>> 8; + } + return res; + }; + + for (let key = 1, l = Math.pow(256, keyLength); key < l; key++) { + if (key % 10000 === 0 && ENVIRONMENT_IS_WORKER()) { + self.sendStatusMessage("Calculating " + l + " values... " + Math.floor(key / l * 100) + "%"); + } + + result = bitOp(input, intToByteArray(key, keyLength), xor, nullPreserving, scheme); + resultUtf8 = Utils.byteArrayToUtf8(result); + record = ""; + + if (crib && resultUtf8.toLowerCase().indexOf(crib) < 0) continue; + if (printKey) record += "Key = " + Utils.hex(key, (2*keyLength)) + ": "; + if (outputHex) { + record += toHex(result); + } else { + record += Utils.printable(resultUtf8, false); + } + + output.push(record); + } + + return output.join("\n"); + } + +} + +export default XORBruteForce; diff --git a/src/core/operations/XPathExpression.mjs b/src/core/operations/XPathExpression.mjs new file mode 100644 index 00000000..7e9d768a --- /dev/null +++ b/src/core/operations/XPathExpression.mjs @@ -0,0 +1,74 @@ +/** + * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import xmldom from "xmldom"; +import xpath from "xpath"; + +/** + * XPath expression operation + */ +class XPathExpression extends Operation { + + /** + * XPathExpression constructor + */ + constructor() { + super(); + + this.name = "XPath expression"; + this.module = "Code"; + this.description = "Extract information from an XML document with an XPath query"; + this.infoURL = "https://wikipedia.org/wiki/XPath"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "XPath", + "type": "string", + "value": "" + }, + { + "name": "Result delimiter", + "type": "binaryShortString", + "value": "\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [query, delimiter] = args; + + let doc; + try { + doc = new xmldom.DOMParser().parseFromString(input, "application/xml"); + } catch (err) { + throw new OperationError("Invalid input XML."); + } + + let nodes; + try { + nodes = xpath.select(query, doc); + } catch (err) { + throw new OperationError(`Invalid XPath. Details:\n${err.message}.`); + } + + const nodeToString = function(node) { + return node.toString(); + }; + + return nodes.map(nodeToString).join(delimiter); + } + +} + +export default XPathExpression; diff --git a/src/core/operations/YARARules.mjs b/src/core/operations/YARARules.mjs new file mode 100644 index 00000000..91a27963 --- /dev/null +++ b/src/core/operations/YARARules.mjs @@ -0,0 +1,122 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Yara from "libyara-wasm"; + +/** + * YARA Rules operation + */ +class YARARules extends Operation { + + /** + * YARARules constructor + */ + constructor() { + super(); + + this.name = "YARA Rules"; + this.module = "Yara"; + this.description = "YARA is a tool developed at VirusTotal, primarily aimed at helping malware researchers to identify and classify malware samples. It matches based on rules specified by the user containing textual or binary patterns and a boolean expression. For help on writing rules, see the YARA documentation."; + this.infoURL = "https://wikipedia.org/wiki/YARA"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Rules", + type: "text", + value: "", + rows: 5 + }, + { + name: "Show strings", + type: "boolean", + value: false + }, + { + name: "Show string lengths", + type: "boolean", + value: false + }, + { + name: "Show metadata", + type: "boolean", + value: false + }, + { + name: "Show counts", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage("Instantiating YARA..."); + const [rules, showStrings, showLengths, showMeta, showCounts] = args; + return new Promise((resolve, reject) => { + Yara().then(yara => { + if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Converting data for YARA."); + let matchString = ""; + + const inpArr = new Uint8Array(input); // Turns out embind knows that JS uint8array <==> C++ std::string + + if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Running YARA matching."); + + const resp = yara.run(inpArr, rules); + + if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Processing data."); + + if (resp.compileErrors.size() > 0) { + for (let i = 0; i < resp.compileErrors.size(); i++) { + const compileError = resp.compileErrors.get(i); + if (!compileError.warning) { + reject(new OperationError(`Error on line ${compileError.lineNumber}: ${compileError.message}`)); + } else { + matchString += `Warning on line ${compileError.lineNumber}: ${compileError.message}`; + } + } + } + const matchedRules = resp.matchedRules; + for (let i = 0; i < matchedRules.size(); i++) { + const rule = matchedRules.get(i); + const matches = rule.resolvedMatches; + let meta = ""; + if (showMeta && rule.metadata.size() > 0) { + meta += " ["; + for (let j = 0; j < rule.metadata.size(); j++) { + meta += `${rule.metadata.get(j).identifier}: ${rule.metadata.get(j).data}, `; + } + meta = meta.slice(0, -2) + "]"; + } + const countString = showCounts ? `${matches.size()} time${matches.size() > 1 ? "s" : ""}` : ""; + if (matches.size() === 0 || !(showStrings || showLengths)) { + matchString += `Input matches rule "${rule.ruleName}"${meta}${countString.length > 0 ? ` ${countString}`: ""}.\n`; + } else { + matchString += `Rule "${rule.ruleName}"${meta} matches (${countString}):\n`; + for (let j = 0; j < matches.size(); j++) { + const match = matches.get(j); + if (showStrings || showLengths) { + matchString += `Pos ${match.location}, ${showLengths ? `length ${match.matchLength}, ` : ""}identifier ${match.stringIdentifier}${showStrings ? `, data: "${match.data}"` : ""}\n`; + } + } + } + } + resolve(matchString); + }); + }); + } + +} + +export default YARARules; diff --git a/src/core/operations/Zip.mjs b/src/core/operations/Zip.mjs new file mode 100644 index 00000000..54a92726 --- /dev/null +++ b/src/core/operations/Zip.mjs @@ -0,0 +1,103 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib"; +import zip from "zlibjs/bin/zip.min"; + +const Zlib = zip.Zlib; + +const ZIP_COMPRESSION_METHOD_LOOKUP = { + "Deflate": Zlib.Zip.CompressionMethod.DEFLATE, + "None (Store)": Zlib.Zip.CompressionMethod.STORE +}; + +const ZIP_OS_LOOKUP = { + "MSDOS": Zlib.Zip.OperatingSystem.MSDOS, + "Unix": Zlib.Zip.OperatingSystem.UNIX, + "Macintosh": Zlib.Zip.OperatingSystem.MACINTOSH +}; + +/** + * Zip operation + */ +class Zip extends Operation { + + /** + * Zip constructor + */ + constructor() { + super(); + + this.name = "Zip"; + this.module = "Compression"; + this.description = "Compresses data using the PKZIP algorithm with the given filename.

No support for multiple files at this time."; + this.infoURL = "https://wikipedia.org/wiki/Zip_(file_format)"; + this.inputType = "ArrayBuffer"; + this.outputType = "File"; + this.args = [ + { + name: "Filename", + type: "string", + value: "file.txt" + }, + { + name: "Comment", + type: "string", + value: "" + }, + { + name: "Password", + type: "binaryString", + value: "" + }, + { + name: "Compression method", + type: "option", + value: ["Deflate", "None (Store)"] + }, + { + name: "Operating system", + type: "option", + value: ["MSDOS", "Unix", "Macintosh"] + }, + { + name: "Compression type", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {File} + */ + run(input, args) { + const filename = args[0], + password = Utils.strToByteArray(args[2]), + options = { + filename: Utils.strToByteArray(filename), + comment: Utils.strToByteArray(args[1]), + compressionMethod: ZIP_COMPRESSION_METHOD_LOOKUP[args[3]], + os: ZIP_OS_LOOKUP[args[4]], + deflateOption: { + compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[5]] + }, + }, + zip = new Zlib.Zip(); + + if (password.length) + zip.setPassword(password); + zip.addFile(new Uint8Array(input), options); + return new File([zip.compress()], filename); + } + +} + +export default Zip; diff --git a/src/core/operations/ZlibDeflate.mjs b/src/core/operations/ZlibDeflate.mjs new file mode 100644 index 00000000..d4685704 --- /dev/null +++ b/src/core/operations/ZlibDeflate.mjs @@ -0,0 +1,53 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; + +const Zlib = zlibAndGzip.Zlib; + +/** + * Zlib Deflate operation + */ +class ZlibDeflate extends Operation { + + /** + * ZlibDeflate constructor + */ + constructor() { + super(); + + this.name = "Zlib Deflate"; + this.module = "Compression"; + this.description = "Compresses data using the deflate algorithm adding zlib headers."; + this.infoURL = "https://wikipedia.org/wiki/Zlib"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Compression type", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const deflate = new Zlib.Deflate(new Uint8Array(input), { + compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] + }); + return new Uint8Array(deflate.compress()).buffer; + } + +} + +export default ZlibDeflate; diff --git a/src/core/operations/ZlibInflate.mjs b/src/core/operations/ZlibInflate.mjs new file mode 100644 index 00000000..a796cb3e --- /dev/null +++ b/src/core/operations/ZlibInflate.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {INFLATE_BUFFER_TYPE} from "../lib/Zlib"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; + +const Zlib = zlibAndGzip.Zlib; + +const ZLIB_BUFFER_TYPE_LOOKUP = { + "Adaptive": Zlib.Inflate.BufferType.ADAPTIVE, + "Block": Zlib.Inflate.BufferType.BLOCK, +}; + +/** + * Zlib Inflate operation + */ +class ZlibInflate extends Operation { + + /** + * ZlibInflate constructor + */ + constructor() { + super(); + + this.name = "Zlib Inflate"; + this.module = "Compression"; + this.description = "Decompresses data which has been compressed using the deflate algorithm with zlib headers."; + this.infoURL = "https://wikipedia.org/wiki/Zlib"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Start index", + type: "number", + value: 0 + }, + { + name: "Initial output buffer size", + type: "number", + value: 0 + }, + { + name: "Buffer expansion type", + type: "option", + value: INFLATE_BUFFER_TYPE + }, + { + name: "Resize buffer after decompression", + type: "boolean", + value: false + }, + { + name: "Verify result", + type: "boolean", + value: false + } + ]; + this.patterns = [ + { + match: "^\\x78(\\x01|\\x9c|\\xda|\\x5e)", + flags: "", + args: [0, 0, "Adaptive", false, false] + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inflate = new Zlib.Inflate(new Uint8Array(input), { + index: args[0], + bufferSize: args[1], + bufferType: ZLIB_BUFFER_TYPE_LOOKUP[args[2]], + resize: args[3], + verify: args[4] + }); + return new Uint8Array(inflate.decompress()).buffer; + } + +} + +export default ZlibInflate; diff --git a/src/core/vendor/Blowfish.mjs b/src/core/vendor/Blowfish.mjs new file mode 100644 index 00000000..f60fb90f --- /dev/null +++ b/src/core/vendor/Blowfish.mjs @@ -0,0 +1,652 @@ +/** + Blowfish.js from Dojo Toolkit 1.8.1 (https://github.com/dojo/dojox/tree/1.8/encoding) + Extracted by Sladex (xslade@gmail.com) + Shoehorned into working with mjs for CyberChef by Matt C (matt@artemisbot.uk) + + @license BSD + ======================================================================== + The "New" BSD License: + ********************** + + Copyright (c) 2005-2016, The Dojo Foundation + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Dojo Foundation nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +let crypto = {}; + + + +/* dojo-release-1.8.1/dojox/encoding/crypto/_base.js.uncompressed.js */ + +crypto.cipherModes = { + // summary: + // Enumeration for various cipher modes. + ECB:0, CBC:1, PCBC:2, CFB:3, OFB:4, CTR:5 +}; +crypto.outputTypes = { + // summary: + // Enumeration for input and output encodings. + Base64:0, Hex:1, String:2, Raw:3 +}; + + + +/* dojo-release-1.8.1/dojox/encoding/base64.js.uncompressed.js */ + +var base64 = {}; +var p="="; +var tab="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +base64.encode=function(/* byte[] */ba){ + // summary: + // Encode an array of bytes as a base64-encoded string + var s=[], l=ba.length; + var rm=l%3; + var x=l-rm; + for (var i=0; i>>18)&0x3f)); + s.push(tab.charAt((t>>>12)&0x3f)); + s.push(tab.charAt((t>>>6)&0x3f)); + s.push(tab.charAt(t&0x3f)); + } + // deal with trailers, based on patch from Peter Wood. + switch(rm){ + case 2:{ + var t=ba[i++]<<16|ba[i++]<<8; + s.push(tab.charAt((t>>>18)&0x3f)); + s.push(tab.charAt((t>>>12)&0x3f)); + s.push(tab.charAt((t>>>6)&0x3f)); + s.push(p); + break; + } + case 1:{ + var t=ba[i++]<<16; + s.push(tab.charAt((t>>>18)&0x3f)); + s.push(tab.charAt((t>>>12)&0x3f)); + s.push(p); + s.push(p); + break; + } + } + return s.join(""); // string +}; + +base64.decode=function(/* string */str){ + // summary: + // Convert a base64-encoded string to an array of bytes + var s=str.split(""), out=[]; + var l=s.length; + while(s[--l]==p){ } // strip off trailing padding + for (var i=0; i>>16)&0xff); + out.push((t>>>8)&0xff); + out.push(t&0xff); + } + // strip off any null bytes + while(out[out.length-1]==0){ out.pop(); } + return out; // byte[] +}; + + + +/* dojo-release-1.8.1/dojo/_base/lang.js.uncompressed.js */ + +var lang = {}; +lang.isString = function(it){ + // summary: + // Return true if it is a String + // it: anything + // Item to test. + return (typeof it == "string" || it instanceof String); // Boolean +}; + + + +/* dojo-release-1.8.1/dojo/_base/array.js.uncompressed.js */ + +var arrayUtil = {}; +arrayUtil.map = function(arr, callback, thisObject, Ctr){ + // summary: + // applies callback to each element of arr and returns + // an Array with the results + // arr: Array|String + // the array to iterate on. If a string, operates on + // individual characters. + // callback: Function|String + // a function is invoked with three arguments, (item, index, + // array), and returns a value + // thisObject: Object? + // may be used to scope the call to callback + // returns: Array + // description: + // This function corresponds to the JavaScript 1.6 Array.map() method, with one difference: when + // run over sparse arrays, this implementation passes the "holes" in the sparse array to + // the callback function with a value of undefined. JavaScript 1.6's map skips the holes in the sparse array. + // For more details, see: + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map + // example: + // | // returns [2, 3, 4, 5] + // | array.map([1, 2, 3, 4], function(item){ return item+1 }); + + // TODO: why do we have a non-standard signature here? do we need "Ctr"? + var i = 0, l = arr && arr.length || 0, out = new (Ctr || Array)(l); + if(l && typeof arr == "string") arr = arr.split(""); + if(typeof callback == "string") callback = cache[callback] || buildFn(callback); + if(thisObject){ + for(; i < l; ++i){ + out[i] = callback.call(thisObject, arr[i], i, arr); + } + }else{ + for(; i < l; ++i){ + out[i] = callback(arr[i], i, arr); + } + } + return out; // Array +}; + + + +/* dojo-release-1.8.1/dojox/encoding/crypto/Blowfish.js.uncompressed.js */ + +/* Blowfish + * Created based on the C# implementation by Marcus Hahn (http://www.hotpixel.net/) + * Unsigned math based on Paul Johnstone and Peter Wood patches. + * 2005-12-08 + */ +crypto.Blowfish = new function(){ + // summary: + // Object for doing Blowfish encryption/decryption. + var POW2=Math.pow(2,2); + var POW3=Math.pow(2,3); + var POW4=Math.pow(2,4); + var POW8=Math.pow(2,8); + var POW16=Math.pow(2,16); + var POW24=Math.pow(2,24); + var iv=null; // CBC mode initialization vector + var boxes={ + p:[ + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + ], + s0:[ + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + ], + s1:[ + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + ], + s2:[ + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + ], + s3:[ + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + ] + } +//////////////////////////////////////////////////////////////////////////// +// fixes based on patch submitted by Peter Wood (#5791) + function add(x,y){ + return (((x>>0x10)+(y>>0x10)+(((x&0xffff)+(y&0xffff))>>0x10))<<0x10)|(((x&0xffff)+(y&0xffff))&0xffff); + } + function xor(x,y){ + return (((x>>0x10)^(y>>0x10))<<0x10)|(((x&0xffff)^(y&0xffff))&0xffff); + } + + function $(v, box){ + var d=box.s3[v&0xff]; v>>=8; + var c=box.s2[v&0xff]; v>>=8; + var b=box.s1[v&0xff]; v>>=8; + var a=box.s0[v&0xff]; + + var r = (((a>>0x10)+(b>>0x10)+(((a&0xffff)+(b&0xffff))>>0x10))<<0x10)|(((a&0xffff)+(b&0xffff))&0xffff); + r = (((r>>0x10)^(c>>0x10))<<0x10)|(((r&0xffff)^(c&0xffff))&0xffff); + return (((r>>0x10)+(d>>0x10)+(((r&0xffff)+(d&0xffff))>>0x10))<<0x10)|(((r&0xffff)+(d&0xffff))&0xffff); + } +//////////////////////////////////////////////////////////////////////////// + function eb(o, box){ + // TODO: see if this can't be made more efficient + var l=o.left; + var r=o.right; + l=xor(l,box.p[0]); + r=xor(r,xor($(l,box),box.p[1])); + l=xor(l,xor($(r,box),box.p[2])); + r=xor(r,xor($(l,box),box.p[3])); + l=xor(l,xor($(r,box),box.p[4])); + r=xor(r,xor($(l,box),box.p[5])); + l=xor(l,xor($(r,box),box.p[6])); + r=xor(r,xor($(l,box),box.p[7])); + l=xor(l,xor($(r,box),box.p[8])); + r=xor(r,xor($(l,box),box.p[9])); + l=xor(l,xor($(r,box),box.p[10])); + r=xor(r,xor($(l,box),box.p[11])); + l=xor(l,xor($(r,box),box.p[12])); + r=xor(r,xor($(l,box),box.p[13])); + l=xor(l,xor($(r,box),box.p[14])); + r=xor(r,xor($(l,box),box.p[15])); + l=xor(l,xor($(r,box),box.p[16])); + o.right=l; + o.left=xor(r,box.p[17]); + } + + function db(o, box){ + var l=o.left; + var r=o.right; + l=xor(l,box.p[17]); + r=xor(r,xor($(l,box),box.p[16])); + l=xor(l,xor($(r,box),box.p[15])); + r=xor(r,xor($(l,box),box.p[14])); + l=xor(l,xor($(r,box),box.p[13])); + r=xor(r,xor($(l,box),box.p[12])); + l=xor(l,xor($(r,box),box.p[11])); + r=xor(r,xor($(l,box),box.p[10])); + l=xor(l,xor($(r,box),box.p[9])); + r=xor(r,xor($(l,box),box.p[8])); + l=xor(l,xor($(r,box),box.p[7])); + r=xor(r,xor($(l,box),box.p[6])); + l=xor(l,xor($(r,box),box.p[5])); + r=xor(r,xor($(l,box),box.p[4])); + l=xor(l,xor($(r,box),box.p[3])); + r=xor(r,xor($(l,box),box.p[2])); + l=xor(l,xor($(r,box),box.p[1])); + o.right=l; + o.left=xor(r,box.p[0]); + } + + // Note that we aren't caching contexts here; it might take a little longer + // but we should be more secure this way. + function init(key){ + var k=key; + if(lang.isString(k)){ + k = arrayUtil.map(k.split(""), function(item){ + return item.charCodeAt(0) & 0xff; + }); + } + + // init the boxes + var pos=0, data=0, res={ left:0, right:0 }, i, j, l; + var box = { + p: arrayUtil.map(boxes.p.slice(0), function(item){ + var l=k.length, j; + for(j=0; j<4; j++){ data=(data*POW8)|k[pos++ % l]; } + return (((item>>0x10)^(data>>0x10))<<0x10)|(((item&0xffff)^(data&0xffff))&0xffff); + }), + s0:boxes.s0.slice(0), + s1:boxes.s1.slice(0), + s2:boxes.s2.slice(0), + s3:boxes.s3.slice(0) + }; + + // encrypt p and the s boxes + for(i=0, l=box.p.length; i> 3, pos=0, o={}, isCBC=(mode==crypto.cipherModes.CBC); + var vector={left:iv.left||null, right:iv.right||null}; + for(var i=0; i>0x10)^(vector.left>>0x10))<<0x10)|(((o.left&0xffff)^(vector.left&0xffff))&0xffff); + o.right=(((o.right>>0x10)^(vector.right>>0x10))<<0x10)|(((o.right&0xffff)^(vector.right&0xffff))&0xffff); + } + + eb(o, bx); // encrypt the block + + if(isCBC){ + vector.left=o.left; + vector.right=o.right; + } + + cipher.push((o.left>>24)&0xff); + cipher.push((o.left>>16)&0xff); + cipher.push((o.left>>8)&0xff); + cipher.push(o.left&0xff); + cipher.push((o.right>>24)&0xff); + cipher.push((o.right>>16)&0xff); + cipher.push((o.right>>8)&0xff); + cipher.push(o.right&0xff); + pos+=8; + } + + switch(out){ + case crypto.outputTypes.Hex:{ + return arrayUtil.map(cipher, function(item){ + return (item<=0xf?'0':'')+item.toString(16); + }).join(""); // string + } + case crypto.outputTypes.String:{ + return cipher.join(""); // string + } + case crypto.outputTypes.Raw:{ + return cipher; // array + } + default:{ + return base64.encode(cipher); // string + } + } + }; + + this.decrypt = function(/* string */ciphertext, /* string */key, /* object? */ao){ + // summary: + // decrypts ciphertext using key; allows specification of how ciphertext is encoded via ao. + var ip=crypto.outputTypes.Base64; + var mode=crypto.cipherModes.ECB; + if (ao){ + if (ao.outputType) ip=ao.outputType; + if (ao.cipherMode) mode=ao.cipherMode; + } + var bx = init(key); + var pt=[]; + + var c=null; + switch(ip){ + case crypto.outputTypes.Hex:{ + c = []; + for(var i=0, l=ciphertext.length-1; i> 3, pos=0, o={}, isCBC=(mode==crypto.cipherModes.CBC); + var vector={left:iv.left||null, right:iv.right||null}; + for(var i=0; i>0x10)^(vector.left>>0x10))<<0x10)|(((o.left&0xffff)^(vector.left&0xffff))&0xffff); + o.right=(((o.right>>0x10)^(vector.right>>0x10))<<0x10)|(((o.right&0xffff)^(vector.right&0xffff))&0xffff); + vector.left=left; + vector.right=right; + } + + pt.push((o.left>>24)&0xff); + pt.push((o.left>>16)&0xff); + pt.push((o.left>>8)&0xff); + pt.push(o.left&0xff); + pt.push((o.right>>24)&0xff); + pt.push((o.right>>16)&0xff); + pt.push((o.right>>8)&0xff); + pt.push(o.right&0xff); + pos+=8; + } + + // check for padding, and remove. + if(pt[pt.length-1]==pt[pt.length-2]||pt[pt.length-1]==0x01){ + var n=pt[pt.length-1]; + pt.splice(pt.length-n, n); + } + + // convert to string + return arrayUtil.map(pt, function(item){ + return String.fromCharCode(item); + }).join(""); // string + }; + + this.setIV("0000000000000000", crypto.outputTypes.Hex); +}(); + +export const Blowfish = crypto.Blowfish; diff --git a/src/core/vendor/DisassembleX86-64.mjs b/src/core/vendor/DisassembleX86-64.mjs new file mode 100644 index 00000000..f0d30511 --- /dev/null +++ b/src/core/vendor/DisassembleX86-64.mjs @@ -0,0 +1,5721 @@ +/*------------------------------------------------------------------------------------------------------------------------- +Binary byte code array. +--------------------------------------------------------------------------------------------------------------------------- +Function ^LoadBinCode()^ takes a string input of hex and loads it into the BinCode array it is recommended that the location +the hex string is read from a file, or sector matches the disassemblers set base address using function ^SetBasePosition()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var BinCode = []; + +/*------------------------------------------------------------------------------------------------------------------------- +When Bit Mode is 2 the disassembler will default to decoding 64 bit binary code possible settings are 0=16 bit, 1=32 bit, 2=64 bit. +-------------------------------------------------------------------------------------------------------------------------*/ + +var BitMode = 2; + +/*------------------------------------------------------------------------------------------------------------------------- +The variable CodePos is the position in the BinCode array starts at 0 for each new section loaded in by ^LoadBinCode()^. +--------------------------------------------------------------------------------------------------------------------------- +The function ^NextByte()^ moves CodePos, and the Disassemblers Base address by one stored in Pos64, Pos32. +The BinCode array is designed for loading in a section of binary that is supposed to be from the set Base address in Pos64, and Pos32. +-------------------------------------------------------------------------------------------------------------------------*/ + +var CodePos = 0x00000000; + +/*------------------------------------------------------------------------------------------------------------------------- +The Pos64, and Pos32 is the actual base address that instructions are supposed to be from in memory when they are loaded +into the BinCode array using the Function ^LoadBinCode()^. +--------------------------------------------------------------------------------------------------------------------------- +The function ^SetBasePosition()^ sets the base location in Pos64, and Pos32, and Code Segment. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Pos64 = 0x00000000, Pos32 = 0x00000000; + +/*------------------------------------------------------------------------------------------------------------------------- +Code Segment is used in 16 bit binaries in which the segment is times 16 (Left Shift 4) added to the 16 bit address position. +This was done to load more programs in 16 bit space at an selected segment location. In 16 bit X86 processors the instruction +pointer register counts from 0000 hex to FFFF hex and starts over at 0000 hex. Allowing a program to be a max length of +65535 bytes long. The Code Segment is multiplied by 16 then is added to the instruction pointer position in memory. +--------------------------------------------------------------------------------------------------------------------------- +In 32 bit, and 64 bit the address combination is large enough that segmented program loading was no longer required. +However 32 bit still supports Segmented addressing if used, but 64 bit binaries do not. Also if the code segment is set +36, or higher in 32 bit binaries this sets SEG:OFFSET address format for each instructions Memory position. +--------------------------------------------------------------------------------------------------------------------------- +In 64 bit mode, an programs instructions are in a 64 bit address using the processors full instruction pointer, but in 32 +bit instructions the first 32 bit of the instruction pointer is used. In 16 bit the first 16 bits of the instruction pointer +is used, but with the code segment. Each instruction is executed in order by the Instruction pointer that goes in sectional sizes +"RIP (64)/EIP (32)/IP (16)" Depending on the Bit mode the 64 bit CPU is set in, or if the CPU is 32 bit to begin with. +-------------------------------------------------------------------------------------------------------------------------*/ + +var CodeSeg = 0x0000; + +/*------------------------------------------------------------------------------------------------------------------------- +The InstructionHex String stores the Bytes of decoded instructions. It is shown to the left side of the disassembled instruction. +-------------------------------------------------------------------------------------------------------------------------*/ + +var InstructionHex = ""; + +/*------------------------------------------------------------------------------------------------------------------------- +The InstructionPos String stores the start position of a decoded binary instruction in memory from the function ^GetPosition()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var InstructionPos = ""; + +/*------------------------------------------------------------------------------------------------------------------------- +Decoding display options. +-------------------------------------------------------------------------------------------------------------------------*/ + +var ShowInstructionHex = true; //setting to show the hex code of the instruction beside the decoded instruction output. +var ShowInstructionPos = true; //setting to show the instruction address position. + +/*------------------------------------------------------------------------------------------------------------------------- +The Opcode, and Opcode map. +--------------------------------------------------------------------------------------------------------------------------- +The first 0 to 255 (Byte) value that is read is the selected instruction code, however some codes are used as Adjustment to +remove limitations that are read by the function ^DecodePrefixAdjustments()^. +--------------------------------------------------------------------------------------------------------------------------- +Because X86 was limited to 255 instructions An number was sacrificed to add more instructions. +By using one of the 0 to 255 instructions like 15 which is "0F" as an hex number the next 0 to 255 value is an hole +new set of 0 to 255 instructions these are called escape code prefixes. +--------------------------------------------------------------------------------------------------------------------------- +Bellow XX is the opcode combined with the adjustment escape codes thus how opcode is used numerically in the disassembler. +--------------------------------------------------------------------------------------------------------------------------- +00,00000000 = 0, lower 8 bit opcode at max 00,11111111 = 255. (First byte opcodes XX) Opcodes values 0 to 255. +01,00000000 = 256, lower 8 bit opcode at max 01,11111111 = 511. (Two byte opcodes 0F XX) Opcodes values 256 to 511. +10,00000000 = 512, lower 8 bit opcode at max 10,11111111 = 767. (Three byte opcodes 0F 38 XX) Opcodes values 512 to 767. +11,00000000 = 768, lower 8 bit opcode at max 11,11111111 = 1023. (Three byte opcodes 0F 3A XX) Opcodes values 768 to 1023. +--------------------------------------------------------------------------------------------------------------------------- +The lower 8 bits is the selectable opcode 0 to 255 plus one from 255 is 1,00000000 = 256 thus 256 acts as the place holder. +The vector adjustment codes contain an map bit selection the map bits go in order to the place holder map bits are in. +This makes it so the map bits can be placed where the place holder bits are. +--------------------------------------------------------------------------------------------------------------------------- +VEX.mmmmm = 000_00b (1-byte map), 000_01b (2-byte map), 000_10b (0Fh,38h), 000_11b (0Fh,3Ah) +EVEX.mm = 00b (1-byte map), 01b (2-byte map), 10b (0Fh,38h), 11b (0Fh,3Ah) +-------------------------------------------------------------------------------------------------------------------------- +Function ^DecodePrefixAdjustments()^ reads opcodes that act as settings it only ends when Opcode is an actual +instruction code value 0 to 1023 inducing escape codes. Opcode is Used by function ^DecodeOpcode()^ with the Mnemonic array. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Opcode = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +Opcode is used as the index for the point in the structure to land on in the "Mnemonics". +--------------------------------------------------------------------------------------------------------------------------- +X86 has an amazing architectural pattern that is like an fractal in many ways. Previously an experiment was done to make +this an one dimensional array, but after testing it proved that it was slower because each of the branches had to be +calculated to an unique index in memory in which lots of combinations map to the same instructions well some changed. +The calculation took more time than comparing if an index is an reference to another array to optionally use an encoding. +--------------------------------------------------------------------------------------------------------------------------- +The first branch is an array 2 in size which separates opcodes that change between register, and memory mode. +--------------------------------------------------------------------------------------------------------------------------- +The second branch is an array 8 in size which uses an register as an 0 to 7 value for the selected instruction code called grouped opcodes. +The second branch can be branched into another array 8 in size this covers the last three bits of the ModR/M byte for static opcodes. +--------------------------------------------------------------------------------------------------------------------------- +The third branch is an array 4 in size which is the SIMD modes. The third branch can branch to an array 4 in size again under +any of the 4 elements in the SIMD modes for instructions that change by vector extension type. +--------------------------------------------------------------------------------------------------------------------------- +The fifth branch is an array 3 in size which branches to encoding's that change by the set size attribute. +--------------------------------------------------------------------------------------------------------------------------- +Each branch can be combined in any combination, but only in order. If we branch to an array 2 in size under an specific opcode +like this ["",""] then decide to branch memory mode to an array 4 in size we end up with ["",["","","",""]] for making it only +active in memory mode and controlled by SIMD modes, but then if we decide to branch one of the 4 SIMD modes to an array 8 +in size for register opcode separation under one SIMD mode, or an few we can't. We can only branch to an array 3 in size +as that comes next after the array 4 in size. WE also do not need the first branch to be an array it can be an single opcode +encoding. We also do not need the first branch to be an array 2 in size it can be any starting branch then the rest must go +in order from that branch point. +--------------------------------------------------------------------------------------------------------------------------- +Opcode is used by the function ^DecodeOpcode()^ after ^DecodePrefixAdjustments()^. +The function ^DecodeOpcode()^ Gives back the instructions name. +--------------------------------------------------------------------------------------------------------------------------*/ + +const Mnemonics = [ + /*------------------------------------------------------------------------------------------------------------------------ + First Byte operations 0 to 255. + ------------------------------------------------------------------------------------------------------------------------*/ + "ADD","ADD","ADD","ADD","ADD","ADD","PUSH ES","POP ES", + "OR","OR","OR","OR","OR","OR","PUSH CS" + , + "" //*Two byte instructions prefix sets opcode 01,000000000 next byte read is added to the lower 8 bit's. + , + "ADC","ADC","ADC","ADC","ADC","ADC","PUSH SS","POP SS", + "SBB","SBB","SBB","SBB","SBB","SBB","PUSH DS","POP DS", + "AND","AND","AND","AND","AND","AND", + "ES:[", //Extra segment override sets SegOveride "ES:[". + "DAA", + "SUB","SUB","SUB","SUB","SUB","SUB", + "CS:[", //Code segment override sets SegOveride "CS:[". + "DAS", + "XOR","XOR","XOR","XOR","XOR","XOR", + "SS:[", //Stack segment override sets SegOveride "SS:[". + "AAA", + "CMP","CMP","CMP","CMP","CMP","CMP", + "DS:[", //Data Segment override sets SegOveride "DS:[". + "AAS", + /*------------------------------------------------------------------------------------------------------------------------ + Start of Rex Prefix adjustment setting uses opcodes 40 to 4F. These opcodes are only decoded as adjustment settings + by the function ^DecodePrefixAdjustments()^ while in 64 bit mode. If not in 64 bit mode the codes are not read + by the function ^DecodePrefixAdjustments()^ which allows the opcode to be set 40 to 4F hex in which the defined + instructions bellow are used by ^DecodeOpcode()^. + ------------------------------------------------------------------------------------------------------------------------*/ + "INC","INC","INC","INC","INC","INC","INC","INC", + "DEC","DEC","DEC","DEC","DEC","DEC","DEC","DEC", + /*------------------------------------------------------------------------------------------------------------------------ + End of the Rex Prefix adjustment setting opcodes. + ------------------------------------------------------------------------------------------------------------------------*/ + "PUSH","PUSH","PUSH","PUSH","PUSH","PUSH","PUSH","PUSH", + "POP","POP","POP","POP","POP","POP","POP","POP", + ["PUSHA","PUSHAD",""],["POPA","POPAD",""], + ["BOUND","BOUND",""], //EVEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined BOUND instruction is used. + "MOVSXD", + "FS:[","GS:[", //Sets SegOveride "FS:[" next opcode sets "GS:[". + "","", //Operand Size, and Address size adjustment to ModR/M. + "PUSH","IMUL","PUSH","IMUL", + "INS","INS","OUTS","OUTS", + "JO","JNO","JB","JAE","JE","JNE","JBE","JA", + "JS","JNS","JP","JNP","JL","JGE","JLE","JG", + ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], //Group opcode uses the ModR/M register selection 0 though 7 giving 8 instruction in one opcode. + ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], + ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], + ["ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"], + "TEST","TEST","XCHG","XCHG", + "MOV","MOV","MOV","MOV","MOV", + ["LEA","???"], //*ModR/M Register, and memory mode separation. + "MOV", + ["POP","???","???","???","???","???","???","???"], + [["NOP","","",""],["NOP","","",""],["PAUSE","","",""],["NOP","","",""]], + "XCHG","XCHG","XCHG","XCHG","XCHG","XCHG","XCHG", + ["CWDE","CBW","CDQE"], //*Opcode 0 to 3 for instructions that change name by size setting. + ["CDQ","CWD","CQO"], + "CALL","WAIT", + ["PUSHFQ","PUSHF","PUSHFQ"], + ["POPFQ","POPF","POPFQ"], + "SAHF","LAHF", + "MOV","MOV","MOV","MOV", + "MOVS","MOVS", + "CMPS","CMPS", + "TEST","TEST", + "STOS","STOS", + "LODS","LODS", + "SCAS","SCAS", + "MOV","MOV","MOV","MOV","MOV","MOV","MOV","MOV", + "MOV","MOV","MOV","MOV","MOV","MOV","MOV","MOV", + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + "RET","RET", + "LES", //VEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined instruction is used. + "LDS", //VEX prefix adjustment settings only if used in register to register, or in 64 bit mode, otherwise the defined instruction is used. + [ + "MOV","???","???","???","???","???","???", + ["XABORT","XABORT","XABORT","XABORT","XABORT","XABORT","XABORT","XABORT"] + ], + [ + "MOV","???","???","???","???","???","???", + ["XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN","XBEGIN"] + ], + "ENTER","LEAVE","RETF","RETF","INT","INT","INTO", + ["IRETD","IRET","IRETQ"], + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + ["ROL","ROR","RCL","RCR","SHL","SHR","SAL","SAR"], + "AAMB","AADB","???", + "XLAT", + /*------------------------------------------------------------------------------------------------------------------------ + X87 FPU. + ------------------------------------------------------------------------------------------------------------------------*/ + [ + ["FADD","FMUL","FCOM","FCOMP","FSUB","FSUBR","FDIV","FDIVR"], + ["FADD","FMUL","FCOM","FCOMP","FSUB","FSUBR","FDIV","FDIVR"] + ], + [ + ["FLD","???","FST","FSTP","FLDENV","FLDCW","FNSTENV","FNSTCW"], + [ + "FLD","FXCH", + ["FNOP","???","???","???","???","???","???","???"], + "FSTP1", + ["FCHS","FABS","???","???","FTST","FXAM","???","???"], + ["FLD1","FLDL2T","FLDL2E","FLDPI","FLDLG2","FLDLN2","FLDZ","???"], + ["F2XM1","FYL2X","FPTAN","FPATAN","FXTRACT","FPREM1","FDECSTP","FINCSTP"], + ["FPREM","FYL2XP1","FSQRT","FSINCOS","FRNDINT","FSCALE","FSIN","FCOS"] + ] + ], + [ + ["FIADD","FIMUL","FICOM","FICOMP","FISUB","FISUBR","FIDIV","FIDIVR"], + [ + "FCMOVB","FCMOVE","FCMOVBE","FCMOVU","???", + ["???","FUCOMPP","???","???","???","???","???","???"], + "???","???" + ] + ], + [ + ["FILD","FISTTP","FIST","FISTP","???","FLD","???","FSTP"], + [ + "CMOVNB","FCMOVNE","FCMOVNBE","FCMOVNU", + ["FENI","FDISI","FNCLEX","FNINIT","FSETPM","???","???","???"], + "FUCOMI","FCOMI","???" + ] + ], + [ + ["FADD","FMUL","FCOM","DCOMP","FSUB","FSUBR","FDIV","FDIVR"], + ["FADD","FMUL","FCOM2","FCOMP3","FSUBR","FSUB","FDIVR","FDIV"] + ], + [ + ["FLD","FISTTP","FST","FSTP","FRSTOR","???","FNSAVE","FNSTSW"], + ["FFREE","FXCH4","FST","FSTP","FUCOM","FUCOMP","???","???"] + ], + [ + ["FIADD","FIMUL","FICOM","FICOMP","FISUB","FISUBR","FIDIV","FIDIVR"], + [ + "FADDP","FMULP","FCOMP5", + ["???","FCOMPP","???","???","???","???","???","???"], + "FSUBRP","FSUBP","FDIVRP","FDIVP" + ] + ], + [ + ["FILD","FISTTP","FIST","FISTP","FBLD","FILD","FBSTP","FISTP"], + [ + "FFREEP","FXCH7","FSTP8","FSTP9", + ["FNSTSW","???","???","???","???","???","???","???"], + "FUCOMIP","FCOMIP","???" + ] + ], + /*------------------------------------------------------------------------------------------------------------------------ + End of X87 FPU. + ------------------------------------------------------------------------------------------------------------------------*/ + "LOOPNE","LOOPE","LOOP","JRCXZ", + "IN","IN","OUT","OUT", + "CALL","JMP","JMP","JMP", + "IN","IN","OUT","OUT", + /*------------------------------------------------------------------------------------------------------------------------ + The Repeat, and lock prefix opcodes apply to the next opcode. + ------------------------------------------------------------------------------------------------------------------------*/ + "LOCK", //Adds LOCK to the start of instruction. When Opcode F0 hex is read by function ^DecodePrefixAdjustments()^ sets PrefixG2 to LOCK. + "ICEBP", //Instruction ICEBP. + "REPNE", //Adds REPNE (Opcode F2 hex) to the start of instruction. Read by function ^DecodePrefixAdjustments()^ sets PrefixG1 to REPNE. + "REP", //Adds REP (Opcode F3 hex) to the start of instruction. Read by function ^DecodePrefixAdjustments()^ sets PrefixG1 to REP. + /*------------------------------------------------------------------------------------------------------------------------ + End of Repeat, and lock instruction adjustment codes. + ------------------------------------------------------------------------------------------------------------------------*/ + "HLT","CMC", + ["TEST","???","NOT","NEG","MUL","IMUL","DIV","IDIV"], + ["TEST","???","NOT","NEG","MUL","IMUL","DIV","IDIV"], + "CLC","STC","CLI","STI","CLD","STD", + ["INC","DEC","???","???","???","???","???","???"], + [ + ["INC","DEC","CALL","CALL","JMP","JMP","PUSH","???"], + ["INC","DEC","CALL","???","JMP","???","PUSH","???"] + ], + /*------------------------------------------------------------------------------------------------------------------------ + Two Byte Opcodes 256 to 511. Opcodes plus 256 goes to 511 used by escape code "0F", Or + set directly by adding map bits "01" because "01 00000000" bin = 256 plus opcode. + ------------------------------------------------------------------------------------------------------------------------*/ + [ + ["SLDT","STR","LLDT","LTR","VERR","VERW","JMPE","???"], + ["SLDT","STR","LLDT","LTR","VERR","VERW","JMPE","???"] + ], + [ + ["SGDT","SIDT","LGDT","LIDT","SMSW","???","LMSW","INVLPG"], + [ + ["???","VMCALL","VMLAUNCH","VMRESUME","VMXOFF","???","???","???"], + ["MONITOR","MWAIT","CLAC","STAC","???","???","???","ENCLS"], + ["XGETBV","XSETBV","???","???","VMFUNC","XEND","XTEST","ENCLU"], + ["VMRUN","VMMCALL","VMLOAD","VMSAVE","STGI","CLGI","SKINIT","INVLPGA"], + "SMSW","???","LMSW", + ["SWAPGS","RDTSCP","MONITORX","MWAITX","???","???","???","???"] + ] + ], + ["LAR","LAR"],["LSL","LSL"],"???", + "SYSCALL","CLTS","SYSRET","INVD", + "WBINVD","???","UD2","???", + [["PREFETCH","PREFETCHW","???","???","???","???","???","???"],"???"], + "FEMMS", + "", //3DNow Instruction name is encoded by the IMM8 operand. + [ + ["MOVUPS","MOVUPD","MOVSS","MOVSD"], + ["MOVUPS","MOVUPD","MOVSS","MOVSD"] + ], + [ + ["MOVUPS","MOVUPD","MOVSS","MOVSD"], + ["MOVUPS","MOVUPD","MOVSS","MOVSD"] + ], + [ + ["MOVLPS","MOVLPD","MOVSLDUP","MOVDDUP"], + ["MOVHLPS","???","MOVSLDUP","MOVDDUP"] + ], + [["MOVLPS","MOVLPD","???","???"],"???"], + ["UNPCKLPS","UNPCKLPD","???","???"], //An instruction with 4 operations uses the 4 SIMD modes as an Vector instruction. + ["UNPCKHPS","UNPCKHPD","???","???"], + [["MOVHPS","MOVHPD","MOVSHDUP","???"],["MOVLHPS","???","MOVSHDUP","???"]], + [["MOVHPS","MOVHPD","???","???"],"???"], + [["PREFETCHNTA","PREFETCHT0","PREFETCHT1","PREFETCHT2","???","???","???","???"],"???"], + "???", + [[["BNDLDX","","",""],["BNDMOV","","",""],["BNDCL","","",""],["BNDCU","","",""]], + ["???",["BNDMOV","","",""],["BNDCL","","",""],["BNDCU","","",""]]], + [[["BNDSTX","","",""],["BNDMOV","","",""],["BNDMK","","",""],["BNDCN","","",""]], + ["???",["BNDMOV","","",""],"???",["BNDCN","","",""]]], + "???","???","???", + "NOP", + ["???","MOV"],["???","MOV"], //CR and DR register Move + ["???","MOV"],["???","MOV"], //CR and DR register Move + ["???","MOV"],"???", //TR (TEST REGISTER) register Move + ["???","MOV"],"???", //TR (TEST REGISTER) register Move + [ + ["MOVAPS","MOVAPS","MOVAPS","MOVAPS"], + ["MOVAPD","MOVAPD","MOVAPD","MOVAPD"], + "???","???" + ], + [ + [ + ["MOVAPS","MOVAPS","MOVAPS","MOVAPS"], + ["MOVAPD","MOVAPD","MOVAPD","MOVAPD"], + ["","","",["MOVNRAPS","MOVNRNGOAPS","MOVNRAPS"]], + ["","","",["MOVNRAPD","MOVNRNGOAPD","MOVNRAPD"]] + ], + [ + ["MOVAPS","MOVAPS","MOVAPS","MOVAPS"], + ["MOVAPD","MOVAPD","MOVAPD","MOVAPD"], + "???","???" + ] + ], + [ + ["CVTPI2PS","","",""],["CVTPI2PD","","",""], //Is not allowed to be Vector encoded. + "CVTSI2SS","CVTSI2SD" + ], + [ + [ + "MOVNTPS","MOVNTPD", + ["MOVNTSS","","",""],["MOVNTSD","","",""] //SSE4a can not be vector encoded. + ],"???" + ], + [ + ["CVTTPS2PI","","",""],["CVTTPD2PI","","",""], //Is not allowed to be Vector encoded. + "CVTTSS2SI","CVTTSD2SI" + ], + [ + ["CVTPS2PI","","",""],["CVTPD2PI","","",""], //Is not allowed to be Vector encoded. + "CVTSS2SI","CVTSD2SI" + ], + ["UCOMISS","UCOMISD","???","???"], + ["COMISS","COMISD","???","???"], + "WRMSR","RDTSC","RDMSR","RDPMC", + "SYSENTER","SYSEXIT","???", + "GETSEC", + "", //*Three byte instructions prefix combo 0F 38 (Opcode = 01,00111000) sets opcode 10,000000000 next byte read is added to the lower 8 bit's. + "???", + "", //*Three byte instructions prefix combo 0F 3A (Opcode = 01,00111010) sets opcode 11,000000000 next byte read is added to the lower 8 bit's. + "???","???","???","???","???", + "CMOVO", + [ + ["CMOVNO",["KANDW","","KANDQ"],"",""], + ["CMOVNO",["KANDB","","KANDD"],"",""],"","" + ], + [ + ["CMOVB",["KANDNW","","KANDNQ"],"",""], + ["CMOVB",["KANDNB","","KANDND"],"",""],"","" + ], + [["CMOVAE","KANDNR","",""],"","",""], + [ + ["CMOVE",["KNOTW","","KNOTQ"],"",""], + ["CMOVE",["KNOTB","","KNOTD"],"",""],"","" + ], + [ + ["CMOVNE",["KORW","","KORQ"],"",""], + ["CMOVNE",["KORB","","KORD"],"",""],"","" + ], + [ + ["CMOVBE",["KXNORW","","KXNORQ"],"",""], + ["CMOVBE",["KXNORB","","KXNORD"],"",""],"","" + ], + [ + ["CMOVA",["KXORW","","KXORQ"],"",""], + ["CMOVA",["KXORB","","KXORD"],"",""],"","" + ], + [["CMOVS","KMERGE2L1H","",""],"","",""], + [["CMOVNS","KMERGE2L1L","",""],"","",""], + [ + ["CMOVP",["KADDW","","KADDQ"],"",""], + ["CMOVP",["KADDB","","KADDD"],"",""],"","" + ], + [ + ["CMOVNP",["KUNPCKWD","","KUNPCKDQ"],"",""], + ["CMOVNP",["KUNPCKBW","","???"],"",""],"","" + ], + "CMOVL","CMOVGE","CMOVLE","CMOVG", + [ + "???", + [ + ["MOVMSKPS","MOVMSKPS","",""],["MOVMSKPD","MOVMSKPD","",""], + "???","???" + ] + ], + ["SQRTPS","SQRTPD","SQRTSS","SQRTSD"], + [ + ["RSQRTPS","RSQRTPS","",""],"???", + ["RSQRTSS","RSQRTSS","",""],"???" + ], + [ + ["RCPPS","RCPPS","",""],"???", + ["RCPSS","RCPSS","",""],"???" + ], + ["ANDPS","ANDPD","???","???"], + ["ANDNPS","ANDNPD","???","???"], + ["ORPS","ORPD","???","???"], + ["XORPS","XORPD","???","???"], + [ + ["ADDPS","ADDPS","ADDPS","ADDPS"], + ["ADDPD","ADDPD","ADDPD","ADDPD"], + "ADDSS","ADDSD" + ], + [ + ["MULPS","MULPS","MULPS","MULPS"], + ["MULPD","MULPD","MULPD","MULPD"], + "MULSS","MULSD" + ], + [ + ["CVTPS2PD","CVTPS2PD","CVTPS2PD","CVTPS2PD"], + ["CVTPD2PS","CVTPD2PS","CVTPD2PS","CVTPD2PS"], + "CVTSS2SD","CVTSD2SS" + ], + [["CVTDQ2PS","","CVTQQ2PS"],["CVTPS2DQ","","???"],"CVTTPS2DQ","???"], + [ + ["SUBPS","SUBPS","SUBPS","SUBPS"], + ["SUBPD","SUBPD","SUBPD","SUBPD"], + "SUBSS","SUBSD" + ], + ["MINPS","MINPD","MINSS","MINSD"], + ["DIVPS","DIVPD","DIVSS","DIVSD"], + ["MAXPS","MAXPD","MAXSS","MAXSD"], + [["PUNPCKLBW","","",""],"PUNPCKLBW","",""], + [["PUNPCKLWD","","",""],"PUNPCKLWD","",""], + [["PUNPCKLDQ","","",""],"PUNPCKLDQ","",""], + [["PACKSSWB","","",""],"PACKSSWB","",""], + [["PCMPGTB","","",""],["PCMPGTB","PCMPGTB","PCMPGTB",""],"",""], + [["PCMPGTW","","",""],["PCMPGTW","PCMPGTW","PCMPGTW",""],"",""], + [["PCMPGTD","","",""],["PCMPGTD","PCMPGTD",["PCMPGTD","","???"],["PCMPGTD","","???"]],"",""], + [["PACKUSWB","","",""],"PACKUSWB","",""], + [["PUNPCKHBW","","",""],"PUNPCKHBW","",""], + [["PUNPCKHWD","","",""],"PUNPCKHWD","",""], + [["PUNPCKHDQ","","",""],["PUNPCKHDQ","","???"],"",""], + [["PACKSSDW","","",""],["PACKSSDW","","???"],"",""], + ["???","PUNPCKLQDQ","???","???"], + ["???","PUNPCKHQDQ","???","???"], + [["MOVD","","",""],["MOVD","","MOVQ"],"",""], + [ + [ + ["MOVQ","","",""], + ["MOVDQA","MOVDQA",["MOVDQA32","","MOVDQA64"],["MOVDQA32","","MOVDQA64"]], + ["MOVDQU","MOVDQU",["MOVDQU32","","MOVDQU64"],""], + ["","",["MOVDQU8","","MOVDQU16"],""] + ], + [ + ["MOVQ","","",""], + ["MOVDQA","MOVDQA",["MOVDQA32","","MOVDQA64"],["MOVDQA32","","MOVDQA64"]], + ["MOVDQU","MOVDQU",["MOVDQU32","","MOVDQU64"],""], + ["","",["MOVDQU8","","MOVDQU16"],""] + ] + ], + [ + ["PSHUFW","","",""], + ["PSHUFD","PSHUFD",["PSHUFD","","???"],["PSHUFD","","???"]], + "PSHUFHW", + "PSHUFLW" + ], + [ + "???", + [ + "???","???", + [["PSRLW","","",""],"PSRLW","",""],"???", + [["PSRAW","","",""],"PSRAW","",""],"???", + [["PSLLW","","",""],"PSLLW","",""],"???" + ] + ], + [ + ["???",["","",["PRORD","","PRORQ"],""],"???","???"], + ["???",["","",["PROLD","","PROLQ"],""],"???","???"], + [["PSRLD","","",""],["PSRLD","PSRLD",["PSRLD","","???"],["PSRLD","","???"]],"",""], + "???", + [["PSRAD","","",""],["PSRAD","PSRAD",["PSRAD","","PSRAQ"],["PSRAD","","???"]],"",""], + "???", + [["PSLLD","","",""],["PSLLD","PSLLD",["PSLLD","","???"],["PSLLD","","???"]],"",""], + "???" + ], + [ + "???", + [ + "???","???", + [["PSRLQ","PSRLQ","",""],"PSRLQ","",""],["???","PSRLDQ","???","???"], + "???","???", + [["PSLLQ","PSLLQ","",""],"PSLLQ","",""],["???","PSLLDQ","???","???"] + ] + ], + [["PCMPEQB","","",""],["PCMPEQB","PCMPEQB","PCMPEQB",""],"",""], + [["PCMPEQW","","",""],["PCMPEQW","PCMPEQW","PCMPEQW",""],"",""], + [["PCMPEQD","","",""],["PCMPEQD","PCMPEQD",["PCMPEQD","","???"],["PCMPEQD","","???"]],"",""], + [["EMMS",["ZEROUPPER","ZEROALL",""],"",""],"???","???","???"], + [ + ["VMREAD","",["CVTTPS2UDQ","","CVTTPD2UDQ"],""], + ["EXTRQ","",["CVTTPS2UQQ","","CVTTPD2UQQ"],""], + ["???","","CVTTSS2USI",""], + ["INSERTQ","","CVTTSD2USI",""] + ], + [ + ["VMWRITE","",["CVTPS2UDQ","","CVTPD2UDQ"], ""], + ["EXTRQ","",["CVTPS2UQQ","","CVTPD2UQQ"],""], + ["???","","CVTSS2USI",""], + ["INSERTQ","","CVTSD2USI",""] + ], + [ + "???", + ["","",["CVTTPS2QQ","","CVTTPD2QQ"],""], + ["","",["CVTUDQ2PD","","CVTUQQ2PD"],"CVTUDQ2PD"], + ["","",["CVTUDQ2PS","","CVTUQQ2PS"],""] + ], + [ + "???", + ["","",["CVTPS2QQ","","CVTPD2QQ"],""], + ["","","CVTUSI2SS",""], + ["","","CVTUSI2SD",""] + ], + [ + "???",["HADDPD","HADDPD","",""], + "???",["HADDPS","HADDPS","",""] + ], + [ + "???",["HSUBPD","HSUBPD","",""], + "???",["HSUBPS","HSUBPS","",""] + ], + [["MOVD","","",""],["MOVD","","MOVQ"],["MOVQ","MOVQ",["???","","MOVQ"],""],"???"], + [ + ["MOVQ","","",""], + ["MOVDQA","MOVDQA",["MOVDQA32","","MOVDQA64"],["MOVDQA32","","MOVDQA64"]], + ["MOVDQU","MOVDQU",["MOVDQU32","","MOVDQU64"],""], + ["???","",["MOVDQU8","","MOVDQU16"],""] + ], + "JO","JNO","JB","JAE", + [["JE","JKZD","",""],"","",""],[["JNE","JKNZD","",""],"","",""], //K1OM. + "JBE","JA","JS","JNS","JP","JNP","JL","JGE","JLE","JG", + [ + ["SETO",["KMOVW","","KMOVQ"],"",""], + ["SETO",["KMOVB","","KMOVD"],"",""],"","" + ], + [ + ["SETNO",["KMOVW","","KMOVQ"],"",""], + ["SETNO",["KMOVB","","KMOVD"],"",""],"","" + ], + [ + ["SETB",["KMOVW","","???"],"",""], + ["SETB",["KMOVB","","???"],"",""],"", + ["SETB",["KMOVD","","KMOVQ"],"",""] + ], + [ + ["SETAE",["KMOVW","","???"],"",""], + ["SETAE",["KMOVB","","???"],"",""],"", + ["SETAE",["KMOVD","","KMOVQ"],"",""] + ], + "SETE",[["SETNE","KCONCATH","",""],"","",""], + "SETBE",[["SETA","KCONCATL","",""],"","",""], + [ + ["SETS",["KORTESTW","","KORTESTQ"],"",""], + ["SETS",["KORTESTB","","KORTESTD"],"",""],"","" + ], + [ + ["SETNS",["KTESTW","","KTESTQ"],"",""], + ["SETNS",["KTESTB","","KTESTD"],"",""],"","" + ], + "SETP","SETNP","SETL","SETGE","SETLE","SETG", + "PUSH","POP", + "CPUID", //Identifies the CPU and which Instructions the current CPU can use. + "BT", + "SHLD","SHLD", + "XBTS","IBTS", + "PUSH","POP", + "RSM", + "BTS", + "SHRD","SHRD", + [ + [ + ["FXSAVE","???","FXSAVE64"],["FXRSTOR","???","FXRSTOR64"], + "LDMXCSR","STMXCSR", + ["XSAVE","","XSAVE64"],["XRSTOR","","XRSTOR64"], + ["XSAVEOPT","CLWB","XSAVEOPT64"], + ["CLFLUSHOPT","CLFLUSH",""] + ], + [ + ["???","???",["RDFSBASE","","",""],"???"],["???","???",["RDGSBASE","","",""],"???"], + ["???","???",["WRFSBASE","","",""],"???"],["???","???",["WRGSBASE","","",""],"???"], + "???", + ["LFENCE","???","???","???","???","???","???","???"], + ["MFENCE","???","???","???","???","???","???","???"], + ["SFENCE","???","???","???","???","???","???","???"] + ] + ], + "IMUL", + "CMPXCHG","CMPXCHG", + ["LSS","???"], + "BTR", + ["LFS","???"], + ["LGS","???"], + "MOVZX","MOVZX", + [ + ["JMPE","","",""],"???", + ["POPCNT","POPCNT","",""],"???" + ], + "???", + ["???","???","???","???","BT","BTS","BTR","BTC"], + "BTC", + [ + ["BSF","","",""],"???", + ["TZCNT","TZCNT","",""],["BSF","TZCNTI","",""] + ], + [ + ["BSR","","",""],"???", + ["LZCNT","LZCNT","",""],["BSR","","",""] + ], + "MOVSX","MOVSX", + "XADD","XADD", + [ + ["CMP,PS,","CMP,PS,","CMP,PS,","CMP,PS,"], + ["CMP,PD,","CMP,PD,","CMP,PD,","CMP,PD,"], + ["CMP,SS,","CMP,SS,","CMP,SS,",""], + ["CMP,SD,","CMP,SD,","CMP,SD,",""] + ], + ["MOVNTI","???"], + [["PINSRW","","",""],"PINSRW","",""], + ["???",[["PEXTRW","","",""],"PEXTRW","",""]], + ["SHUFPS","SHUFPD","???","???"], + [ + [ + "???", + ["CMPXCHG8B","","CMPXCHG16B"], + "???", + ["XRSTORS","","XRSTORS64"], + ["XSAVEC","","XSAVEC64"], + ["XSAVES","","XSAVES64"], + ["VMPTRLD","VMCLEAR","VMXON","???"],["VMPTRST","???","???","???"] + ], + [ + "???", + ["SSS","???","???","???","???","???","???","???"], //Synthetic virtual machine operation codes. + "???","???","???","???", + "RDRAND","RDSEED" + ] + ], + "BSWAP","BSWAP","BSWAP","BSWAP","BSWAP","BSWAP","BSWAP","BSWAP", + ["???",["ADDSUBPD","ADDSUBPD","",""],"???",["ADDSUBPS","ADDSUBPS","",""]], + [["PSRLW","","",""],"PSRLW","",""], + [["PSRLD","","",""],["PSRLD","PSRLD",["PSRLD","","???"],""],"",""], + [["PSRLQ","","",""],"PSRLQ","",""], + [["PADDQ","","",""],"PADDQ","",""], + [["PMULLW","","",""],"PMULLW","",""], + [ + ["???","MOVQ","???","???"], + ["???","MOVQ",["MOVQ2DQ","","",""],["MOVDQ2Q","","",""]] + ], + ["???",[["PMOVMSKB","","",""],["PMOVMSKB","PMOVMSKB","",""],"???","???"]], + [["PSUBUSB","","",""],"PSUBUSB","",""], + [["PSUBUSW","","",""],"PSUBUSW","",""], + [["PMINUB","","",""],"PMINUB","",""], + [["PAND","","",""],["PAND","PAND",["PANDD","","PANDQ"],["PANDD","","PANDQ"]],"",""], + [["PADDUSB","","",""],"PADDUSB","",""], + [["PADDUSW","","",""],"PADDUSW","",""], + [["PMAXUB","","",""],"PMAXUB","",""], + [["PANDN","","",""],["PANDN","PANDN",["PANDND","","PANDNQ"],["PANDND","","PANDNQ"]],"",""], + [["PAVGB","","",""],"PAVGB","",""], + [ + [["PSRAW","","",""],["PSRAW","PSRAW","PSRAW",""],"",""], + [["PSRAW","","",""],["PSRAW","PSRAW","PSRAW",""],"",""] + ], + [["PSRAD","","",""],["PSRAD","PSRAD",["PSRAD","","PSRAQ"],""],"",""], + [["PAVGW","","",""],"PAVGW","",""], + [["PMULHUW","","",""],"PMULHUW","",""], + [["PMULHW","","",""],"PMULHW","",""], + [ + "???", + ["CVTTPD2DQ","CVTTPD2DQ","CVTTPD2DQ",""], + ["CVTDQ2PD","CVTDQ2PD",["CVTDQ2PD","CVTDQ2PD","CVTQQ2PD"],"CVTDQ2PD"], + "CVTPD2DQ" + ], + [[["MOVNTQ","","",""],["MOVNTDQ","","???"],"???","???"],"???"], + [["PSUBSB","","",""],"PSUBSB","",""], + [["PSUBSW","","",""],"PSUBSW","",""], + [["PMINSW","","",""],"PMINSW","",""], + [["POR","","",""],["POR","POR",["PORD","","PORQ"],["PORD","","PORQ"]],"",""], + [["PADDSB","","",""],"PADDSB","",""], + [["PADDSW","","",""],"PADDSW","",""], + [["PMAXSW","","",""],"PMAXSW","",""], + [["PXOR","","",""],["PXOR","PXOR",["PXORD","","PXORQ"],["PXORD","","PXORQ"]],"",""], + [["???","???","???",["LDDQU","LDDQU","",""]],"???"], + [["PSLLW","","",""],"PSLLW","",""], + [["PSLLD","","",""],["PSLLD","","???"],"",""], + [["PSLLQ","","",""],"PSLLQ","",""], + [["PMULUDQ","","",""],"PMULUDQ","",""], + [["PMADDWD","","",""],"PMADDWD","",""], + [["PSADBW","","",""],"PSADBW","",""], + ["???",[["MASKMOVQ","","",""],["MASKMOVDQU","MASKMOVDQU","",""],"???","???"]], + [["PSUBB","","",""],"PSUBB","",""], + [["PSUBW","","",""],"PSUBW","",""], + [["PSUBD","","",""],["PSUBD","PSUBD",["PSUBD","","???"],["PSUBD","","???"]],"",""], + [["PSUBQ","","",""],"PSUBQ","",""], + [["PADDB","","",""],"PADDB","",""], + [["PADDW","","",""],"PADDW","",""], + [["PADDD","","",""],["PADDD","PADDD",["PADDD","","???"],["PADDD","","???"]],"",""], + "???", + /*------------------------------------------------------------------------------------------------------------------------ + Three Byte operations 0F38. Opcodes plus 512 goes to 767 used by escape codes "0F,38", Or + set directly by adding map bits "10" because "10 00000000" bin = 512 plus opcode. + ------------------------------------------------------------------------------------------------------------------------*/ + [["PSHUFB","","",""],"PSHUFB","???","???"], + [["PHADDW","","",""],["PHADDW","PHADDW","",""],"???","???"], + [["PHADDD","","",""],["PHADDD","PHADDD","",""],"???","???"], + [["PHADDSW","","",""],["PHADDSW","PHADDSW","",""],"???","???"], + [["PMADDUBSW","","",""],"PMADDUBSW","???","???"], + [["PHSUBW","","",""],["PHSUBW","PHSUBW","",""],"???","???"], + [["PHSUBD","","",""],["PHSUBD","PHSUBD","",""],"???","???"], + [["PHSUBSW","","",""],["PHSUBSW","PHSUBSW","",""],"???","???"], + [["PSIGNB","","",""],["PSIGNB","PSIGNB","",""],"???","???"], + [["PSIGNW","","",""],["PSIGNW","PSIGNW","",""],"???","???"], + [["PSIGND","","",""],["PSIGND","PSIGND","",""],"???","???"], + [["PMULHRSW","","",""],"PMULHRSW","???","???"], + ["???",["","PERMILPS",["PERMILPS","","???"],""],"???","???"], + ["???",["","PERMILPD","PERMILPD",""],"???","???"], + ["???",["","TESTPS","",""],"???","???"], + ["???",["","TESTPD","",""],"???","???"], + ["???",["PBLENDVB","PBLENDVB","PSRLVW",""],["","","PMOVUSWB",""],"???"], + ["???",["","","PSRAVW",""],["","","PMOVUSDB",""],"???"], + ["???",["","","PSLLVW",""],["","","PMOVUSQB",""],"???"], + ["???",["","CVTPH2PS",["CVTPH2PS","","???"],""],["","","PMOVUSDW",""],"???"], + ["???",["BLENDVPS","BLENDVPS",["PRORVD","","PRORVQ"],""],["","","PMOVUSQW",""],"???"], + ["???",["BLENDVPD","BLENDVPD",["PROLVD","","PROLVQ"],""],["","","PMOVUSQD",""],"???"], + ["???",["","PERMPS",["PERMPS","","PERMPD"],""],"???","???"], + ["???",["PTEST","PTEST","",""],"???","???"], + ["???",["","BROADCASTSS",["BROADCASTSS","","???"],["BROADCASTSS","","???"]],"???","???"], + ["???",["","BROADCASTSD",["BROADCASTF32X2","","BROADCASTSD"],["???","","BROADCASTSD"]],"???","???"], + ["???",["","BROADCASTF128",["BROADCASTF32X4","","BROADCASTF64X2"],["BROADCASTF32X4","","???"]],"???","???"], + ["???",["","",["BROADCASTF32X8","","BROADCASTF64X4"],["???","","BROADCASTF64X4"]],"???","???"], + [["PABSB","","",""],"PABSB","???","???"], + [["PABSW","","",""],"PABSW","???","???"], + [["PABSD","","",""],["PABSD","","???"],"???","???"], + ["???",["","","PABSQ",""],"???","???"], + ["???","PMOVSXBW",["","","PMOVSWB",""],"???"], + ["???","PMOVSXBD",["","","PMOVSDB",""],"???"], + ["???","PMOVSXBQ",["","","PMOVSQB",""],"???"], + ["???","PMOVSXWD",["","","PMOVSDW",""],"???"], + ["???","PMOVSXWQ",["","","PMOVSQW",""],"???"], + ["???","PMOVSXDQ",["","","PMOVSQD",""],"???"], + ["???",["","",["PTESTMB","","PTESTMW"],""],["","",["PTESTNMB","","PTESTNMW"],""],"???"], + ["???",["","",["PTESTMD","","PTESTMQ"],["PTESTMD","","???"]],["","",["PTESTNMD","","PTESTNMQ"],""],"???"], + ["???","PMULDQ",["","",["PMOVM2B","","PMOVM2W"],""],"???"], + ["???",["PCMPEQQ","PCMPEQQ","PCMPEQQ",""],["","",["PMOVB2M","","PMOVW2M"],""],"???"], + [["???",["MOVNTDQA","","???"],"???","???"],["???","???",["","",["???","","PBROADCASTMB2Q"],""],"???"]], + ["???",["PACKUSDW","","???"],"???","???"], + ["???",["","MASKMOVPS",["SCALEFPS","","SCALEFPD"],""],"???","???"], + ["???",["","MASKMOVPD",["SCALEFSS","","SCALEFSD"],""],"???","???"], + ["???",["","MASKMOVPS","",""],"???","???"], + ["???",["","MASKMOVPD","",""],"???","???"], + ["???","PMOVZXBW",["","","PMOVWB",""],"???"], + ["???","PMOVZXBD",["","","PMOVDB",""],"???"], + ["???","PMOVZXBQ",["","","PMOVQB",""],"???"], + ["???","PMOVZXWD",["","","PMOVDW",""],"???"], + ["???","PMOVZXWQ",["","","PMOVQW",""],"???"], + ["???","PMOVZXDQ",["","",["PMOVQD","PMOVQD",""],""],"???"], + ["???",["","PERMD",["PERMD","","PERMQ"],["PERMD","","???"]],"???","???"], + ["???",["PCMPGTQ","PCMPGTQ","PCMPGTQ",""],"???","???"], + ["???","PMINSB",["","",["PMOVM2D","","PMOVM2Q"],""],"???"], + ["???",["PMINSD","PMINSD",["PMINSD","","PMINSQ"],["PMINSD","","???"]],["","",["PMOVD2M","","PMOVQ2M"],""],"???"], + ["???","PMINUW",["","","PBROADCASTMW2D",""],"???"], + ["???",["PMINUD","PMINUD",["PMINUD","","PMINUQ"],["PMINUD","","???"]],"???","???"], + ["???","PMAXSB","???","???"], + ["???",["PMAXSD","PMAXSD",["PMAXSD","","PMAXSQ"],["PMAXSD","","???"]],"???","???"], + ["???","PMAXUW","???","???"], + ["???",["PMAXUD","PMAXUD",["PMAXUD","","PMAXUQ"],["PMAXUD","","???"]],"???","???"], + ["???",["PMULLD","PMULLD",["PMULLD","","PMULLQ"],["PMULLD","",""]],"???","???"], + ["???",["PHMINPOSUW",["PHMINPOSUW","PHMINPOSUW",""],"",""],"???","???"], + ["???",["","",["GETEXPPS","","GETEXPPD"],["GETEXPPS","","GETEXPPD"]],"???","???"], + ["???",["","",["GETEXPSS","","GETEXPSD"],""],"???","???"], + ["???",["","",["PLZCNTD","","PLZCNTQ"],""],"???","???"], + ["???",["",["PSRLVD","","PSRLVQ"],["PSRLVD","","PSRLVQ"],["PSRLVD","","???"]],"???","???"], + ["???",["",["PSRAVD","",""],["PSRAVD","","PSRAVQ"],["PSRAVD","","???"]],"???","???"], + ["???",["",["PSLLVD","","PSLLVQ"],["PSLLVD","","PSLLVQ"],["PSLLVD","","???"]],"???","???"], + "???","???","???","???", + ["???",["","",["RCP14PS","","RCP14PD"],""],"???","???"], + ["???",["","",["RCP14SS","","RCP14SD"],""],"???","???"], + ["???",["","",["RSQRT14PS","","RSQRT14PD"],""],"???","???"], + ["???",["","",["RSQRT14SS","","RSQRT14SD"],""],"???","???"], + ["???",["","","",["ADDNPS","","ADDNPD"]],"???","???"], + ["???",["","","",["GMAXABSPS","","???"]],"???","???"], + ["???",["","","",["GMINPS","","GMINPD"]],"???","???"], + ["???",["","","",["GMAXPS","","GMAXPD"]],"???","???"], + "", + ["???",["","","",["FIXUPNANPS","","FIXUPNANPD"]],"???","???"], + "","", + ["???",["","PBROADCASTD",["PBROADCASTD","","???"],["PBROADCASTD","","???"]],"???","???"], + ["???",["","PBROADCASTQ",["BROADCASTI32X2","","PBROADCASTQ"],["???","","PBROADCASTQ"]],"???","???"], + ["???",["","BROADCASTI128",["BROADCASTI32X4","","BROADCASTI64X2"],["BROADCASTI32X4","","???"]],"???","???"], + ["???",["","",["BROADCASTI32X8","","BROADCASTI64X4"],["???","","BROADCASTI64X4"]],"???","???"], + ["???",["","","",["PADCD","","???"]],"???","???"], + ["???",["","","",["PADDSETCD","","???"]],"???","???"], + ["???",["","","",["PSBBD","","???"]],"???","???"], + ["???",["","","",["PSUBSETBD","","???"]],"???","???"], + "???","???","???","???", + ["???",["","",["PBLENDMD","","PBLENDMQ"],["PBLENDMD","","PBLENDMQ"]],"???","???"], + ["???",["","",["BLENDMPS","","BLENDMPD"],["BLENDMPS","","BLENDMPD"]],"???","???"], + ["???",["","",["PBLENDMB","","PBLENDMW"],""],"???","???"], + "???","???","???","???","???", + ["???",["","","",["PSUBRD","","???"]],"???","???"], + ["???",["","","",["SUBRPS","","SUBRPD"]],"???","???"], + ["???",["","","",["PSBBRD","","???"]],"???","???"], + ["???",["","","",["PSUBRSETBD","","???"]],"???","???"], + "???","???","???","???", + ["???",["","","",["PCMPLTD","","???"]],"???","???"], + ["???",["","",["PERMI2B","","PERMI2W"],""],"???","???"], + ["???",["","",["PERMI2D","","PERMI2Q"],""],"???","???"], + ["???",["","",["PERMI2PS","","PERMI2PD"],""],"???","???"], + ["???",["","PBROADCASTB",["PBROADCASTB","","???"],""],"???","???"], + ["???",["","PBROADCASTW",["PBROADCASTW","","???"],""],"???","???"], + ["???",["???",["","",["PBROADCASTB","","???"],""],"???","???"]], + ["???",["???",["","",["PBROADCASTW","","???"],""],"???","???"]], + ["???",["","",["PBROADCASTD","","PBROADCASTQ"],""],"???","???"], + ["???",["","",["PERMT2B","","PERMT2W"],""],"???","???"], + ["???",["","",["PERMT2D","","PERMT2Q"],""],"???","???"], + ["???",["","",["PERMT2PS","","PERMT2PD"],""],"???","???"], + [["???","INVEPT","???","???"],"???"], + [["???","INVVPID","???","???"],"???"], + [["???","INVPCID","???","???"],"???"], + ["???",["???","???","PMULTISHIFTQB","???"],"???","???"], + ["???",["","","",["SCALEPS","","???"]],"???","???"], + "???", + ["???",["","","",["PMULHUD","","???"]],"???","???"], + ["???",["","","",["PMULHD","","???"]],"???","???"], + ["???",["","",["EXPANDPS","","EXPANDPD"],""],"???","???"], + ["???",["","",["PEXPANDD","","PEXPANDQ"],""],"???","???"], + ["???",["","",["COMPRESSPS","","COMPRESSPD"],""],"???","???"], + ["???",["","",["PCOMPRESSD","","PCOMPRESSQ"],""],"???","???"], + "???", + ["???",["","",["PERMB","","PERMW"],""],"???","???"], + "???","???", + ["???",["",["PGATHERDD","","PGATHERDQ"],["PGATHERDD","","PGATHERDQ"],["PGATHERDD","","PGATHERDQ"]],"???","???"], + ["???",["",["PGATHERQD","","PGATHERQQ"],["PGATHERQD","","PGATHERQQ"],""],"???","???"], + ["???",["",["GATHERDPS","","GATHERDPD"],["GATHERDPS","","GATHERDPD"],["GATHERDPS","","GATHERDPD"]],"???","???"], + ["???",["",["GATHERQPS","","GATHERQPD"],["GATHERQPS","","GATHERQPD"],""],"???","???"], + "???","???", + ["???",["",["FMADDSUB132PS","","FMADDSUB132PD"],["FMADDSUB132PS","","FMADDSUB132PD"],""],"???","???"], + ["???",["",["FMSUBADD132PS","","FMSUBADD132PD"],["FMSUBADD132PS","","FMSUBADD132PD"],""],"???","???"], + ["???",["",["FMADD132PS","","FMADD132PD"],["FMADD132PS","","FMADD132PD"],["FMADD132PS","","FMADD132PD"]],"???","???"], + ["???",["",["FMADD132SS","","FMADD132SD"],["FMADD132SS","","FMADD132SD"],""],"???","???"], + ["???",["",["FMSUB132PS","","FMSUB132PD"],["FMSUB132PS","","FMSUB132PD"],["FMSUB132PS","","FMSUB132PD"]],"???","???"], + ["???",["",["FMSUB132SS","","FMSUB132SD"],["FMSUB132SS","","FMSUB132SD"],""],"???","???"], + ["???",["",["FNMADD132PS","","FNMADD132PD"],["FNMADD132PS","","FNMADD132PD"],["NMADD132PS","","FNMADD132PD"]],"???","???"], + ["???",["",["FNMADD132SS","","FNMADD132SD"],["FNMADD132SS","","FNMADD132SD"],""],"???","???"], + ["???",["",["FNMSUB132PS","","FNMSUB132PD"],["FNMSUB132PS","","FNMSUB132PD"],["FNMSUB132PS","","FNMSUB132PS"]],"???","???"], + ["???",["",["FNMSUB132SS","","FNMSUB132SD"],["FNMSUB132SS","","FNMSUB132SD"],""],"???","???"], + ["???",["","",["PSCATTERDD","","PSCATTERDQ"],["PSCATTERDD","","PSCATTERDQ"]],"???","???"], + ["???",["","",["PSCATTERQD","","PSCATTERQQ"],""],"???","???"], + ["???",["","",["SCATTERDPS","","SCATTERDPD"],["SCATTERDPS","","SCATTERDPD"]],"???","???"], + ["???",["","",["SCATTERQPS","","SCATTERQPD"],""],"???","???"], + ["???",["","","",["FMADD233PS","","???"]],"???","???"], + "???", + ["???",["",["FMADDSUB213PS","","FMADDSUB213PD"],["FMADDSUB213PS","","FMADDSUB213PD"],""],"???","???"], + ["???",["",["FMSUBADD213PS","","FMSUBADD213PD"],["FMSUBADD213PS","","FMSUBADD213PD"],""],"???","???"], + ["???",["",["FMADD213PS","","FMADD213PD"],["FMADD213PS","","FMADD213PD"],["FMADD213PS","","FMADD213PD"]],"???","???"], + ["???",["",["FMADD213SS","","FMADD213SD"],["FMADD213SS","","FMADD213SD"],""],"???","???"], + ["???",["",["FMSUB213PS","","FMSUB213PD"],["FMSUB213PS","","FMSUB213PD"],["FMSUB213PS","","FMSUB213PD"]],"???","???"], + ["???",["",["FMSUB213SS","","FMSUB213SD"],["FMSUB213SS","","FMSUB213SD"],""],"???","???"], + ["???",["",["FNMADD213PS","","FNMADD213PD"],["FNMADD213PS","","FNMADD213PD"],["FNMADD213PS","","FNMADD213PD"]],"???","???"], + ["???",["",["FNMADD213SS","","FNMADD213SD"],["FNMADD213SS","","FNMADD213SD"],""],"???","???"], + ["???",["",["FNMSUB213PS","","FNMSUB213PD"],["FNMSUB213PS","","FNMSUB213PD"],["FNMSUB213PS","","FNMSUB213PD"]],"???","???"], + ["???",["",["FNMSUB213SS","","FNMSUB213SD"],["FNMSUB213SS","","FNMSUB213SD"],""],"???","???"], + "???","???","???","???", + ["???",["","","PMADD52LUQ",["PMADD233D","","???"]],"???","???"], + ["???",["","","PMADD52HUQ",["PMADD231D","","???"]],"???","???"], + ["???",["",["FMADDSUB231PS","","FMADDSUB231PD"],["FMADDSUB231PS","","FMADDSUB231PD"],""],"???","???"], + ["???",["",["FMSUBADD231PS","","FMSUBADD231PD"],["FMSUBADD231PS","","FMSUBADD231PD"],""],"???","???"], + ["???",["",["FMADD231PS","","FMADD231PD"],["FMADD231PS","","FMADD231PD"],["FMADD231PS","","FMADD231PD"]],"???","???"], + ["???",["",["FMADD231SS","","FMADD231SD"],["FMADD231SS","","FMADD231SD"],""],"???","???"], + ["???",["",["FMSUB231PS","","FMSUB231PD"],["FMSUB231PS","","FMSUB231PD"],["FMSUB231PS","","FMSUB231PD"]],"???","???"], + ["???",["",["FMSUB231SS","","FMSUB231SD"],["FMSUB231SS","","FMSUB231SD"],""],"???","???"], + ["???",["",["FNMADD231PS","","FNMADD231PD"],["FNMADD231PS","","FNMADD231PD"],["FNMADD231PS","","FNMADD231PD"]],"???","???"], + ["???",["",["FNMADD231SS","","FNMADD231SD"],["FNMADD231SS","","FNMADD231SD"],""],"???","???"], + ["???",["",["FNMSUB231PS","","FNMSUB231PD"],["FNMSUB231PS","","FNMSUB231PD"],["FNMSUB231PS","","FNMSUB231PD"]],"???","???"], + ["???",["",["FNMSUB231SS","","FNMSUB231SD"],["FNMSUB231SS","","FNMSUB231SD"],""],"???","???"], + "???","???","???","???", + ["???",["","",["PCONFLICTD","","PCONFLICTQ"],""],"???","???"], + "???", + [ + [ + ["???",["","","",["GATHERPF0HINTDPS","","GATHERPF0HINTDPD"]],"???","???"], + ["???",["","",["GATHERPF0DPS","","GATHERPF0DPD"],["GATHERPF0DPS","",""]],"???","???"], + ["???",["","",["GATHERPF1DPS","","GATHERPF1DPD"],["GATHERPF1DPS","",""]],"???","???"], + "???", + ["???",["","","",["SCATTERPF0HINTDPS","","SCATTERPF0HINTDPD"]],"???","???"], + ["???",["","",["SCATTERPF0DPS","","SCATTERPF0DPD"],["VSCATTERPF0DPS","",""]],"???","???"], + ["???",["","",["SCATTERPF1DPS","","SCATTERPF1DPD"],["VSCATTERPF1DPS","",""]],"???","???"], + "???" + ],"???" + ], + [ + [ + "???", + ["???",["","",["GATHERPF0QPS","","GATHERPF0QPD"],""],"???","???"], + ["???",["","",["GATHERPF1QPS","","GATHERPF1QPD"],""],"???","???"], + "???","???", + ["???",["","",["SCATTERPF0QPS","","SCATTERPF0QPD"],""],"???","???"], + ["???",["","",["SCATTERPF1QPS","","SCATTERPF1QPD"],""],"???","???"], + "???" + ],"???" + ], + [["SHA1NEXTE","","",""],["","",["EXP2PS","","EXP2PD"],["EXP223PS","","???"]],"???","???"], + [["SHA1MSG1","","",""],["","","",["LOG2PS","","???"]],"???","???"], + [["SHA1MSG2","","",""],["","",["RCP28PS","","RCP28PD"],["RCP23PS","","???"]],"???","???"], + [["SHA256RNDS2","","",""],["","",["RCP28SS","","RCP28SD"],["RSQRT23PS","","???"]],"???","???"], + [["SHA256MSG1","","",""],["","",["RSQRT28PS","","RSQRT28PD"],["ADDSETSPS","","???"]],"???","???"], + [["SHA256MSG2","","",""],["","",["RSQRT28SS","","RSQRT28SD"],["PADDSETSD","","???"]],"???","???"], + "???","???", + [[["","","",["LOADUNPACKLD","","LOADUNPACKLQ"]],["","","",["PACKSTORELD","","PACKSTORELQ"]],"???","???"],"???"], + [[["","","",["LOADUNPACKLPS","","LOADUNPACKLPD"]],["","","",["PACKSTORELPS","","PACKSTORELPD"]],"???","???"],"???"], + "???","???", + [[["","","",["LOADUNPACKHD","","LOADUNPACKHQ"]],["","","",["PACKSTOREHD","","PACKSTOREHQ"]],"???","???"],"???"], + [[["","","",["LOADUNPACKHPS","","LOADUNPACKHPD"]],["","","",["PACKSTOREHPS","","PACKSTOREHPD"]],"???","???"],"???"], + "???","???","???","???","???", + ["???",["AESIMC","AESIMC","",""],"???","???"], + ["???",["AESENC","AESENC","",""],"???","???"], + ["???",["AESENCLAST","AESENCLAST","",""],"???","???"], + ["???",["AESDEC","AESDEC","",""],"???","???"], + ["???",["AESDECLAST","AESDECLAST","",""],"???","???"], + "???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???", + [ + ["MOVBE","","",""], + ["MOVBE","","",""],"???", + ["CRC32","","",""] + ], + [ + ["MOVBE","","",""], + ["MOVBE","","",""],"???", + ["CRC32","","",""] + ], + ["???",["","ANDN","",""],"???","???"], + [ + "???", + ["???",["","BLSR","",""],"???","???"], + ["???",["","BLSMSK","",""],"???","???"], + ["???",["","BLSI","",""],"???","???"], + "???","???","???","???" + ],"???", + [ + ["","BZHI","",""],"???", + ["","PEXT","",""], + ["","PDEP","",""] + ], + [ + "???", + ["ADCX","","",""], + ["ADOX","","",""], + ["","MULX","",""] + ], + [ + ["","BEXTR","",""], + ["","SHLX","",""], + ["","SARX","",""], + ["","SHRX","",""] + ], + "???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------ + Three Byte operations 0F38. Opcodes plus 768 goes to 767 used by escape codes "0F, 3A", Or + set directly by adding map bits "11" because "11 00000000" bin = 768 plus opcode. + ------------------------------------------------------------------------------------------------------------------------*/ + ["???",["","PERMQ","PERMQ",""],"???","???"], + ["???",["","PERMPD","PERMPD",""],"???","???"], + ["???",["",["PBLENDD","",""],"",""],"???","???"], + ["???",["","",["ALIGND","","ALIGNQ"],["ALIGND","","???"]],"???","???"], + ["???",["","PERMILPS",["PERMILPS","","???"],""],"???","???"], + ["???",["","PERMILPD","PERMILPD",""],"???","???"], + ["???",["","PERM2F128","",""],"???","???"], + ["???",["","","",["PERMF32X4","","???"]],"???","???"], + ["???",["ROUNDPS","ROUNDPS",["RNDSCALEPS","","???"],""],"???","???"], + ["???",["ROUNDPD","ROUNDPD","RNDSCALEPD",""],"???","???"], + ["???",["ROUNDSS","ROUNDSS",["RNDSCALESS","","???"],""],"???","???"], + ["???",["ROUNDSD","ROUNDSD","RNDSCALESD",""],"???","???"], + ["???",["BLENDPS","BLENDPS","",""],"???","???"], + ["???",["BLENDPD","BLENDPD","",""],"???","???"], + ["???",["PBLENDW","PBLENDW","",""],"???","???"], + [["PALIGNR","","",""],"PALIGNR","???","???"], + "???","???","???","???", + [["???","PEXTRB","???","???"],["???","PEXTRB","???","???"]], + [["???","PEXTRW","???","???"],["???","PEXTRW","???","???"]], + ["???",["PEXTRD","","PEXTRQ"],"???","???"], + ["???","EXTRACTPS","???","???"], + ["???",["","INSERTF128",["INSERTF32X4","","INSERTF64X2"],""],"???","???"], + ["???",["","EXTRACTF128",["EXTRACTF32X4","","EXTRACTF64X2"],""],"???","???"], + ["???",["","",["INSERTF32X8","","INSERTF64X4"],""],"???","???"], + ["???",["","",["EXTRACTF32X8","","EXTRACTF64X4"],""],"???","???"], + "???", + ["???",["","CVTPS2PH",["CVTPS2PH","","???"],""],"???","???"], + ["???",["","",["PCMP,UD,","","PCMP,UQ,"],["PCMP,UD,","","???"]],"???","???"], + ["???",["","",["PCM,PD,","","PCM,PQ,"],["PCM,PD,","","???"]],"???","???"], + ["???","PINSRB","???","???"], + ["???",["INSERTPS","","???"],"???","???"], + ["???",["",["PINSRD","","PINSRQ"],["PINSRD","","PINSRQ"],""],"???","???"], + ["???",["","",["SHUFF32X4","","SHUFF64X2"],""],"???","???"], + "???", + ["???",["","",["PTERNLOGD","","PTERNLOGQ"],""],"???","???"], + ["???",["","",["GETMANTPS","","GETMANTPD"],["GETMANTPS","","GETMANTPD"]],"???","???"], + ["???",["","",["GETMANTSS","","GETMANTSD"],""],"???","???"], + "???","???","???","???","???","???","???","???", + ["???",["",["KSHIFTRB","","KSHIFTRW"],"",""],"???","???"], + ["???",["",["KSHIFTRD","","KSHIFTRQ"],"",""],"???","???"], + ["???",["",["KSHIFTLB","","KSHIFTLW"],"",""],"???","???"], + ["???",["",["KSHIFTLD","","KSHIFTLQ"],"",""],"???","???"], + "???","???","???","???", + ["???",["","INSERTI128",["INSERTI32X4","","INSERTI64X2"],""],"???","???"], + ["???",["","EXTRACTI128",["EXTRACTI32X4","","EXTRACTI64X2"],""],"???","???"], + ["???",["","",["INSERTI32X8","","INSERTI64X4"],""],"???","???"], + ["???",["","",["EXTRACTI32X8","","EXTRACTI64X4"],""],"???","???"], + "???","???", + ["???",["","KEXTRACT",["PCMP,UB,","","PCMP,UW,"],""],"???","???"], + ["???",["","",["PCM,PB,","","PCM,PW,"],""],"???","???"], + ["???",["DPPS","DPPS","",""],"???","???"], + ["???",["DPPD","DPPD","",""],"???","???"], + ["???",["MPSADBW","MPSADBW",["DBPSADBW","","???"],""],"???","???"], + ["???",["","",["SHUFI32X4","","SHUFI64X2"],""],"???","???"], + ["???",["PCLMULQDQ","PCLMULQDQ","",""],"???","???"], + "???", + ["???",["","PERM2I128","",""],"???","???"], + "???", + ["???",["",["PERMIL2PS","","PERMIL2PS"],"",""],"???","???"], + ["???",["",["PERMIL2PD","","PERMIL2PD"],"",""],"???","???"], + ["???",["","BLENDVPS","",""],"???","???"], + ["???",["","BLENDVPD","",""],"???","???"], + ["???",["","PBLENDVB","",""],"???","???"], + "???","???","???", + ["???",["","",["RANGEPS","","RANGEPD"],""],"???","???"], + ["???",["","",["RANGESS","","RANGESD"],""],"???","???"], + ["???",["","","",["RNDFXPNTPS","","RNDFXPNTPD"]],"???","???"], + "???", + ["???",["","",["FIXUPIMMPS","","FIXUPIMMPD"],""],"???","???"], + ["???",["","",["FIXUPIMMSS","","FIXUPIMMSD"],""],"???","???"], + ["???",["","",["REDUCEPS","","REDUCEPD"],""],"???","???"], + ["???",["","",["REDUCESS","","REDUCESD"],""],"???","???"], + "???","???","???","???", + ["???",["",["FMADDSUBPS","","FMADDSUBPS"],"",""],"???","???"], + ["???",["",["FMADDSUBPD","","FMADDSUBPD"],"",""],"???","???"], + ["???",["",["FMSUBADDPS","","FMSUBADDPS"],"",""],"???","???"], + ["???",["",["FMSUBADDPD","","FMSUBADDPD"],"",""],"???","???"], + ["???",["PCMPESTRM","PCMPESTRM","",""],"???","???"], + ["???",["PCMPESTRI","PCMPESTRI","",""],"???","???"], + ["???",["PCMPISTRM","PCMPISTRM","",""],"???","???"], + ["???",["PCMPISTRI","PCMPISTRI","",""],"???","???"], + "???","???", + ["???",["","",["FPCLASSPS","","FPCLASSPD"],""],"???","???"], + ["???",["","",["FPCLASSSS","","FPCLASSSD"],""],"???","???"], + ["???",["",["FMADDPS","","FMADDPS"],"",""],"???","???"], + ["???",["",["FMADDPD","","FMADDPD"],"",""],"???","???"], + ["???",["",["FMADDSS","","FMADDSS"],"",""],"???","???"], + ["???",["",["FMADDSD","","FMADDSD"],"",""],"???","???"], + ["???",["",["FMSUBPS","","FMSUBPS"],"",""],"???","???"], + ["???",["",["FMSUBPD","","FMSUBPD"],"",""],"???","???"], + ["???",["",["FMSUBSS","","FMSUBSS"],"",""],"???","???"], + ["???",["",["FMSUBSD","","FMSUBSD"],"",""],"???","???"], + "???","???","???","???","???","???","???","???", + ["???",["",["FNMADDPS","","FNMADDPS"],"",""],"???","???"], + ["???",["",["FNMADDPD","","FNMADDPD"],"",""],"???","???"], + ["???",["",["FNMADDSS","","FNMADDSS"],"",""],"???","???"], + ["???",["",["FNMADDSD","","FNMADDSD"],"",""],"???","???"], + ["???",["",["FNMSUBPS","","FNMSUBPS"],"",""],"???","???"], + ["???",["",["FNMSUBPD","","FNMSUBPD"],"",""],"???","???"], + ["???",["",["FNMSUBSS","","FNMSUBSS"],"",""],"???","???"], + ["???",["",["FNMSUBSD","","FNMSUBSD"],"",""],"???","???"], + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???", + [["","","","CVTFXPNTUDQ2PS"],["","","",["CVTFXPNTPS2UDQ","","???"]],"???",["","","","CVTFXPNTPD2UDQ"]], + [["","","","CVTFXPNTDQ2PS"],["","","",["CVTFXPNTPS2DQ","","???"]],"???","???"], + "SHA1RNDS4", + "???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + ["???",["AESKEYGENASSIST","AESKEYGENASSIST","",""],"???","???"], + "???","???","???","???","???","???", + ["???","???","???",["","","","CVTFXPNTPD2DQ"]], + "???","???","???","???","???","???","???","???","???", + ["???","???","???",["","RORX","",""]], + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP 8. + ------------------------------------------------------------------------------------------------------------------------*/ + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VPMACSSWW","VPMACSSWD","VPMACSSDQL","???","???","???","???","???","???", + "VPMACSSDD","VPMACSSDQH","???","???","???","???","???","VPMACSWW","VPMACSWD","VPMACSDQL", + "???","???","???","???","???","???","VPMACSDD","VPMACSDQH", + "???","???",["VPCMOV","","VPCMOV"],["VPPERM","","VPPERM"],"???","???","VPMADCSSWD", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VPMADCSWD","???","???","???","???","???","???","???","???","???", + "VPROTB","VPROTW","VPROTD","VPROTQ","???","???","???","???","???","???","???","???", + "VPCOM,B,","VPCOM,W,","VPCOM,D,","VPCOM,Q,","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VPCOM,UB,","VPCOM,UW,","VPCOM,UD,","VPCOM,UQ,", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP 9. + ------------------------------------------------------------------------------------------------------------------------*/ + "???", + ["???","BLCFILL","BLSFILL","BLCS","TZMSK","BLCIC","BLSIC","T1MSKC"],["???","BLCMSK","???","???","???","???","BLCI","???"], + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + ["???",["LLWPCB","SLWPCB","???","???","???","???","???","???"]], + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VFRCZPS","VFRCZPD","VFRCZSS","VFRCZSD","???","???","???","???","???","???","???","???","???","???","???","???", + ["VPROTB","","VPROTB"],["VPROTW","","VPROTW"],["VPROTD","","VPROTD"],["VPROTQ","","VPROTQ"], + ["VPSHLB","","VPSHLB"],["VPSHLW","","VPSHLW"],["VPSHLD","","VPSHLD"],["VPSHLQ","","VPSHLQ"], + ["VPSHAB","","VPSHAB"],["VPSHAW","","VPSHAW"],["VPSHAD","","VPSHAD"],["VPSHAQ","","VPSHAQ"], + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VPHADDBW","VPHADDBD","VPHADDBQ","???","???","VPHADDWD","VPHADDWQ","???","???","???","VPHADDDQ","???","???","???","???","???", + "VPHADDUBWD","VPHADDUBD","VPHADDUBQ","???","???","VPHADDUWD","VPHADDUWQ","???","???","???","VPHADDUDQ","???","???","???","???","???", + "VPHSUBBW","VPHSUBWD","VPHSUBDQ","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP A. + ------------------------------------------------------------------------------------------------------------------------*/ + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "BEXTR","???",["LWPINS","LWPVAL","???","???","???","???","???","???"], + "???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------- + L1OM Vector. + -------------------------------------------------------------------------------------------------------------------------*/ + "???","???","???","???","DELAY","???","???","???","???","???","???","???","???","???","???","???", + [["VLOADD","VLOADQ","",""],"???"],"???", + [["VLOADUNPACKLD","VLOADUNPACKLQ","",""],"???"], + [["VLOADUNPACKHD","VLOADUNPACKHQ","",""],"???"], + [["VSTORED","VSTOREQ","",""],"???"],"???", + [["VPACKSTORELD","VPACKSTORELQ","",""],"???"], + [["VPACKSTOREHD","VPACKSTOREHQ","",""],"???"], + ["VGATHERD","???"],["VGATHERPFD","???"],"???",["VGATHERPF2D","???"], + ["VSCATTERD","???"],["VSCATTERPFD","???"],"???",["VSCATTERPF2D","???"], + ["VCMP,PS,","VCMP,PD,","",""],"VCMP,PI,","VCMP,PU,","???", + ["VCMP,PS,","VCMP,PD,","",""],"VCMP,PI,","VCMP,PU,","???", + "???","???","???","???","???","???","???","???", + "VTESTPI","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + ["VADDPS","VADDPD","",""],"VADDPI","???","VADDSETCPI","???","VADCPI","VADDSETSPS","VADDSETSPI", + ["VADDNPS","VADDNPD","",""],"???","???","???","???","???","???","???", + ["VSUBPS","VSUBPD","",""],"VSUBPI","???","VSUBSETBPI","???","VSBBPI","???","???", + ["VSUBRPS","VSUBRPD","",""],"VSUBRPI","???","VSUBRSETBPI","???","VSBBRPI","???","???", + ["VMADD231PS","VMADD231PD","",""],"VMADD231PI", + ["VMADD213PS","VMADD213PD","",""],"???", + ["VMADD132PS","VMADD132PD","",""],"???", + "VMADD233PS","VMADD233PI", + ["VMSUB231PS","VMSUB231PD","",""],"???", + ["VMSUB213PS","VMSUB213PD","",""],"???", + ["VMSUB132PS","VMSUB132PD","",""],"???","???","???", + ["VMADDN231PS","VMADDN231PD","",""],"???", + ["VMADDN213PS","VMADDN213PD","",""],"???", + ["VMADDN132PS","VMADDN132PD","",""],"???","???","???", + ["VMSUBR231PS","VMSUBR231PD","",""],"???", + ["VMSUBR213PS","VMSUBR213PD","",""],"???", + ["VMSUBR132PS","VMSUBR132PD","",""],"???", + ["VMSUBR23C1PS","VMSUBR23C1PD","",""],"???", + ["VMULPS","VMULPD","",""],"VMULHPI","VMULHPU","VMULLPI","???","???","VCLAMPZPS","VCLAMPZPI", + ["VMAXPS","VMAXPD","",""],"VMAXPI","VMAXPU","???", + ["VMINPS","VMINPD","",""],"VMINPI","VMINPU","???", + ["???","VCVT,PD2PS,","",""],["VCVTPS2PI","VCVT,PD2PI,","",""],["VCVTPS2PU","VCVT,PD2PU,","",""],"???", + ["???","VCVT,PS2PD,","",""],["VCVTPI2PS","VCVT,PI2PD,","",""],["VCVTPU2PS","VCVT,PU2PD,","",""],"???", + "VROUNDPS","???","VCVTINSPS2U10","VCVTINSPS2F11","???","VCVTPS2SRGB8","VMAXABSPS","???", + "VSLLPI","VSRAPI","VSRLPI","???", + ["VANDNPI","VANDNPQ","",""],["VANDPI","VANDPQ","",""], + ["VORPI","VORPQ","",""],["VXORPI","VXORPQ","",""], + "VBINTINTERLEAVE11PI","VBINTINTERLEAVE21PI","???","???","???","???","???","???", + "VEXP2LUTPS","VLOG2LUTPS","VRSQRTLUTPS","???","VGETEXPPS","???","???","???", + "VSCALEPS","???","???","???","???","???","???","???", + "VRCPRESPS","???","VRCPREFINEPS","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "???","???","???","???","???","???","???","???","???","???","???","???","???","???","???","???", + "VFIXUPPS","VSHUF128X32","VINSERTFIELDPI","VROTATEFIELDPI","???","???","???","???", + "???","???","???","???","???","???","???","???", + /*------------------------------------------------------------------------------------------------------------------------- + L1OM Mask, Mem, and bit opcodes. + -------------------------------------------------------------------------------------------------------------------------*/ + ["???","BSFI"],["???","BSFI"],["???","BSFI"],["???","BSFI"], + ["???","BSRI"],["???","BSRI"],["???","BSRI"],["???","BSRI"], + ["???","BSFF"],["???","BSFF"],["???","BSFF"],["???","BSFF"], + ["???","BSRF"],["???","BSRF"],["???","BSRF"],["???","BSRF"], + ["???","BITINTERLEAVE11"],["???","BITINTERLEAVE11"],["???","BITINTERLEAVE11"],["???","BITINTERLEAVE11"], + ["???","BITINTERLEAVE21"],["???","BITINTERLEAVE21"],["???","BITINTERLEAVE21"],["???","BITINTERLEAVE21"], + ["???","INSERTFIELD"],["???","INSERTFIELD"],["???","INSERTFIELD"],["???","INSERTFIELD"], + ["???","ROTATEFIELD"],["???","ROTATEFIELD"],["???","ROTATEFIELD"],["???","ROTATEFIELD"], + ["???","COUNTBITS"],["???","COUNTBITS"],["???","COUNTBITS"],["???","COUNTBITS"], + ["???","QUADMASK16"],["???","QUADMASK16"],["???","QUADMASK16"],["???","QUADMASK16"], + "???","???","???","???", + "VKMOVLHB", + [["CLEVICT1","CLEVICT2","LDVXCSR","STVXCSR","???","???","???","???"],"???"], + [["VPREFETCH1","VPREFETCH2","???","???","???","???","???","???"],"???"], + [["VPREFETCH1","VPREFETCH2","???","???","???","???","???","???"],"???"], + "VKMOV","VKMOV","VKMOV","VKMOV", + "VKNOT","VKANDNR","VKANDN","VKAND", + "VKXNOR","VKXOR","VKORTEST","VKOR", + "???","VKSWAPB", + ["???",["DELAY","SPFLT","???","???","???","???","???","???"]], + ["???",["DELAY","SPFLT","???","???","???","???","???","???"]] +]; + +/*------------------------------------------------------------------------------------------------------------------------- +The Operand type array each operation code can use different operands that must be decoded after the select Opcode. +Basically some instruction may use the ModR/M talked about above while some may use an Immediate, or Both. +An Immediate input uses the byte after the opcode as a number some instructions combine a number and an ModR/M address selection +By using two bytes for each encoding after the opcode. X86 uses very few operand types for input selections to instructions, but +there are many useful combinations. The order the operands are "displayed" is the order they are in the Operands string for the +operation code. +--------------------------------------------------------------------------------------------------------------------------- +The first 2 digits is the selected operand type, and for if the operand can change size. Again more opcodes where sacrificed +to make this an setting Opcode "66" goes 16 bit this is explained in detail in the SizeAttrSelect variable section that is +adjusted by the function ^DecodePrefixAdjustments()^. The Variable SizeAttrSelect effects all operand formats that are decoded by +different functions except for single size. Don't forget X86 uses very few operand types in which different prefix adjustments +are used to add extra functionality to each operand type. The next two numbers is the operands size settings. +If the operand number is set to the operand version that can not change size then the next two numbers act as a single size for +faster decoding. Single size is also used to select numbers that are higher than the max size to select special registers that +are used by some instructions like Debug Registers. +--------------------------------------------------------------------------------------------------------------------------- +Registers have 8, 16, 32, 64, 128, 256, 512 names. The selected ModR/M address location uses a pointer name that shows it's select +size then it's location in left, and right brackets like "QWORD PTR[Address]". The pointer name changes by sizes 8, 16, 64, 128, 256, 512. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^DecodeOpcode()^ after ^DecodePrefixAdjustments()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +const Operands = [ + //------------------------------------------------------------------------------------------------------------------------ + //First Byte operations. + //------------------------------------------------------------------------------------------------------------------------ + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A000003","070E0B0E0003","0A0006000003","0B0E070E0003","16000C000003","170E0DE60003","","", + "06000A00","070E0B0E","0A000600","0B0E070E","16000C00","170E0DE6","","", + "03060003","03060003","03060003","03060003","03060003","03060003","03060003","03060003", + "03060003","03060003","03060003","03060003","03060003","03060003","03060003","03060003", + "030A","030A","030A","030A","030A","030A","030A","030A", + "030A","030A","030A","030A","030A","030A","030A","030A", + ["","",""],["","",""], + ["0A020606","0A010604",""], + "0B0E0704", + "","","","", + "0DE6","0B0E070E0DE6", + "0DA1","0B0E070E0DE1", + "22001A01","230E1A01","1A012000","1A01210E", + "10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C", + "10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C","10000002000C", + ["06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C00"], + ["070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE60003","070E0DE6"], + ["06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C000003","06000C00"], + ["070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE10003","070E0DE1"], + "06000A00","070E0B0E", + "0A0006000003","0B0E070E0003", + "06000A000001","070E0B0E0001", + "0A0006000001","0B0E070E0001", + "06020A080001", + ["0B0E0601",""], + "0A0806020001", + ["070A","","","","","","",""], + [["","","",""],["","","",""],["","","",""],["","","",""]], + "170E030E0003","170E030E0003","170E030E0003","170E030E0003","170E030E0003","170E030E0003","170E030E0003", + ["","",""],["","",""], + "0D060C01", //CALL Ap (w:z). + "", + ["","",""],["","",""], + "","", + "160004000001","170E050E0001", + "040016000001","050E170E0001", + "22002000","230E210E", + "22002000","230E210E", + "16000C00","170E0DE6", + "22001600","230E170E","16002000","170E210E","16002200","170E230E", + "02000C000001","02000C000001","02000C000001","02000C000001","02000C000001","02000C000001","02000C000001","02000C000001", + "030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001","030E0D0E0001", + ["06000C00","06000C00","06000C00","06000C00","06000C00","06000C00","06000C00","06000C00"], + ["070E0C00","070E0C00","070E0C00","070E0C00","070E0C00","070E0C00","070E0C00","070E0C00"], + "0C010008","0008", + "0B060906","0B060906", + [ + "06000C000001","","","","","","", + ["0C00","0C00","0C00","0C00","0C00","0C00","0C00","0C00"] + ], + [ + "070E0D060001","","","","","","", + ["1002","1002","1002","1002","1002","1002","1002","1002"] + ], + "0C010C00","", + "0C01","","2C00", + "0C00","", + ["","",""], + ["06002A00","06002A00","06002A00","06002A00","06002A00","06002A00","06002A00","06002A00"], + ["070E2A00","070E2A00","070E2A00","070E2A00","070E2A00","070E2A00","070E2A00","070E2A00"], + ["06001800","06001800","06001800","06001800","06001800","06001800","06001800","06001800"], + ["070E1800","070E1800","070E1800","070E1800","070E1800","070E1800","070E1800","070E1800"], + "0C00","0C00","", + "1E00", + /*------------------------------------------------------------------------------------------------------------------------ + X87 FPU. + ------------------------------------------------------------------------------------------------------------------------*/ + [ + ["0604","0604","0604","0604","0604","0604","0604","0604"], + ["24080609","24080609","0609","0609","24080609","24080609","24080609","24080609"] + ], + [ + ["0604","","0604","0604","0601","0602","0601","0602"], + [ + "0609","0609", + ["","","","","","","",""], + "0609", + ["","","","","","","",""], + ["","","","","","","",""], + ["","","","","","","",""], + ["","","","","","","",""] + ] + ], + [ + ["0604","0604","0604","0604","0604","0604","0604","0604"], + [ + "24080609","24080609","24080609","24080609","", + ["","","","","","","",""],"","" + ] + ], + [ + ["0604","0604","0604","0604","","0607","","0607",""], + [ + "24080609","24080609","24080609","24080609", + ["","","","","","","",""], + "24080609","24080609","" + ] + ], + [ + ["0606","0606","0606","0606","0606","0606","0606","0606"], + ["06092408","06092408","0609","0609","06092408","06092408","06092408","06092408"] + ], + [ + ["0606","0606","0606","0606","0606","","0601","0602"], + ["0609","0609","0609","0609","0609","0609","",""] + ], + [ + ["0602","0602","0602","0602","0602","0602","0602","0602"], + [ + "06092408","06092408","0609", + ["","","","","","","",""], + "06092408","06092408","06092408","06092408" + ] + ], + [ + ["0602","0602","0602","0602","0607","0606","0607","0606"], + [ + "0609","0609","0609","0609", + ["1601","","","","","","",""], + "24080609","24080609", + "" + ] + ], + /*------------------------------------------------------------------------------------------------------------------------ + End of X87 FPU. + ------------------------------------------------------------------------------------------------------------------------*/ + "10000004","10000004","10000004","10000004", + "16000C00","170E0C00","0C001600","0C00170E", + "110E0008", + "110E0008", + "0D060C01", //JMP Ap (w:z). + "100000040004", + "16001A01","170E1A01", + "1A011600","1A01170E", + "","","","","","", + ["06000C00","","06000003","06000003","16000600","0600","16000600","0600"], + ["070E0D06","","070E0003","070E0003","170E070E","070E","170E070E","170E070E"], + "","","","","","", + ["06000003","06000003","","","","","",""], + [ + ["070E0003","070E0003","070A0004","090E0008","070A0008","090E0008","070A",""], + ["070E0003","070E0003","070A0008","","070A0008","","070A",""] + ], + /*------------------------------------------------------------------------------------------------------------------------ + Two Byte operations. + ------------------------------------------------------------------------------------------------------------------------*/ + [ + ["0602","0602","0602","0602","0602","0602","070E",""], + ["070E","070E","0601","0601","0601","0601","070E",""] + ], + [ + ["0908","0908","0908","0908","0602","","0602","0601"], + [ + ["","","","","","","",""], + ["170819081B08","17081908","","","","","",""], + ["","","","","","","",""], + ["1708","","1708","1708","","","1602","17081802"], + "070E","","0601", + ["","","170819081B08","170819081B08","","","",""] + ] + ], + ["0B0E0612","0B0E070E"],["0B0E0612","0B0E070E"],"", + "","","","", + "","","","", + [["0601","0601","","","","","",""],""], + "", + "0A0A06A9", //3DNow takes ModR/M, IMM8. + [ + ["0B700770","0B700770","0A040603","0A040609"], + ["0B700770","0B700770","0A0412040604","0A0412040604"] + ], + [ + ["07700B70","07700B70","06030A04","06090A04"], + ["07700B70","07700B70","060412040A04","060412040A04"] + ], + [ + ["0A0412040606","0A0412040606","0B700770","0B700768"], + ["0A0412040604","","0B700770","0B700770"] + ], + [["06060A04","06060A04","",""],""], + ["0B70137007700140","0B70137007700140","",""], + ["0B70137007700140","0B70137007700140","",""], + [["0A0412040606","0A0412040606","0B700770",""],["0A0412040604","","0B700770",""]], + [["06060A04","06060A04","",""],""], + [["0601","0601","0601","0601","","","",""],""], + "", + [[["0A0B07080180","","",""],["0A0B07100180","","",""],["0A0B07080180","","",""],["0A0B07080180","","",""]], + ["",["0A0B060B","","",""],["0A0B07080180","","",""],["0A0B07080180","","",""]]], + [[["07080A0B0180","","",""],["07100A0B0180","","",""],["0A0B07080180","","",""],["0A0B07080180","","",""]], + ["",["0A0B060B","","",""],"",["0A0B07080180","","",""]]], + "","","", + "070E", + ["","07080A0C0001"],["","07080A0D0001"], + ["","0A0C07080001"],["","0A0D07080001"], + ["","07080A0E0001"],"", + ["","0A0E07080001"],"", + [ + ["0A040648","0B300730","0B700770","0A06066C0130"], + ["0A040648","0B300730","0B700770","0A06066C0130"], + "","" + ], + [ + [ + ["06480A04","07300B30","07700B70","066C0A060130"], + ["06480A04","07300B30","07700B70","066C0A060130"], + ["","","",["066C0A060138","066C0A060138","066C0A060138"]], + ["","","",["066C0A060138","066C0A060138","066C0A060138"]] + ], + [ + ["06480A04","07300B30","07700B70","066C0A06"], + ["06480A04","07300B30","07700B70","066C0A06"], + "","" + ] + ], + [ + ["0A0406A9","","",""],["0A0406A9","","",""], //Not Allowed to be Vector encoded. + "0A041204070C010A","0A041204070C010A" + ], + [ + [ + "07700B70","07700B70", + ["06030A04","","",""],["06060A04","","",""] //SSE4a can not be vector encoded. + ],"" + ], + [ + ["0A0A0649","","",""],["0A0A0648","","",""], //Not allowed to be Vector encoded. + "0B0C06430109","0B0C06490109" + ], + [ + ["0A0A0649","","",""],["0A0A0648","","",""], //Not allowed to be vector encoded. + "0B0C0643010A","0B0C0649010A" + ], + ["0A0406430101","0A0406490101","",""], + ["0A0406430101","0A0406490101","",""], + "","","","", + "","","", + "", + "",//Three byte opcodes 0F38 + "", + "",//Three byte opcodes 0F3A + "","","","","", + "0B0E070E", + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [["0B0E070E0180","0A0F06FF","",""],"","",""], + [ + ["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""], + ["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""],"","" + ], + [ + ["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [["0B0E070E0180","0A0F06FF","",""],"","",""], + [["0B0E070E0180","0A0F06FF","",""],"","",""], + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"","" + ], + [ + ["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""], + ["0B0E070E0180",["0A0F120F06FF","",""],"",""],"","" + ], + "0B0E070E","0B0E070E","0B0E070E","0B0E070E", + ["",[["0B0C0648","0B0C0730","",""],["0B0C0648","0B0C0730","",""],"",""]], + ["0B7007700142","0B7007700142","0A04120406430102","0A04120406490102"], + [ + ["0A040648","0A040648","",""],"", + ["0A040643","0A0412040643","",""],"" + ], + [ + ["0A040648","0A040648","",""],"", + ["0A040643","0A0412040643","",""],"" + ], + ["0B70137007700140","0B70137007700140","",""], + ["0B70137007700140","0B70137007700140","",""], + ["0B70137007700140","0B70137007700140","",""], + ["0B70137007700140","0B70137007700140","",""], + [ + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + "0A04120406430102","0A04120406460102" + ], + [ + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + "0A04120406430102","0A04120406460102" + ], + [ + ["0A040648","0B300718","0B7007380151","0A06065A0171"], + ["0A040648","0B180730","0B3807700152","0A05066C0152"], + "0A04120406430101","0A04120406460102" + ], + [["0B7007700142","","0B380770014A"],["0B700770014A","",""],"0B7007700141",""], + [ + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + ["0A040648","0B3013300730","0B70137007700152","0A061206066C0152"], + "0A04120406430102","0A04120406460102" + ], + ["0B70137007700141","0B70137007700141","0A04120406430101","0A04120406460101"], + ["0B70137007700142","0B70137007700142","0A04120406430102","0A04120406460102"], + ["0B70137007700141","0B70137007700141","0A04120406430101","0A04120406460101"], + [["0A0A06A3","","",""],"0B70137007700108","",""], + [["0A0A06A3","","",""],"0B70137007700108","",""], + [["0A0A06A3","","",""],"0B701370077001400108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","0A0F137007700108",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","0A0F137007700108",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0A0F137007700148","",""],["0A0F1206066C0148","",""]],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0B70137007700148","",""],"",""], + [["0A0A06A9","","",""],["0B70137007700148","",""],"",""], + ["","0B70137007700140","",""], + ["","0B70137007700140","",""], + [["0A0A070C","","",""],["0A04070C0108","","0A04070C0108"],"",""], + [ + [ + ["0A0A06A9","", "",""], + ["0B700770","0B700770",["0B7007700108","","0B700770"],["0A06066C0128","","0A06066C0120"]], + ["0A040710","0B700770",["0B700770","","0B7007700108"],""], + ["","",["0B7007700108","","0B700770"],""] + ], + [ + ["0A0A06A9","", "",""], + ["0B700770","0B700770",["0B7007700108","","0B700770"],["0A06066C0148","","0A06066C0140"]], + ["0A040710","0B700770",["0B700770","","0B7007700108"],""], + ["","",["0B7007700108","","0B700770"],""] + ] + ], + [ + ["0A0A06A90C00","","",""], + ["0A0406480C00","0B3007300C00",["0B7007700C000108","",""],["0A06066C0C000108","",""]], + "0B7007700C000108", + "0B7007700C000108" + ], + [ + "", + [ + "","", + [["060A0C00","","",""],"137007700C000108","",""],"", + [["060A0C00","","",""],"137007700C000108","",""],"", + [["060A0C00","","",""],"137007700C000108","",""],"" + ] + ], + [ + ["",["","",["137007700C000148","","137007700C000140"],""],"",""], + ["",["","",["137007700C000148","","137007700C000140"],""],"",""], + [["060A0C00","","",""],["06480C00","133007300C00",["137007700C000148","",""],["1206066C0C000148","",""]],"",""], + "", + [["060A0C00","","",""],["06480C00","133007300C00",["137007700C000148","","137007700C000140"],["1206066C0C000148","",""]],"",""], + "", + [["060A0C00","","",""],["06480C00","133007300C00",["137007700C000148","",""],["1206066C0C000148","",""]],"",""], + "" + ], + [ + "", + [ + "","", + [["137007700C00","137007700C00","",""],"137007700C000140","",""],["","137007700C000108","",""], + "","", + [["137007700C00","137007700C00","",""],"137007100C000140","",""],["","137007700C000108","",""] + ] + ], + [["0A0A06A9","","",""],["0A040710","13300B300730","0A0F137007700108",""],"",""], + [["0A0A06A9","","",""],["0A040710","13300B300730","0A0F137007700108",""],"",""], + [["0A0A06A9","","",""],["0A040710","13300B300730",["0A0F137007700148","",""],["0A0F1206066C0148","",""]],"",""], + [["",["","",""],"",""],"","",""], + [ + ["07080B080180","",["0B7007700141","","0B3807700149"],""], + ["064F0C000C00","",["0B7007380149","","0B7007700141"],""], + ["","","0B0C06440109",""], + ["0A04064F0C000C00","","0B0C06460109",""] + ], + [ + ["0B0807080180","",["0B7007700142","","0B380770014A"],""], + ["0A04064F","",["0B700738014A","","0B7007700142"],""], + ["","","0B0C0644010A",""], + ["0A04064F","","0B0C0646010A",""] + ], + [ + "", + ["","",["0B7007380149","","0B7007700141"],""], + ["","",["0B7007380142","","0B700770014A"],"0A06065A0170"], + ["","",["0B700770014A","","0B3807700142"],""] + ], + [ + "", + ["","",["0B700738014A","","0B7007700142"],""], + ["","","0A041204070C010A",""], + ["","","0A041204070C010A",""] + ], + [ + "",["0A040604","0B7013700770","",""], + "",["0A040604","0B7013700770","",""] + ], + [ + "",["0A040604","0B7013700770","",""], + "",["0A040604","0B7013700770","",""] + ], + [["070C0A0A","","",""],["06240A040108","","06360A040108"],["0A040646","0A040646",["","","0A0406460108"],""],""], + [ + ["06A90A0A","","",""], + ["06480A04","07300B30",["07700B700108","","07700B70"],["066C0A060128","","066C0A060120"]], + ["06480A04","07300B30",["07700B70","","07700B700108"],""], + ["","",["07700B700108","","07700B70"],""] + ], + "1106000C","1106000C","1106000C","1106000C", + [["1106000C","120F1002","",""],"","",""],[["1106000C","120F1002","",""],"","",""], + "1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C","1106000C", + [ + ["0600",["0A0F06F2","","0A0F06F6"],"",""], + ["0600",["0A0F06F0","","0A0F06F4"],"",""],"","" + ], + [ + ["0600",["06120A0F","","06360A0F"],"",""], + ["0600",["06000A0F","","06240A0F"],"",""],"","" + ], + [ + ["0600",["0A0F062F","",""],"",""], + ["0600",["0A0F062F","",""],"",""],"", + ["0600",["0A0F062F","","0A0F063F"],"",""] + ], + [ + ["0600",["062F0A0F","",""],"",""], + ["0600",["062F0A0F","",""],"",""],"", + ["0600",["062F0A0F","","063F0A0F"],"",""] + ], + "0600",[["0600","0A03120F06FF","",""],"","",""], + "0600",[["0600","0A03120F06FF","",""],"","",""], + [ + ["0600",["0A0F06FF","","0A0F06FF"],"",""], + ["0600",["0A0F06FF","","0A0F06FF"],"",""],"","" + ], + [ + ["0600",["0A0F06FF","","0A0F06FF"],"",""], + ["0600",["0A0F06FF","","0A0F06FF"],"",""],"","" + ], + "0600","0600","0600","0600","0600","0600", + "2608","2608", + "", + "070E0B0E0003", + "070E0B0E0C00","070E0B0E1800", + "0B0E070E","070E0B0E", + "2808","2808", + "", + "070E0B0E0003", + "070E0B0E0C00","070E0B0E1800", + [ + [ + ["0601","","0601"],["0601","","0601"], + "0603","0603", + ["0601","","0601"],["0601","","0601"], + ["0601","0601","0601"], + ["0601","0601",""] + ], + [ + ["","",["0602","","",""],""],["","",["0602","","",""],""], + ["","",["0602","","",""],""],["","",["0602","","",""],""], + "", + ["","","","","","","",""], + ["","","","","","","",""], + ["","","","","","","",""] + ] + ], + "0B0E070E", + "06000A000003","070E0B0E0003", + ["0B0E090E",""], + "070E0B0E0003", + ["0B0E090E",""], + ["0B0E090E",""], + "0B0E0600","0B0E0602", + [ + ["1002","","",""],"", + ["0B060706","0A020602","",""],"" + ],"", + ["","","","","070E0C000003","070E0C000003","070E0C000003","070E0C000003"], + "0B0E070E0003", + [ + ["0B0E070E0180","","",""],"", + ["0B0E070E0180","0A020602","",""],["0B0E070E0180","0A020602","",""] + ], + [ + ["0B0E070E0180","","",""],"", + ["0B0E070E0180","0A020602","",""],["0B0E070E0180","","",""] + ], + "0B0E0600","0B0E0602", + "06000A000003","070E0B0E0003", + [ + ["0A0406480C00","0B30133007300C00","0A0F137007700C000151","0A0F066C0C000151"], + ["0A0406480C00","0B30133007300C00","0A0F137007700C000151","0A0F066C0C000151"], + ["0A0406440C00","0A04120406480C00","0A0F120406440C000151",""], + ["0A0406490C00","0A04120406480C00","0A0F120406460C000151",""] + ], + ["06030A02",""], + [["0A0A06220C00","","",""],"0A04120406220C000108","",""], + ["",[["06020A0A0C00","","",""],"06020A040C000108","",""]], + ["0B70137007700C000140","0B70137007700C000140","",""], + [ + [ + "", + ["06060003","","060B0003"], + "", + ["0601","","0601"], + ["0601","","0601"], + ["0601","","0601"], + ["0606","0606","0606",""],["0606","","",""] + ], + [ + "", + ["","","","","","","",""], + "","","","", + "070E","070E" + ] + ], + "030E","030E","030E","030E","030E","030E","030E","030E", + ["",["0A040648","0B3013300730","",""],"",["0A040648","0B3013300730","",""]], + [["0A0A06A9","","",""],"0B70137006480108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300648",["0B70137006480108","",""],""],"",""], + [["0A0A06A9","","",""],"0B70137006480100","",""], + [["0A0A06A9","","",""],"0B70137007700140","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [ + ["","06490A040100","",""], + ["","06490A040100",["0A040649","","",""],["0A040649","","",""]] + ], + ["",[["0B0C06A0","","",""],["0B0C0640","0B0C0730","",""],"",""]], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [ + [["0A0A06A9","","",""],["0A040648","0B3013300648","0B70137006480108",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","0B70137006480108",""],"",""] + ], + [["0A0A06A9","","",""],["0A040648","0B3013300648",["0B70137006480108","","0B7013700648"],""],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [ + "", + ["0A040648","0A040730","0B3807700141",""], + ["0A040649","0B300738",["0A0406480140","0B7007380140","0B700770014A"],"0A06065A0170"], + "0B3807700142" + ], + [[["06090A0A","","",""],["07700B700108","",""],"",""],""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], + [["","","",["0A040648","0A040730","",""]],"0000"], + [["0A0A06A9","","",""],"0B70137006480108","",""], + [["0A0A06A9","","",""],["0B70137006480108","",""],"",""], + [["0A0A06A9","","",""],"0B7013700648","",""], + [["0A0A06A9","","",""],"0B70137007700140","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + ["",[["0A0A060A","","",""],["0B040648","0B040648","",""],"",""]], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","",""],["0A061206066C0148","",""]],"",""], + [["0A0A06A9","","",""],"0B70137007700140","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730",["0B70137007700148","",""],["0A061206066C0148","",""]],"",""], + "", + /*------------------------------------------------------------------------------------------------------------------------ + Three Byte operations 0F38. + ------------------------------------------------------------------------------------------------------------------------*/ + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],["0A040648","0B3013300730","",""],"",""], + [["0A0A06A9","","",""],"0B70137007700108","",""], + ["",["","0B3013300730",["0B70137007700148","",""],""],"",""], + ["",["","0B3013300730","0B70137007700140",""],"",""], + ["",["","0B300730","",""],"",""], + ["",["","0B300730","",""],"",""], + ["",["0A0406482E00","0B30133007301530","0B7013700770",""],["","","07380B70",""],""], + ["",["","","0B7013700770",""],["","","071C0B70",""],""], + ["",["","","0B7013700770",""],["","","070E0B70",""],""], + ["",["","0B300718",["0B7007380109","",""],""],["","","07380B70",""],""], + ["",["0A0407102E00","0B30133007301530",["0B70137007700148","","0B70137007700140"],""],["","","071C0B70",""],""], + ["",["0A0407102E00","0B30133007301530",["0B70137007700148","","0B70137007700140"],""],["","","07380B70",""],""], + ["",["","0B3013300730",["0B70137007700148","","0B70137007700140"],""],"",""], + ["",["0A040648","0B300730","",""],"",""], + ["",["","0B300644",["0B7006440138","",""],["0A0606440138","",""]],"",""], + ["",["","0A050646",["0B6806460108","","0B700646"],["","","0A060646"]],"",""], + ["",["","0A050648",["0B6806480138","","0B680648"],["0A0606480138","",""]],"",""], + ["",["","",["0A06065A0108","","0A06065A"],["","","0A06065A"]],"",""], + [["0A0A06A9","","",""],"0B7007700108","",""], + [["0A0A06A9","","",""],"0B7007700108","",""], + [["0A0A06A9","","",""],["0B7007700148","",""],"",""], + ["",["","","0B7007700140",""],"",""], + ["","0B7007380108",["","","07380B70",""],""], + ["","0B70071C0108",["","","071C0B70",""],""], + ["","0B70070E0108",["","","070E0B70",""],""], + ["","0B7007380108",["","","07380B70",""],""], + ["","0B70071C0108",["","","071C0B70",""],""], + ["","0B7007380108",["","","07380B70",""],""], + ["",["","",["0A0F137007700108","","0A0F13700770"],""],["","",["0A0F13700770","","0A0F137007700108"],""],""], + ["",["","",["0A0F137007700148","","0A0F137007700140"],["0A0F1206066C0148","",""]],["","",["0A0F137007700140","","0A0F137007700148"],""],""], + ["","0B70137007700140",["","",["0B7006FF","","0B7006FF0108"],""],""], + ["",["0A040648","0B3013300730","0A0F137007700140",""],["","",["0A0F0770","","0A0F07700108"],""],""], + [["",["0B7007700108","",""],"",""],["","",["","",["","","0B7006FF0108"],""],""]], + ["",["0B70137007700148","",""],"",""], + ["",["","0B3013300730",["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["","0B3013300730",["0A0412040644014A","","0A04120406480142"],""],"",""], + ["",["","073013300B30","",""],"",""], + ["",["","0B3013300730","",""],"",""], + ["","0B7007380108",["","","07380B70",""],""], + ["","0B70071C0108",["","","071C0B70",""],""], + ["","0B70070E0108",["","","070E0B70",""],""], + ["","0B7007380108",["","","07380B70",""],""], + ["","0B70071C0108",["","","071C0B70",""],""], + ["","0B7007380108",["","",["06480A04","07380B70",""],""],""], + ["",["","0A051205065A",["0B70137007700148","","0B70137007700140"],["0A061206066C0108","",""]],"",""], + ["",["0A040710","0B3013300730","0A0F137007700140",""],"",""], + ["","0B70137007700108",["","",["0B7006FF","","0B7006FF0108"],""],""], + ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],["","",["0A0F0770","","0A0F07700108"],""],""], + ["","0B70137007700108",["","","0B7006FF0100",""],""], + ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["","0B70137007700108","",""], + ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["","0B70137007700108","",""], + ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["",["0A0412040648","0B3013300730",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["",["0A040648",["0A040648","0A040648","",""],"",""],"",""], + ["",["","",["0B7007700159","","0B7007700151"],["0A06066C0159","","0A06066C0151"]],"",""], + ["",["","",["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["","",["0B7007700148","","0B7007700140"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["",["",["0B3013300730","",""],["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B70137007700148","","0B70137007700140"],["0A061206066C0148","",""]],"",""], + "","","","", + ["",["","",["0B7007700148","","0B7007700140"],""],"",""], + ["",["","",["0A04120406440108","","0A0412040646"],""],"",""], + ["",["","",["0B7007700148","","0B7007700140"],""],"",""], + ["",["","",["0A04120406440108","","0A0412040646"],""],"",""], + ["",["","","",["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["","","",["0A061206066C0159","",""]],"",""], + ["",["","","",["0A061206066C0159","","0A061206066C0151"]],"",""], + ["",["","","",["0A061206066C0159","","0A061206066C0151"]],"",""], + "", + ["",["","","",["0A061206066C0149","","0A061206066C0141"]],"",""], + "","", + ["",["","0B300644",["0B7006440128","",""],["0A0606440128","",""]],"",""], + ["",["","0B300646",["0B7006460128","","0B7006460120"],["","","0A0606460120"]],"",""], + ["",["","0A050648",["0B6806480128","","0B6806480120"],["0A0606480128","",""]],"",""], + ["",["","",["0A06065A0128","","0A06065A0120"],["","","0A06065A0120"]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + "","","","", + ["",["","",["0B70137007700148","","0B70137007700140"],["0A061206066C0148","","0A061206066C0140"]],"",""], + ["",["","",["0B70137007700158","","0B70137007700150"],["0A061206066C0158","","0A061206066C0150"]],"",""], + ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], + "","","","","", + ["",["","","",["0A061206066C0148","",""]],"",""], + ["",["","","",["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + ["",["","","",["0A06120F066C0148","",""]],"",""], + "","","","", + ["",["","","",["0A0F1206066C0148","",""]],"",""], + ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], + ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], + ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], + ["",["","0B300640",["0B7006400108","",""],""],"",""], + ["",["","0B300642",["0B7006420108","",""],""],"",""], + ["",["",["","",["0B7006000108","",""],""],"",""]], + ["",["",["","",["0B7006100108","",""],""],"",""]], + ["",["","",["0B70062F0108","","0B70063F"],""],"",""], + ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], + ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], + ["",["","",["0B70137007700148","","0B70137007700140"],""],"",""], + [["","0B0C060B0180","",""],""], + [["","0B0C060B0180","",""],""], + [["","0B0C060B0180","",""],""], + ["",["","","0B70137007700140",""],"",""], + ["",["","","",["0A061206066C014A","",""]],"",""], + "", + ["",["","","",["0A061206066C0148","",""]],"",""], + ["",["","","",["0A061206066C0148","",""]],"",""], + ["",["","",["0B7007700108","","0B700770"],""],"",""], + ["",["","",["0B7007700108","","0B700770"],""],"",""], + ["",["","",["07700B700108","","07700B70"],""],"",""], + ["",["","",["07700B700108","","07700B70"],""],"",""], + "", + ["",["","",["0B70137007700108","","0B7013700770"],""],"",""], + "","", + ["",["",["0B30073013300124","","0B30064813300124"],["0B700770012C","","0B7007380124"],["0A06066C012C","","0A06065A0124"]],"",""], + ["",["",["0A04073012040104","","0B30073013300104"],["0B380770010C","","0B7007700104"],""],"",""], + ["",["",["0B30073013300134","","0B30064813300134"],["0B700770013C","","0B7007380134"],["0A06066C013C","","0A06065A0104"]],"",""], + ["",["",["0A04073012040104","","0B30073013300104"],["0B380770010C","","0B7007700104"],""],"",""], + "","", + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040714","","0A0412040718"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["","",["07700B70010C","","07380B700104"],["066C0A06012C","","065A0A060124"]],"",""], + ["",["","",["07700B38010C","","07700B700104"],""],"",""], + ["",["","",["07700B70013C","","07380B700134"],["066C0A06013C","","065A0A060134"]],"",""], + ["",["","",["07700B38010C","","07700B700104"],""],"",""], + ["",["","","",["0A061206066C011A","",""]],"",""], + "", + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + "","","","", + ["",["","","0B70137007700140",["0A061206066C0118","",""]],"",""], + ["",["","","0B70137007700140",["0A061206066C0148","",""]],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + ["",["",["0B3013300730","","0B3013300730"],["0B7013700770014A","","0B70137007700142"],["0A061206066C015A","","0A061206066C0152"]],"",""], + ["",["",["0A0412040644","","0A0412040646"],["0A0412040644010A","","0A04120406460102"],""],"",""], + "","","","", + ["",["","",["0B7007700148","","0B7007700140"],""],"",""], + "", + [ + [ + ["",["","","",["060C013C","","060A0134"]],"",""], + ["",["","",["060C013C","","060A0134"],["060C013C","",""]],"",""], + ["",["","",["060C013C","","070A0134"],["060C013C","",""]],"",""], + "", + ["",["","","",["060C013C","","060A0134"]],"",""], + ["",["","",["060C013C","","060A0134"],["060C013C","",""]],"",""], + ["",["","",["060C013C","","060A0134"],["060C013C","",""]],"",""], + "" + ],"" + ], + [ + [ + "", + ["",["","",["060C010C","","060C0104"],""],"",""], + ["",["","",["060C010C","","060C0104"],""],"",""], + "","", + ["",["","",["060C010C","","060C0104"],""],"",""], + ["",["","",["060C010C","","060C0104"],""],"",""], + "" + ],"" + ], + [["0A040648","","",""],["","",["0A06066C0159","","0A06066C0151"],["0A06066C0109","",""]],"",""], + [["0A040648","","",""],["","","",["0A06066C0109","",""]],"",""], + [["0A040648","","",""],["","",["0A06066C0159","","0A06066C0151"],["0A06066C0109","",""]],"",""], + [["0A0406482E00","","",""],["","",["0A04120406440109","","0A04120406460101"],["0A06066C0109","",""]],"",""], + [["0A040648","","",""],["","",["0A06066C0159","","0A06066C0151"],["0A06066C015A","",""]],"",""], + [["0A040648","","",""],["","",["0A04120406440109","","0A04120406460101"],["0A06066C0148","",""]],"",""], + "","", + [[["","","",["0A06060C0120","","0A06060C0128"]],["","","",["060C0A060128","","060C0A060120"]],"",""],""], + [[["","","",["0A06060C0130","","0A06060C0138"]],["","","",["060C0A060138","","060C0A060130"]],"",""],""], + "","", + [[["","","",["0A06060C0120","","0A06060C0128"]],["","","",["060C0A060128","","060C0A060120"]],"",""],""], + [[["","","",["0A06060C0130","","0A06060C0138"]],["","","",["060C0A060138","","060C0A060130"]],"",""],""], + "","","","","", + ["",["0A040648","0A040648","",""],"",""], + ["",["0A040648","0A0412040648","",""],"",""], + ["",["0A040648","0A0412040648","",""],"",""], + ["",["0A040648","0A0412040648","",""],"",""], + ["",["0A040648","0A0412040648","",""],"",""], + "","","","","","","","","","","","","","","","", + [ + ["0B0E070E0180","","",""], + ["0B0E070E0180","","",""],"", + ["0B0C06000180","","",""] + ], + [ + ["070E0B0E0180","","",""], + ["070E0B0E0180","","",""],"", + ["0B0C070E0180","","",""] + ], + ["",["","0B0C130C070C","",""],"",""], + [ + "", + ["",["","130C070C","",""],"",""], + ["",["","130C070C","",""],"",""], + ["",["","130C070C","",""],"",""], + "","","","" + ],"", + [ + ["","0B0C070C130C","",""],"", + ["","0B0C130C070C","",""], + ["","0B0C130C070C","",""] + ], + [ + "", + ["0B0C070C","","",""], + ["0B0C070C","","",""], + ["","0B0C130C070C1B0C","",""] + ], + [ + ["","0B0C130C070C","",""], + ["","0B0C130C070C","",""], + ["","0B0C130C070C","",""], + ["","0B0C130C070C","",""] + ], + "","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------ + Three Byte operations 0F3A. + ------------------------------------------------------------------------------------------------------------------------*/ + ["",["","0A05065A0C00","0B7007700C000140",""],"",""], + ["",["","0A05065A0C00","0B7007700C000140",""],"",""], + ["",["",["0B30133007300C00","",""],"",""],"",""], + ["",["","",["0B70137007700C000148","","0B70137007700C000140"],["0A061206066C0C000108","",""]],"",""], + ["",["","0B3007300C00",["0B7007700C000148","",""],""],"",""], + ["",["","0B3007300C00","0B7007700C000140",""],"",""], + ["",["","0A051205065A0C00","",""],"",""], + ["",["","","",["0A06066C0C000108","",""]],"",""], + ["",["0A0406480C00","0B3007300C00",["0B7007700C000149","",""],""],"",""], + ["",["0A0406480C00","0B3007300C00","0B7007700C000141",""],"",""], + ["",["0A0406440C00","0A04120406440C00",["0A04120406440C000109","",""],""],"",""], + ["",["0A0406460C00","0A04120406460C00","0A04120406460C000101",""],"",""], + ["",["0A0406480C00","0B30133007300C00","",""],"",""], + ["",["0A0406480C00","0B30133007300C00","",""],"",""], + ["",["0A0406480C00","0B30133007300C00","",""],"",""], + [["0A0A06A90C00","","",""],"0B70137007700C000108","",""], + "","","","", + [["","06000A040C000108","",""],["","070C0A040C000108","",""]], + [["","06020A040C000108","",""],["","070C0A040C000108","",""]], + ["",["06240A040C000108","","06360A040C00"],"",""], + ["","070C0A040C000108","",""], + ["",["","0A05120506480C00",["0B70137006480C000108","","0B70137006480C00"],""],"",""], + ["",["","06480A050C00",["06480B700C000108","","06480B700C00"],""],"",""], + ["",["","",["0A061206065A0C000108","","0A061206065A0C00"],""],"",""], + ["",["","",["065A0A060C000108","","065A0A060C00"],""],"",""], + "", + ["",["","07180B300C00",["07380B700C000109","",""],""],"",""], + ["",["","",["0A0F137007700C000148","","0A0F137007700C000140"],["0A0F1206066C0C000148","",""]],"",""], + ["",["","",["0A0F137007700C000148","","0A0F137007700C000140"],["0A0F1206066C0C000148","",""]],"",""], + ["","0A04120406200C000108","",""], + ["",["0A04120406440C000108","",""],"",""], + ["",["",["0A04120406240C00","","0A04120406360C00"],["0A04120406240C000108","","0A04120406360C00"],""],"",""], + ["",["","",["0B70137007700C000148","","0B70137007700C000140"],""],"",""], + "", + ["",["","",["0B70137007700C000148","","0B70137007700C000140"],""],"",""], + ["",["","",["0B7007700C000149","","0B7007700C000141"],["0A06066C0C000159","","0A06066C0C000151"]],"",""], + ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], + "","","","","","","","", + ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], + ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], + ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], + ["",["",["0A0F06FF0C00","","0A0F06FF0C00"],"",""],"",""], + "","","","", + ["",["","0A05120506480C00",["0B70137006480C000108","","0B70137006480C00"],""],"",""], + ["",["","06480A050C00",["06480B700C000108","","06480B700C00"],""],"",""], + ["",["","",["0A061206065A0C000108","","0A061206065A0C00"],""],"",""], + ["",["","",["065A0A060C000108","","065A0A060C00"],""],"",""], + "","", + ["",["","0A0F063F0C00",["0A0F137007700C000108","","0A0F137007700C00"],""],"",""], + ["",["","",["0A0F137007700C000108","","0A0F137007700C00"],""],"",""], + ["",["0A0406480C00","0B30133007300C00","",""],"",""], + ["",["0A0406480C00","0A04120406480C00","",""],"",""], + ["",["0A0406480C00","0B30133007300C00",["0B70137007700C000108","",""],""],"",""], + ["",["","",["0B70137007700C000148","","0B70137007700C000140"],""],"",""], + ["",["0A0406480C00","0A04120406480C00","",""],"",""], + "", + ["",["","0A051205065A0C00","",""],"",""], + "", + ["",["",["0B301330073015300E00","","0B301330153007300E00"],"",""],"",""], + ["",["",["0B301330073015300E00","","0B301330153007300E00"],"",""],"",""], + ["",["","0B30133007301530","",""],"",""], + ["",["","0B30133007301530","",""],"",""], + ["",["","0A051205065A1505","",""],"",""], + "","","", + ["",["","",["0B70137007700C000149","","0B70137007700C000141"],""],"",""], + ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], + ["",["","","",["0A06066C0C000159","","0A06066C0C000151"]],"",""], + "", + ["",["","",["0B70137007700C000149","","0B70137007700C000141"],""],"",""], + ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], + ["",["","",["0B7007700C000149","","0B7007700C000141"],""],"",""], + ["",["","",["0A04120406440C000109","","0A04120406460C000101"],""],"",""], + "","","","", + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["0A0406480C00","0A0406480C00","",""],"",""], + ["",["0A0406480C00","0A0406480C00","",""],"",""], + ["",["0A0406480C00","0A0406480C00","",""],"",""], + ["",["0A0406480C00","0A0406480C00","",""],"",""], + "","", + ["",["","",["0A0F07700C000148","","0A0F07700C000140"],""],"",""], + ["",["","",["0A0F06440C000108","","0A0F06460C00"],""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], + ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], + ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], + "","","","","","","","", + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], + ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0B30133007301530","","0B30133015300730"],"",""],"",""], + ["",["",["0A04120406441530","","0A04120415300644"],"",""],"",""], + ["",["",["0A04120406461530","","0A04120415300646"],"",""],"",""], + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","", + [["","","","0A06066C0C000141"],["","","",["0A06066C0C000159","",""]],"",["","","","0A06066C0C000151"]], + [["","","","0A06066C0C000141"],["","","",["0A06066C0C000159","",""]],"",""], + "0A0406480C00","","","", + "","","","","","","","","","","","","","","", + ["",["0A0406480C00","0A0406480C00","",""],"",""], + "","","","","","", + ["","","",["","","","0A06066C0C000151"]], + "","","","","","","","","", + ["","","",["","0B0C070C0C00","",""]], + "","","","","","","","","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP 8. + ------------------------------------------------------------------------------------------------------------------------*/ + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","", + "0A04120406481404","0A04120406481404","0A04120406481404","","","","","","", + "0A04120406481404","0A04120406481404","","","","","","0A04120406481404","0A04120406481404","0A04120406481404", + "","","","","","","0A04120406481404","0A04120406481404", + "","",["0B30133007301530","","0B30133015300730"],["0A04120406481404","","0A04120414040648"],"","","0A04120406481404", + "","","","","","","","","","","","","","","", + "0A04120406481404","","","","","","","","","","0A0406480C00","0A0406480C00","0A0406480C00","0A0406480C00", + "","","","","","","","", + "0A04120406480C00","0A04120406480C00","0A04120406480C00","0A04120406480C00", + "","","","","","","","","","","","","","","","","","","","","","","","","","","","", + "0A04120406480C00","0A04120406480C00","0A04120406480C00","0A04120406480C00", + "","","","","","","","","","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP 9. + ------------------------------------------------------------------------------------------------------------------------*/ + "", + ["","130C070C","130C070C","130C070C","130C070C","130C070C","130C070C","130C070C"], + ["","130C070C","","","","","130C070C",""], + "","","","","","","","","","","","","","","", + ["",["070C","070C","","","","","",""]], + "","","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "0B300730","0B300730","0B300730","0B300730", + "","","","","","","","","","","","", + ["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"], + ["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"], + ["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"],["0A0406481204","","0A0412040648"], + "","","","","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","","","", + "0A040648","0A040648","0A040648","","","0A040648","0A040648","","","","0A040648","","","","","", + "0A040648","0A040648","0A040648","","","0A040648","0A040648","","","","0A040648","","","","","", + "0A040648","0A040648","0A040648","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------ + AMD XOP A. + ------------------------------------------------------------------------------------------------------------------------*/ + "","","","","","","","","","","","","","","","", + "0B0C070C0C020180","",["130C06240C020180","130C06240C020180","","","","","",""], + "","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------- + L1OM Vector. + -------------------------------------------------------------------------------------------------------------------------*/ + "","","","","1206","","","","","","","","","","","", + [["0A0606610120","0A0606610120","",""],""],"", + [["0A0606610120","0A0606610120","",""],""], + [["0A0606610120","0A0606610120","",""],""], + [["0A0606610100","0A0606610100","",""],""],"", + [["0A0606610100","0A0606610100","",""],""], + [["0A0606610100","0A0606610100","",""],""], + ["0A06066C0124",""],["066C0124",""],"",["066C0124",""], + ["066C0A060104",""],["066C0104",""],"",["066C0104",""], + ["0A0F120606610150","0A0F120606610150","",""],"0A0F120606610140","0A0F120606610140","", + ["0A0F120606610150","0A0F120606610150","",""],"0A0F120606610140","0A0F120606610140","", + "","","","","","","","", + "0A0F120606610140","","","","","","","","","","","","","","","", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","","0A06120F06610140","","0A06120F06610140","0A06120606610150","0A06120606610140", + ["0A06120606610150","0A06120606610150","",""],"","","","","","","", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","","0A06120F06610140","","0A06120F06610140","","", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","","0A06120F06610140","","0A06120F06610140","","", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + "0A06120606610150","0A06120606610140", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"","","", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"","","", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","0A06120606610140","0A06120606610140","","","0A06120606610150","0A06120606610140", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","0A06120606610140","", + ["0A06120606610150","0A06120606610150","",""],"0A06120606610140","0A06120606610140","", + ["","0A0606610152","",""],["0A0606610153","0A0606610152","",""],["0A0606610153","0A0606610152","",""],"", + ["","0A0606610158","",""],["0A0606610141","0A0606610148","",""],["0A0606610141","0A0606610148","",""],"", + "0A0606610153","","0A0606610150","0A0606610152","","0A0606610150","0A0606610150","", + "0A06120606610140","0A06120606610140","0A06120606610140","", + ["0A06120606610140","0A06120606610140","",""],["0A06120606610140","0A06120606610140","",""], + ["0A06120606610140","0A06120606610140","",""],["0A06120606610140","0A06120606610140","",""], + "0A06120606610140","0A06120606610140","","","","","","", + "0A0606610140","0A0606610150","0A0606610150","","0A0606610150","","","", + "0A06120606610140","","","","","","","", + "0A0606610150","","0A06120606610150","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "0A0606610C010150","0A0606610C000C00","0A06120606610C010140","0A0606610C010140","","","","", + "","","","","","","","", + /*------------------------------------------------------------------------------------------------------------------------- + L1OM Mask, Mem, and bit opcodes. + -------------------------------------------------------------------------------------------------------------------------*/ + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"], + ["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"],["","0B0E070E0C010C000C00"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + ["","0B0E070E"],["","0B0E070E"],["","0B0E070E"],["","0B0E070E"], + "","","","", + "06FF0A0F", + [["0601","0601","0604","0604","","","",""],""], + [["0601","0601","","","","","",""],""], + [["0601","0601","","","","","",""],""], + "06FF0A0F","06FF0B06","07060A0F","06FF0B06", + "06FF0A0F","06FF0A0F","06FF0A0F","06FF0A0F", + "06FF0A0F","06FF0A0F","06FF0A0F","06FF0A0F", + "","06FF0A0F", + ["",["0B07","0B07","","","","","",""]], + ["",["0B07","0B07","","","","","",""]] +]; + +/*------------------------------------------------------------------------------------------------------------------------- +3DNow uses the byte after the operands as the select instruction code, so in the Mnemonics there is no instruction name, but +in the Operands array the operation code 0F0F which is two byte opcode 0x10F (using the disassemblers opcode value system) +automatically takes operands ModR/M, and MM register. Once the operands are decoded the byte value after the operands is +the selected instruction code for 3DNow. The byte value is an 0 to 255 value so the listing is 0 to 255. +--------------------------------------------------------------------------------------------------------------------------- +At the very end of the function ^DecodeInstruction()^ an undefined instruction name with the operands MM, and MM/MMWORD is +compared for if the operation code is 0x10F then the next byte is read and is used as the selected 3DNow instruction. +-------------------------------------------------------------------------------------------------------------------------*/ + +const M3DNow = [ + "","","","","","","","","","","","","PI2FW","PI2FD","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","PFNACC","","","","PFPNACC","", + "PFCMPGE","","","","PFMIN","","PFRCP","PFRSQRT","","","FPSUB","","","","FPADD","", + "PFCMPGT","","","","PFMAX","","PFRCPIT1","PFRSQIT1","","","PFSUBR","","","","PFACC","", + "PFCMPEQ","","","","PFMUL","","PFRCPIT2","PMULHRW","","","","PSWAPD","","","","PAVGUSB", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","", + "","","","","","","","","","","","","","","","" +]; + +/*------------------------------------------------------------------------------------------------------------------------- +Virtual machine synthetic operation codes is under two byte operation code 0FC7 which is opcode 0x1C7 using the disassemblers +opcode value system. The operation code 0x1C7 is an group opcode containing 3 operation codes, but only one of the codes +is used in the ModR/M grouped opcode for synthetic virtual machine operation codes. The ModR/M byte has to be in register mode +using register code 001 for the virtual machine synthetic operation codes. The effective address has to be set 000 which uses +the full ModR/M byte as an static opcode encoding under the group opcode 001. This makes the operation code 0F C7 C8. +The resulting instruction name in the Mnemonics map is "SSS", and takes no Operands in the Operands array. The two bytes after +0F C7 C8 are used as the select synthetic operation code. Only the first 4 values of both bytes have an select operation code, +so an 5x5 map is used to keep the mapping small. +--------------------------------------------------------------------------------------------------------------------------- +When the operation code is 0F C7 and takes the ModR/M byte value C8 the operation code is "SSS" with no operands. +At the very end of the function ^DecodeInstruction()^ an instruction that is "SSS" is compared if it is instruction "SSS". +If it is operation "SSS" then the two bytes are then read as two codes which are used as the selected operation code in the 5x5 map. +--------------------------------------------------------------------------------------------------------------------------- +link to the patent https://www.google.com/patents/US7552426 +-------------------------------------------------------------------------------------------------------------------------*/ + +const MSynthetic = [ + "VMGETINFO","VMSETINFO","VMDXDSBL","VMDXENBL","", + "VMCPUID","VMHLT","VMSPLAF","","", + "VMPUSHFD","VMPOPFD","VMCLI","VMSTI","VMIRETD", + "VMSGDT","VMSIDT","VMSLDT","VMSTR","", + "VMSDTE","","","","" +]; + +/*------------------------------------------------------------------------------------------------------------------------- +Condition codes Note that the SSE, and MVEX versions are limited to the first 7 condition codes. +XOP condition codes map differently. +-------------------------------------------------------------------------------------------------------------------------*/ + +const ConditionCodes = [ + "EQ","LT","LE","UNORD","NEQ","NLT","NLE","ORD", //SSE/L1OM/MVEX. + "EQ_UQ","NGE","NGT","FALSE","NEQ_OQ","GE","GT","TRUE", //VEX/EVEX. + "EQ_OS","LT_OQ","LE_OQ","UNORD_S","NEQ_US","NLT_UQ","NLE_UQ","ORD_S", //VEX/EVEX. + "EQ_US","NGE_UQ","NGT_UQ","FALSE_OS","NEQ_OS","GE_OQ","GT_OQ","TRUE_US", //VEX/EVEX. + "LT","LE","GT","GE","EQ","NEQ","FALSE","TRUE" //XOP. +]; + +/*------------------------------------------------------------------------------------------------------------------------- +The Decoded operation name. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Instruction = ""; + +/*------------------------------------------------------------------------------------------------------------------------- +The Instructions operands. +-------------------------------------------------------------------------------------------------------------------------*/ + +var InsOperands = ""; + +/*------------------------------------------------------------------------------------------------------------------------- +This object stores a single decoded Operand, and gives it an number in OperandNum (Operand Number) for the order they are +read in the operand string. It also stores all of the Settings for the operand. +--------------------------------------------------------------------------------------------------------------------------- +Each Operand is sorted into an decoder array in the order they are decoded by the CPU in series. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^DecodeOperandString()^ Which sets the operands active and gives them there settings along the X86Decoder array. +--------------------------------------------------------------------------------------------------------------------------- +The following X86 patent link might help http://www.google.com/patents/US7640417 +-------------------------------------------------------------------------------------------------------------------------*/ + +var Operand = function(){ + return( + { + Type:0, //The operand type some operands have different formats like DecodeImmediate() which has a type input. + BySizeAttrubute:false, //Effects how size is used depends on which operand type for which operand across the decoder array. + /*------------------------------------------------------------------------------------------------------------------------- + How Size is used depends on the operand it is along the decoder array for which function it uses to + decode Like DecodeRegValue(), or Decode_ModRM_SIB_Address(), and lastly DecodeImmediate() as they all take the BySize. + -------------------------------------------------------------------------------------------------------------------------*/ + Size:0x00, //The Setting. + OperandNum:0, //The operand number basically the order each operand is read in the operand string. + Active:false, //This is set by the set function not all operand are used across the decoder array. + //set the operands attributes then set it active in the decoder array. + set:function(T, BySize, Settings, OperandNumber) + { + this.Type = T; + this.BySizeAttrubute = BySize; + this.Size = Settings; + this.OpNum = OperandNumber; //Give the operand the number it was read in the operand string. + this.Active = true; //set the operand active so it's settings are decoded by the ^DecodeOperands()^ function. + }, + //Deactivates the operand after they are decoded by the ^DecodeOperands()^ function. + Deactivate:function(){ this.Active = false; } + } + ); +}; + +/*------------------------------------------------------------------------------------------------------------------------- +The Decoder array is the order each operand is decoded after the select opcode if used. They are set during the decoding of +the operand string using the function ^DecodeOperandString()^ which also gives each operand an number for the order they are +read in. Then they are decoded by the Function ^DecodeOperands()^ which decodes each set operand across the X86Decoder in order. +The number the operands are set during the decoding of the operand string is the order they will be positioned after decoding. +As the operands are decoded they are also Deactivated so the next instruction can be decoded using different operands. +--------------------------------------------------------------------------------------------------------------------------- +The following X86 patent link might help http://www.google.com/patents/US7640417 +--------------------------------------------------------------------------------------------------------------------------- +Used by functions ^DecodeOperandString()^, and ^DecodeOperands()^, after function ^DecodeOpcode()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var X86Decoder = [ + /*------------------------------------------------------------------------------------------------------------------------- + First operand that is always decoded is "Reg Opcode" if used. + Uses the function ^DecodeRegValue()^ the input RValue is the three first bits of the opcode. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Reg Opcode if used. + /*------------------------------------------------------------------------------------------------------------------------- + The Second operand that is decoded in series is the ModR/M address if used. + Reads a byte using function ^Decode_ModRM_SIB_Value()^ gives it to the function ^Decode_ModRM_SIB_Address()^ which only + reads the Mode, and Base register for the address, and then decodes the SIB byte if base register is "100" binary in value. + does not use the Register value in the ModR/M because the register can also be used as a group opcode used by the + function ^DecodeOpcode()^, or uses a different register in single size with a different address pointer. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //ModR/M address if used. + /*------------------------------------------------------------------------------------------------------------------------- + The third operand that is decoded if used is for the ModR/M reg bits. + Uses the already decoded byte from ^Decode_ModRM_SIB_Value()^ gives the three bit reg value to the function ^DecodeRegValue()^. + The ModR/M address, and reg are usually used together, but can also change direction in the encoding string. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //ModR/M reg bits if used. + /*------------------------------------------------------------------------------------------------------------------------- + The fourth operand that is decoded in sequence is the first Immediate input if used. + The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //First Immediate if used. + /*------------------------------------------------------------------------------------------------------------------------- + The fifth operand that is decoded in sequence is the second Immediate input if used. + The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Second Immediate if used (Note that the instruction "Enter" uses two immediate inputs). + /*------------------------------------------------------------------------------------------------------------------------- + The sixth operand that is decoded in sequence is the third Immediate input if used. + The function ^DecodeImmediate()^ starts reading bytes as a number for input to instruction. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Third Immediate if used (Note that the Larrabee vector instructions can use three immediate inputs). + /*------------------------------------------------------------------------------------------------------------------------- + Vector adjustment codes allow the selection of the vector register value that is stored into variable + VectorRegister that applies to the selected SSE instruction that is read after that uses it. + The adjusted vector value is given to the function ^DecodeRegValue()^. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Vector register if used. And if vector adjustments are applied to the SSE instruction. + /*------------------------------------------------------------------------------------------------------------------------- + Immediate Register encoding if used. + During the decoding of the immediate operands the ^DecodeImmediate()^ function stores the read IMM into an variable called + IMMValue. The upper four bits of IMMValue is given to the input RValue to the function ^DecodeRegValue()^. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Immediate Register encoding if used. + /*------------------------------------------------------------------------------------------------------------------------- + It does not matter which order the explicit operands decode as they do not require reading another byte after the opcode. + Explicit operands are selected internally in the cpu for instruction codes that only use one register, or pointer, or number input. + -------------------------------------------------------------------------------------------------------------------------*/ + new Operand(), //Explicit Operand one. + new Operand(), //Explicit Operand two. + new Operand(), //Explicit Operand three. + new Operand() //Explicit Operand four. +]; + +/*------------------------------------------------------------------------------------------------------------------------- +SizeAttrSelect controls the General arithmetic extended sizes "8/16/32/64", and SIMD Vector register extended sizes "128/256/512/1024". +--------------------------------------------------------------------------------------------------------------------------- +General arithmetic sizes "8/16/32/64" change by operand override which makes all operands go 16 bit. +The width bit which is in the REX prefix makes operands go all 64 bits the changes depend on the instructions adjustable size. +The value system goes as follows: 0=8, or 16, then 1=Default32, then 2=Max64. Smallest to largest in order. +Changeable from prefixes. Code 66 hex is operand override, 48 hex is the REX.W setting. By default operands are 32 bit +in size in both 32 bit mode, and 64 bit modes so by default the Size attribute setting is 1 in value so it lines up with 32. +In the case of fewer size settings the size system aligns in order to the correct prefix settings. +--------------------------------------------------------------------------------------------------------------------------- +If in 16 bit mode the 16 bit operand size trades places with 32, so when the operand override is used it goes from 16 to 32. +Also in 32 bit mode any size that is 64 changes to 32, but except for operands that do not use the BySize system. +--------------------------------------------------------------------------------------------------------------------------- +During Vector instructions size settings "128/256/512" use the SizeAttrSelect as the vector length setting as a 0 to 3 value from +smallest to largest Note 1024 is Reserved the same system used for General arithmetic sizes "8/16/32/64" that go in order. +If an operand is used that is 32/64 in size the Width bit allows to move between Sizes 32/64 separately. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^GetOperandSize()^ which uses a fast base 2 logarithm system. +The function ^DecodeOpcode()^ also uses the current size setting for operation names that change name by size, Or +In vector instructions the select instruction by size is used to Add additional instructions between the width bit (W=0), and (W=1). +-------------------------------------------------------------------------------------------------------------------------*/ + +var SizeAttrSelect = 1; + +/*------------------------------------------------------------------------------------------------------------------------- +The Width bit is used in combination with SizeAttrSelect only with Vector instructions. +-------------------------------------------------------------------------------------------------------------------------*/ + +var WidthBit = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +Pointer size plus 16 bit's used by FAR JUMP and other instructions. +For example FAR JUMP is size attributes 16/32/64 normally 32 is the default size, but it is 32+16=48 FWORD PTR. +In 16 bit CPU mode the FAR jump defaults to 16 bits, but because it is a far jump it is 16+16=32 which is DWORD PTR. +Set by the function ^DecodeOperandString()^ for if the ModR/M operand type is far pointer address. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var FarPointer = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +AddressOverride is hex opcode 67 then when used with any operation that uses the ModR/M in address mode the ram address +goes down one in bit mode. Switches 64 address mode to 32 bit address mode, and in 32 bit mode the address switches to +16 bit address mode which uses a completely different ModR/M format. When in 16 bit mode the address switches to 32 bit. +Set true when Opcode 67 is read by ^DecodePrefixAdjustments()^ which effects the next opcode that is not a prefix opcode +then is set false after instruction decodes. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var AddressOverride = false; + +/*------------------------------------------------------------------------------------------------------------------------- +Extended Register value changes by the "R bit" in the REX prefix, or by the "Double R bit" settings in EVEX Extension +which makes the Register operand reach to a max value of 32 registers along the register array. +Normally the Register selection in ModR/M, is limited to three bits in binary 000 = 0 to 111 = 7. +RegExtend stores the two binary bits that are added onto the three bit register selection. +--------------------------------------------------------------------------------------------------------------------------- +When RegExtend is 00,000 the added lower three bits is 00,000 = 0 to 00,111 = 7. +When RegExtend is 01,000 the added lower three bits is 01,000 = 8 to 01,111 = 15. +When RegExtend is 10,000 the added lower three bits is 10,000 = 16 to 10,111 = 23. +When RegExtend is 11,000 the added lower three bits is 11,000 = 24 to 10,111 = 31. +--------------------------------------------------------------------------------------------------------------------------- +The Register expansion bits make the binary number from a 3 bit number to a 5 bit number by combining the EVEX.R'R bits. +The REX opcode, and EVEX opcode 62 hex are decoded with function ^DecodePrefixAdjustments()^ which contain R bit settings. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^DecodeRegValue()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var RegExtend = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +The base register is used in ModR/M address mode, and Register mode and can be extended to 8 using the "B bit" setting +from the REX prefix, or VEX Extension, and EVEX Extension, however in EVEX the tow bits "X, and B" are used together to +make the base register reach 32 in register value if the ModR/M is in Register mode. +--------------------------------------------------------------------------------------------------------------------------- +The highest the Base Register can be extended is from a 3 bit number to a 5 bit number. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var BaseExtend = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +The index register is used in ModR/M memory address mode if the base register is "100" bin in the ModR/M which sets SIB mode. +The Index register can be extended to 8 using the "X bit" setting when the Index register is used. +The X bit setting is used in the REX prefix settings, and also the VEX Extension, and EVEX Extension. +--------------------------------------------------------------------------------------------------------------------------- +The highest the Index Register can be extended is from a 3 bit number to a 4 bit number. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var IndexExtend = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +SegOverride is the bracket that is added onto the start of the decoded address it is designed this way so that if a segment +Override Prefix is used it is stored with the segment. +--------------------------------------------------------------------------------------------------------------------------- +used by function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var SegOverride = "["; + +/*------------------------------------------------------------------------------------------------------------------------- +This may seem confusing, but the 8 bit high low registers are used all in "low order" when any REX prefix is used. +Set RexActive true when the REX Prefix is used, for the High, and low Register separation. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^DecodeRegValue()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var RexActive = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +The SIMD value is set according to SIMD MODE by prefixes (none, 66, F2, F3), or by the value of VEX.pp, and EVEX.pp. +Changes the selected instruction in ^DecodeOpcode()^ only for SSE vector opcodes that have 4 possible instructions in +one instruction for the 4 modes otherwise 66 is Operand override, and F2 is REPNE, and F3 is REP prefix adjustments. +By reusing some of the already used Prefix adjustments more opcodes did not have to be sacrificed. +--------------------------------------------------------------------------------------------------------------------------- +SIMD is set 00 in binary by default, SIMD is set 01 in binary when opcode 66 is read by ^DecodePrefixAdjustments()^, +SIMD is set 10 in binary when opcode F2 is read by ^DecodePrefixAdjustments()^, and SIMD is set 11 in binary when F3 is read +by ^DecodePrefixAdjustments()^. +--------------------------------------------------------------------------------------------------------------------------- +The VEX, and EVEX adjustment codes contain SIMD mode adjustment bits in which each code that is used to change the mode go +in the same order as SIMD. This allows SIMD to be set directly by the VEX.pp, and EVEX.pp bit value. +--------------------------------------------------------------------------------------------------------------------------- +VEX.pp = 00b (None), 01b (66h), 10b (F2h), 11b (F3h) +EVEX.pp = 00b (None), 01b (66h), 10b (F2h), 11b (F3h) +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^DecodeOpcode()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var SIMD = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +Vect is set true during the decoding of an instruction code. If the instruction is an Vector instruction 4 in length for +the four modes then Vect is set true. When Vect is set true the Function ^Decode_ModRM_SIB_Address()^ Will decode the +ModR/M as a Vector address. +--------------------------------------------------------------------------------------------------------------------------- +Set By function ^DecodeOpcode()^, and used by function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Vect = false; + +/*------------------------------------------------------------------------------------------------------------------------- +In AVX512 The width bit can be ignored, or used. The width bit relates to the SIMD mode for size of the numbers in the vector. +Modes N/A, F3 are 32 bit, while 66, F2 are 64 bit. The width bit has to be set for the extend data size for +most AVX512 instructions unless the width bit is ignored. Some AVX512 vectors can also broadcast round to there extend data size +controlled by the width bit extend size and SIMD mode. +-------------------------------------------------------------------------------------------------------------------------*/ + +var IgnoresWidthbit = false; + +/*------------------------------------------------------------------------------------------------------------------------- +The VSIB setting is used for vectors that multiply the displacement by the Element size of the vectors, and use index as an vector pointer. +-------------------------------------------------------------------------------------------------------------------------*/ + +var VSIB = false; + +/*------------------------------------------------------------------------------------------------------------------------- +EVEX also has error suppression modes {ER} controlled by vector length, and if the broadcast round is active in register mode, +or {SAE} suppresses all exceptions then it can not change rounding mode by vector length. +MVEX also has error suppression modes {ER} controlled by conversion mode, and if the MVEX.E bit is set to round in register mode, +or {SAE} suppresses all exceptions then it can not change rounding mode by vector length. +L1OM vectors use {ER} as round control, and {SEA} as exponent adjustment. +-------------------------------------------------------------------------------------------------------------------------*/ + +var RoundingSetting = 0; //1 = SAE, and 2 = ER. + +/*------------------------------------------------------------------------------------------------------------------------- +The MVEX prefix can Integer convert, and Float convert, and Broadcast round using Swizzle. +The EVEX prefix can only Broadcast round using an "b" control which sets the Broadcast round option for Swizzle. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Swizzle = false; //Swizzle based instruction. If false then Up, or Down conversion. +var Up = false; //Only used if Swizzle is false. If set false then it is an down conversion. +var Float = false; //If False Integer data is used. +var VectS = 0x00; //Stores the three vector settings Swizzle, Up, and Float, for faster comparison to special cases. + +/*------------------------------------------------------------------------------------------------------------------------- +The Extension is set 2 during opcode 62 hex for EVEX in which the ^DecodePrefixAdjustments()^ decodes the settings, but if +the bit that must be set 0 for EVEX is set 1 then Extension is set 3 for MVEX. +The Extension is set 1 during opcodes C4, and C5 hex in which the ^DecodePrefixAdjustments()^ decodes the settings for the VEX prefixes. +--------------------------------------------------------------------------------------------------------------------------- +An instruction that has 4 opcode combinations based on SIMD can use another 4 in length separator in the select SIMD mode +which selects the opcode based on extension used. This is used to separate codes that can be Vector adjusted, and not. +Some codes can only be used in VEX, but not EVEX, and not all EVEX can be MVEX encoded as the EVEX versions were introduced after, +also MMX instruction can not be used with vector adjustments. +--------------------------------------------------------------------------------------------------------------------------- +By default Extension is 0 for decoding instructions normally. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^DecodeOpcode()^ adds the letter "V" to the instruction name to show it uses Vector adjustments. +When the Function ^DecodeOpcode()^ completes if Vect is not true and an Extension is active the instruction is invalid. +Used By function ^DecodeOperandString()^ which allows the Vector operand to be used if existent in the operand string. +-------------------------------------------------------------------------------------------------------------------------*/ + +var Extension = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +MVEX/EVEX conversion modes. MVEX can directly set the conversion mode between float, or integer, to broadcast round using option bits. +The EVEX Extension only has the broadcast rounding control. In which some instructions support "]{1to16}" (B32), or "]{1to8}" (B64) +Based on the data size using the width bit setting. EVEX only can use the 1ToX broadcast round control. +--------------------------------------------------------------------------------------------------------------------------- +Used by function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var ConversionMode = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +MVEX/EVEX rounding modes. In EVEX if the ModR/M is used in register mode and Bround Is active. +The EVEX Error Suppression type is set by the RoundingSetting {ER}, and {SAE} settings for if the instruction supports it. +The MVEX version allows the use of both rounding modes. MVEX can select the rounding type using option bits if the +"MVEX.E" control is set in an register to register operation. +--------------------------------------------------------------------------------------------------------------------------- +The function ^Decode_ModRM_SIB_Address()^ sets RoundMode. +The function DecodeInstruction() adds the error Suppression to the end of the instruction. +-------------------------------------------------------------------------------------------------------------------------*/ + +var RoundMode = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +MVEX/EVEX register round modes. +--------------------------------------------------------------------------------------------------------------------------- +Some instructions use SAE which suppresses all errors, but if an instruction uses {er} the 4 others are used by vector length. +-------------------------------------------------------------------------------------------------------------------------*/ + +const RoundModes = [ + "","","","","","","","", //First 8 No rounding mode. + /*------------------------------------------------------------------------------------------------------------------------- + MVEX/EVEX round Modes {SAE} Note MVEX (1xx) must be set 4 or higher, while EVEX uses upper 4 in rounding mode by vector length. + -------------------------------------------------------------------------------------------------------------------------*/ + ", {Error}", ", {Error}", ", {Error}", ", {Error}", ", {SAE}", ", {SAE}", ", {SAE}", ", {SAE}", + /*------------------------------------------------------------------------------------------------------------------------- + L1OM/MVEX/EVEX round modes {ER}. L1OM uses the first 4, and EVEX uses the upper 4, while MVEX can use all 8. + -------------------------------------------------------------------------------------------------------------------------*/ + ", {RN}", ", {RD}", ", {RU}", ", {RZ}", ", {RN-SAE}", ", {RD-SAE}", ", {RU-SAE}", ", {RZ-SAE}", + /*------------------------------------------------------------------------------------------------------------------------- + MVEX/EVEX round modes {SAE}, {ER} Both rounding modes can not possibly be set both at the same time. + -------------------------------------------------------------------------------------------------------------------------*/ + "0B", "4B", "5B", "8B", "16B", "24B", "31B", "32B" //L1OM exponent adjustments. +]; + +/*------------------------------------------------------------------------------------------------------------------------- +L1OM/MVEX register swizzle modes. When an swizzle operation is done register to register. +Note L1OM skips swizzle type DACB thus the last swizzle type is an repeat of the DACB as the last L1OM swizzle. +-------------------------------------------------------------------------------------------------------------------------*/ + +const RegSwizzleModes = [ "", "CDAB", "BADC", "DACB", "AAAA", "BBBB", "CCCC", "DDDD", "DACB" ]; + +/*------------------------------------------------------------------------------------------------------------------------- +EVEX does not support conversion modes. Only broadcast round of 1To16, or 1To8 controlled by the data size. +--------------------------------------------------------------------------------------------------------------------------- +MVEX.sss permits the use of conversion types by value without relating to the Swizzle conversion type. +However During Up, and Down conversion MVEX does not allow Broadcast round control. +--------------------------------------------------------------------------------------------------------------------------- +L1OM.CCCCC can only be used with Up, and Down conversion data types, and L1OM.sss can only be used with broadcast round. +L1OM.SSS can only be used with swizzle conversions. +--------------------------------------------------------------------------------------------------------------------------- +The Width bit relates to the data size of broadcast round as 32 bit it is X=16, and 64 bit number are larger and are X=8 in the "(1, or 4)ToX". +The Width bit also relates to the Up conversion, and down conversion data size. +Currently in K1OM, and L1OM there are no 64 bit Up, or Down conversions. +--------------------------------------------------------------------------------------------------------------------------- +Note 66 hex is used as data size 64 in L1OM. +--------------------------------------------------------------------------------------------------------------------------- +The element to grab from the array bellow is calculated mathematically. +Note each element is an multiple of 2 in which the first element is the 32 size, and second element is 64 size. +Lastly the elements are in order to the "CCCCC" value, and "SSS" value times 2, and plus 1 if 64 data size. +-------------------------------------------------------------------------------------------------------------------------*/ + +const ConversionModes = [ + //------------------------------------------------------------------------ + "", "", //Not used. + //------------------------------------------------------------------------ + "1To16", "1To8", //Settable as L1OM.sss/MVEX.sss = 001. Settable using EVEX broadcast round. + "4To16", "4To8", //Settable as L1OM.sss/MVEX.sss = 010. Settable using EVEX broadcast round. + //------------------------------------------------------------------------ + "Float16", "Error", //Settable as "MVEX.sss = 011", and "L1OM.sss = 110 , L1OM.CCCCC = 00001". + //------------------------------------------------------------------------ + "Float16RZ", "Error", //Settable only as L1OM.CCCCC = 00010. + //------------------------------------------------------------------------ + "SRGB8", "Error", //Settable only as L1OM.CCCCC = 00011. + /*------------------------------------------------------------------------ + MVEX/L1OM Up conversion, and down conversion types. + ------------------------------------------------------------------------*/ + "UInt8", "Error", //Settable as L1OM.sss/MVEX.sss = 100, and L1OM.CCCCC = 00100. + "SInt8", "Error", //Settable as L1OM.sss/MVEX.sss = 101, and L1OM.CCCCC = 00101. + //------------------------------------------------------------------------ + "UNorm8", "Error", //Settable as L1OM.sss = 101, or L1OM.CCCCC = 00110. + "SNorm8", "Error", //Settable as L1OM.CCCCC = 00111. + //------------------------------------------------------------------------ + "UInt16", "Error", //Settable as L1OM.sss/MVEX.sss = 110, and L1OM.CCCCC = 01000 + "SInt16", "Error", //Settable as L1OM.sss/MVEX.sss = 111, and L1OM.CCCCC = 01001 + //------------------------------------------------------------------------ + "UNorm16", "Error", //Settable as L1OM.CCCCC = 01010. + "SNorm16", "Error", //Settable as L1OM.CCCCC = 01011. + "UInt8I", "Error", //Settable as L1OM.CCCCC = 01100. + "SInt8I", "Error", //Settable as L1OM.CCCCC = 01101. + "UInt16I", "Error", //Settable as L1OM.CCCCC = 01110. + "SInt16I", "Error", //Settable as L1OM.CCCCC = 01111. + /*------------------------------------------------------------------------ + L1OM Up conversion, and field conversion. + ------------------------------------------------------------------------*/ + "UNorm10A", "Error", //Settable as L1OM.CCCCC = 10000. Also Usable as Integer Field control. + "UNorm10B", "Error", //Settable as L1OM.CCCCC = 10001. Also Usable as Integer Field control. + "UNorm10C", "Error", //Settable as L1OM.CCCCC = 10010. Also Usable as Integer Field control. + "UNorm2D", "Error", //Settable as L1OM.CCCCC = 10011. Also Usable as Integer Field control. + //------------------------------------------------------------------------ + "Float11A", "Error", //Settable as L1OM.CCCCC = 10100. Also Usable as Float Field control. + "Float11B", "Error", //Settable as L1OM.CCCCC = 10101. Also Usable as Float Field control. + "Float10C", "Error", //Settable as L1OM.CCCCC = 10110. Also Usable as Float Field control. + "Error", "Error", //Settable as L1OM.CCCCC = 10111. Also Usable as Float Field control. + /*------------------------------------------------------------------------ + Unused Conversion modes. + ------------------------------------------------------------------------*/ + "Error", "Error", //Settable as L1OM.CCCCC = 11000. + "Error", "Error", //Settable as L1OM.CCCCC = 11001. + "Error", "Error", //Settable as L1OM.CCCCC = 11010. + "Error", "Error", //Settable as L1OM.CCCCC = 11011. + "Error", "Error", //Settable as L1OM.CCCCC = 11100. + "Error", "Error", //Settable as L1OM.CCCCC = 11101. + "Error", "Error", //Settable as L1OM.CCCCC = 11110. + "Error", "Error" //Settable as L1OM.CCCCC = 11111. +]; + +/*------------------------------------------------------------------------------------------------------------------------- +The VEX Extension, and MVEX/EVEX Extension have an Vector register selection built in for Vector operation codes that use the +vector register. This operand is only read in the "operand string" if an VEX, or EVEX prefix was decoded by the +function ^DecodePrefixAdjustments()^, and making Extension 1 for VEX, or 2 for EVEX instead of 0 by default. +During a VEX, or EVEX version of the SSE instruction the vector bits are a 4 bit binary value of 0 to 15, and are extended +in EVEX and MVEX to 32 by adding the EVEX.V, or MVEX.V bit to the vector register value. +--------------------------------------------------------------------------------------------------------------------------- +Used with the function ^DecodeRegValue()^ to decode the Register value. +-------------------------------------------------------------------------------------------------------------------------*/ + +var VectorRegister = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +The MVEX/EVEX Extension has an mask Register value selection for {K0-K7} mask to destination operand. +The K mask register is always displayed to the destination operand in any Vector instruction used with MVEX/EVEX settings. +--------------------------------------------------------------------------------------------------------------------------- +The {K} is added onto the first operand in OpNum before returning the decoded operands from the function ^DecodeOperands()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var MaskRegister = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +The EVEX Extension has an zero mask bit setting for {z} zeroing off the registers. +--------------------------------------------------------------------------------------------------------------------------- +The {z} is added onto the first operand in OpNum before returning the decoded operands from the function ^DecodeOperands()^. +--------------------------------------------------------------------------------------------------------------------------- +In L1OM/MVEX this is used as the {NT}/{EH} control which when used with an memory address that supports it will prevent +the data from going into the cache memory. Used as Hint control in the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var HInt_ZeroMerg = false; + +/*------------------------------------------------------------------------------------------------------------------------- +Some operands use the value of the Immediate operand as an opcode, or upper 4 bits as Another register, or condition codes. +The Immediate is decoded normally, but this variable stores the integer value of the first IMM byte for the other byte +encodings if used. +--------------------------------------------------------------------------------------------------------------------------- +Used By the function ^DecodeOpcode()^ for condition codes, and by ^DecodeOperands()^ using the upper four bits as a register. +-------------------------------------------------------------------------------------------------------------------------*/ + +var IMMValue = 0; + +/*------------------------------------------------------------------------------------------------------------------------- +Prefix G1, and G2 are used with Intel HLE, and other prefix codes such as repeat the instruction Codes F2, F3 which can be +applied to any instruction unless it is an SIMD instruction which uses F2, and F3 as the SIMD Mode. +-------------------------------------------------------------------------------------------------------------------------*/ + +var PrefixG1 = "", PrefixG2 = ""; + +/*------------------------------------------------------------------------------------------------------------------------- +Intel HLE is used with basic arithmetic instructions like Add, and subtract, and shift operations. +Intel HLE instructions replace the Repeat F2, and F3, also lock F0 with XACQUIRE, and XRELEASE. +--------------------------------------------------------------------------------------------------------------------------- +This is used by function ^DecodeInstruction()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var XRelease = false, XAcquire = false; + +/*------------------------------------------------------------------------------------------------------------------------- +Intel HLE flip "G1 is used as = REP (XACQUIRE), or RENP (XRELEASE)", and "G2 is used as = LOCK" if the lock prefix was +not read first then G1, and G2 flip. Also XACQUIRE, and XRELEASE replace REP, and REPNE if the LOCK prefix is used with +REP, or REPNE if the instruction supports Intel HLE. +--------------------------------------------------------------------------------------------------------------------------- +This is used by function ^DecodeInstruction()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var HLEFlipG1G2 = false; + +/*------------------------------------------------------------------------------------------------------------------------- +Replaces segment overrides CS, and DS with HT, and HNT prefix for Branch taken and not taken used by jump instructions. +--------------------------------------------------------------------------------------------------------------------------- +This is used by functions ^Decode_ModRM_SIB_Address()^, and ^DecodeInstruction()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var HT = false; + +/*------------------------------------------------------------------------------------------------------------------------- +Instruction that support MPX replace the REPNE prefix with BND if operation is a MPX instruction. +--------------------------------------------------------------------------------------------------------------------------- +This is used by function ^DecodeInstruction()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +var BND = false; + +/*------------------------------------------------------------------------------------------------------------------------- +The Invalid Instruction variable is very important as some bit settings in vector extensions create invalid operation codes. +Also some opcodes are invalid in different cpu bit modes. +--------------------------------------------------------------------------------------------------------------------------- +Function ^DecodePrefixAdjustments()^ Set the Invalid Opcode if an instruction or prefix is compared that is invalid for CPU bit mode. +The function ^DecodeInstruction()^ returns an invalid instruction if Invalid Operation is used for CPU bit mode. +-------------------------------------------------------------------------------------------------------------------------*/ + +var InvalidOp = false; + +/*------------------------------------------------------------------------------------------------------------------------- +The Register array holds arrays in order from 0 though 7 for the GetOperandSize function Which goes by Prefix size settings, +and SIMD Vector length instructions using the adjusted variable SizeAttrSelect. +--------------------------------------------------------------------------------------------------------------------------- +Used by functions ^DecodeRegValue()^, ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +const REG = [ + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 0 Is used only if the value returned from the GetOperandSize is 0 in value which is the 8 bit general use + Arithmetic registers names. Note that these same registers can be made 16 bit across instead of using just the first 8 bit + in size it depends on the instruction codes extension size. + --------------------------------------------------------------------------------------------------------------------------- + The function ^GetOperandSize()^ takes the size value the instruction uses for it's register selection by looking up binary + bit positions in the size value in log 2. Different instructions can be adjusted to different sizes using the operand size + override adjustment code, or width bit to adjust instructions to 64 in size introduced by AMD64, and EM64T in 64 bit computers. + --------------------------------------------------------------------------------------------------------------------------- + REG array Index 0 is the first 8 bit's of Arithmetic registers, however they can be used in both high, and low order in + which the upper 16 bit's is used as 8 bit's for H (High part), and the first 8 bits is L (LOW part) unless the rex prefix is + used then the first 8 bit's is used by all general use arithmetic registers. Because of this the array is broken into two + name listings that is used with the "RValue" number given to the function ^DecodeRegValue()^. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + /*------------------------------------------------------------------------------------------------------------------------- + 8 bit registers without any rex prefix active is the normal low byte to high byte order of the + first 4 general use registers "A, C, D, and B" using 8 bits. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Registers 8 bit names without any rex prefix index 0 to 7. + "AL", "CL", "DL", "BL", "AH", "CH", "DH", "BH" + ], + /*------------------------------------------------------------------------------------------------------------------------- + 8 bit registers with any rex prefix active uses all 15 registers in low byte order. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Registers 8 bit names with any rex prefix index 0 to 7. + "AL", "CL", "DL", "BL", "SPL", "BPL", "SIL", "DIL", + /*------------------------------------------------------------------------------------------------------------------------- + Registers 8 bit names Extended using the REX.R extend setting in the Rex prefix, or VEX.R bit, or EVEX.R. + What ever RegExtend is set based on prefix settings is added to the select Reg Index + -------------------------------------------------------------------------------------------------------------------------*/ + "R8B", "R9B", "R10B", "R11B", "R12B", "R13B", "R14B", "R15B" + ] + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 1 Is used only if the value returned from the GetOperandSize function is 1 in value in which bellow is the + general use Arithmetic register names 16 in size. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Registers 16 bit names index 0 to 15. + "AX", "CX", "DX", "BX", "SP", "BP", "SI", "DI", "R8W", "R9W", "R10W", "R11W", "R12W", "R13W", "R14W", "R15W" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 2 Is used only if the value from the GetOperandSize function is 2 in value in which bellow is the + general use Arithmetic register names 32 in size. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Registers 32 bit names index 0 to 15. + "EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI", "R8D", "R9D", "R10D", "R11D", "R12D", "R13D", "R14D", "R15D" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 3 Is used only if the value returned from the GetOperandSize function is 3 in value in which bellow is the + general use Arithmetic register names 64 in size. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //general use Arithmetic registers 64 names index 0 to 15. + "RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 4 SIMD registers 128 across in size names. The SIMD registers are used by the SIMD Vector math unit. + Used only if the value from the GetOperandSize function is 4 in value. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Register XMM names index 0 to 15. + "XMM0", "XMM1", "XMM2", "XMM3", "XMM4", "XMM5", "XMM6", "XMM7", "XMM8", "XMM9", "XMM10", "XMM11", "XMM12", "XMM13", "XMM14", "XMM15", + /*------------------------------------------------------------------------------------------------------------------------- + Register XMM names index 16 to 31. + Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. + -------------------------------------------------------------------------------------------------------------------------*/ + "XMM16", "XMM17", "XMM18", "XMM19", "XMM20", "XMM21", "XMM22", "XMM23", "XMM24", "XMM25", "XMM26", "XMM27", "XMM28", "XMM29", "XMM30", "XMM31" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 5 SIMD registers 256 across in size names. + Used only if the value from the GetOperandSize function is 5 in value. Set by vector length setting. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Register YMM names index 0 to 15. + "YMM0", "YMM1", "YMM2", "YMM3", "YMM4", "YMM5", "YMM6", "YMM7", "YMM8", "YMM9", "YMM10", "YMM11", "YMM12", "YMM13", "YMM14", "YMM15", + /*------------------------------------------------------------------------------------------------------------------------- + Register YMM names index 16 to 31. + Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. + -------------------------------------------------------------------------------------------------------------------------*/ + "YMM16", "YMM17", "YMM18", "YMM19", "YMM20", "YMM21", "YMM22", "YMM23", "YMM24", "YMM25", "YMM26", "YMM27", "YMM28", "YMM29", "YMM30", "YMM31" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 6 SIMD registers 512 across in size names. + Used only if the value from the GetOperandSize function is 6 in value. Set by Vector length setting. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Register ZMM names index 0 to 15. + "ZMM0", "ZMM1", "ZMM2", "ZMM3", "ZMM4", "ZMM5", "ZMM6", "ZMM7", "ZMM8", "ZMM9", "ZMM10", "ZMM11", "ZMM12", "ZMM13", "ZMM14", "ZMM15", + /*------------------------------------------------------------------------------------------------------------------------- + Register ZMM names index 16 to 31. + Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. + -------------------------------------------------------------------------------------------------------------------------*/ + "ZMM16", "ZMM17", "ZMM18", "ZMM19", "ZMM20", "ZMM21", "ZMM22", "ZMM23", "ZMM24", "ZMM25", "ZMM26", "ZMM27", "ZMM28", "ZMM29", "ZMM30", "ZMM31" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 7 SIMD registers 1024 bit. The SIMD registers have not been made this long yet. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Register unknowable names index 0 to 15. + "?MM0", "?MM1", "?MM2", "?MM3", "?MM4", "?MM5", "?MM6", "?MM7", "?MM8", "?MM9", "?MM10", "?MM11", "?MM12", "?MM13", "?MM14", "?MM15", + /*------------------------------------------------------------------------------------------------------------------------- + Register unknowable names index 16 to 31. + Note different bit settings in the EVEX prefixes allow higher Extension values in the Register Extend variables. + -------------------------------------------------------------------------------------------------------------------------*/ + "?MM16", "?MM17", "?MM18", "?MM19", "?MM20", "?MM21", "?MM22", "?MM23", "?MM24", "?MM25", "?MM26", "?MM27", "?MM28", "?MM29", "?MM30", "?MM31" + ], + /*------------------------------------------------------------------------------------------------------------------------- + The Registers bellow do not change size they are completely separate, thus are used for special purposes. These registers + are selected by using size as a value for the index instead instead of giving size to the function ^GetOperandSize()^. + --------------------------------------------------------------------------------------------------------------------------- + REG array Index 8 Segment Registers. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Segment Registers names index 0 to 7 + "ES", "CS", "SS", "DS", "FS", "GS", "ST(-2)", "ST(-1)" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 9 Stack, and MM registers used by the X87 Float point unit. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //ST registers Names index 0 to 7 + //note these are used with the X87 FPU, but are aliased to MM in MMX SSE. + "ST(0)", "ST(1)", "ST(2)", "ST(3)", "ST(4)", "ST(5)", "ST(6)", "ST(7)" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG index 10 Intel MM qword technology MMX vector instructions. + --------------------------------------------------------------------------------------------------------------------------- + These can not be used with Vector length adjustment used in vector extensions. The MM register are the ST registers aliased + to MM register. Instructions that use these registers use the the SIMD vector unit registers (MM), these are called the old + MMX vector instructions. When Intel added the SSE instructions to the SIMD math vector unit the new 128 bit XMM registers, + are added into the SIMD unit then they ware made longer in size 256, then 512 across in length, with 1024 (?MM Reserved) + In which the vector length setting was added to control there size though vector setting adjustment codes. Instruction + that can be adjusted by vector length are separate from the MM registers, but still use the same SIMD unit. Because of this + some Vector instruction codes can not be used with vector extension setting codes. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //Register MM names index 0 to 7 + "MM0", "MM1", "MM2", "MM3", "MM4", "MM5", "MM6", "MM7" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG Array Index 11 bound registers introduced with MPX instructions. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //BND0 to BND3,and CR0 to CR3 for two byte opcodes 0x0F1A,and 0x0F1B register index 0 to 7 + "BND0", "BND1", "BND2", "BND3", "CR0", "CR1", "CR2", "CR3" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 12 control registers depending on the values they are set changes the modes of the CPU. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //control Registers index 0 to 15 + "CR0", "CR1", "CR2", "CR3", "CR4", "CR5", "CR6", "CR7", "CR8", "CR9", "CR10", "CR11", "CR12", "CR13", "CR14", "CR15" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 13 Debug mode registers. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //debug registers index 0 to 15 + "DR0", "DR1", "DR2", "DR3", "DR4", "DR5", "DR6", "DR7", "DR8", "DR9", "DR10", "DR11", "DR12", "DR13", "DR14", "DR15" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG array Index 14 test registers. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //TR registers index 0 to 7 + "TR0", "TR1", "TR2", "TR3", "TR4", "TR5", "TR6", "TR7" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG Array Index 15 SIMD vector mask registers. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + //K registers index 0 to 7, because of vector extensions it is repeated till last extension. + "K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7","K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7", + "K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7","K0", "K1", "K2", "K3", "K4", "K5", "K6", "K7" + ], + /*------------------------------------------------------------------------------------------------------------------------- + REG Array Index 16 SIMD L1OM vector registers. + -------------------------------------------------------------------------------------------------------------------------*/ + [ + "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10", "V11", "V12", "V13", "V14", "V15", + "V16", "V17", "V18", "V19", "V20", "V21", "V22", "V23", "V24", "V25", "V26", "V27", "V28", "V29", "V30", "V31" + ] +]; + +/*------------------------------------------------------------------------------------------------------------------------- +RAM Pointer sizes are controlled by the GetOperandSize function which uses the Size Setting attributes for +the select pointer in the PTR array alignment. The REG array above uses the same alignment to the returned +size attribute except address pointers have far address pointers which are 16 bits plus there (8, or 16)/32/64 size attribute. +--------------------------------------------------------------------------------------------------------------------------- +Far pointers add 16 bits to the default pointer sizes. +16 bits become 16+16=32 DWORD, 32 bits becomes 32+16=48 FWORD, and 64+16=80 TBYTE. +The function GetOperandSize goes 0=8 bit, 1=16 bit, 2=32 bit, 3=64 bit, 4=128, 5=256, 6=512, 7=1024. +--------------------------------------------------------------------------------------------------------------------------- +The pointers are stored in doubles this is so every second position is each size setting. +So the Returned size attribute has to be in multiples of 2 each size multiplied by 2 looks like this. +(0*2=0)=8 bit, (1*2=2)=16 bit, (2*2=4)=32 bit, (3*2=6)=64 bit, (4*2=8)=128, (5*2=10)=256, (6*2=12)=512. +This is the same as moving by 2 this is why each pointer is in groups of two before the next line. +When the 16 bit shift is used for far pointers only plus one is added for the 16 bit shifted name of the pointer. +--------------------------------------------------------------------------------------------------------------------------- +Used by the function ^Decode_ModRM_SIB_Address()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +const PTR = [ + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 0 when GetOperandSize returns size 0 then times 2 for 8 bit pointer. + In plus 16 bit shift array index 0 is added by 1 making 0+1=1 no pointer name is used. + The blank pointer is used for instructions like LEA which loads the effective address. + -------------------------------------------------------------------------------------------------------------------------*/ + "BYTE PTR ","", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 2 when GetOperandSize returns size 1 then times 2 for 16 bit pointer alignment. + In plus 16 bit shift index 2 is added by 1 making 2+1=3 The 32 bit pointer name is used (mathematically 16+16=32). + -------------------------------------------------------------------------------------------------------------------------*/ + "WORD PTR ","DWORD PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 4 when GetOperandSize returns size 2 then multiply by 2 for index 4 for the 32 bit pointer. + In plus 16 bit shift index 4 is added by 1 making 4+1=5 the 48 bit Far pointer name is used (mathematically 32+16=48). + -------------------------------------------------------------------------------------------------------------------------*/ + "DWORD PTR ","FWORD PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 6 when GetOperandSize returns size 3 then multiply by 2 gives index 6 for the 64 bit pointer. + The Non shifted 64 bit pointer has two types the 64 bit vector "MM", and regular "QWORD" the same as the REG array. + In plus 16 bit shift index 6 is added by 1 making 6+1=7 the 80 bit TBYTE pointer name is used (mathematically 64+16=80). + -------------------------------------------------------------------------------------------------------------------------*/ + "QWORD PTR ","TBYTE PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 8 when GetOperandSize returns size 4 then multiply by 2 gives index 8 for the 128 bit Vector pointer. + In far pointer shift the MMX vector pointer is used. + MM is designed to be used when the by size system is false using index 9 for Pointer, and index 10 for Reg. + -------------------------------------------------------------------------------------------------------------------------*/ + "XMMWORD PTR ","MMWORD PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 10 when GetOperandSize returns size 5 then multiply by 2 gives index 10 for the 256 bit SIMD pointer. + In far pointer shift the OWORD pointer is used with the bounds instructions it is also designed to be used when the by size is set false same as MM. + -------------------------------------------------------------------------------------------------------------------------*/ + "YMMWORD PTR ","OWORD PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 12 when GetOperandSize returns size 6 then multiply by 2 gives index 12 for the 512 bit pointer. + In plus 16 bit shift index 12 is added by 1 making 12+1=13 there is no 528 bit pointer name (mathematically 5126+16=528). + -------------------------------------------------------------------------------------------------------------------------*/ + "ZMMWORD PTR ","ERROR PTR ", + /*------------------------------------------------------------------------------------------------------------------------- + Pointer array index 14 when GetOperandSize returns size 7 then multiply by 2 gives index 12 for the 1024 bit pointer. + In plus 16 bit shift index 14 is added by 1 making 12+1=13 there is no 1 bit pointer name (mathematically 5126+16=528). + -------------------------------------------------------------------------------------------------------------------------*/ + "?MMWORD PTR ","ERROR PTR "]; + +/*------------------------------------------------------------------------------------------------------------------------- +SIB byte scale Note the Scale bits value is the selected index of the array bellow only used under +a Memory address that uses the SIB Address mode which uses another byte for the address selection. +--------------------------------------------------------------------------------------------------------------------------- +used by the ^Decode_ModRM_SIB_Address function()^. +-------------------------------------------------------------------------------------------------------------------------*/ + +const scale = [ + "", //when scale bits are 0 in value no scale multiple is used + "*2", //when scale bits are 1 in value a scale multiple of times two is used + "*4", //when scale bits are 2 in value a scale multiple of times four is used + "*8" //when scale bits are 3 in value a scale multiple of times eight is used + ]; + +/*------------------------------------------------------------------------------------------------------------------------- +This function changes the Mnemonics array, for older instruction codes used by specific X86 cores that are under the same instruction codes. +--------------------------------------------------------------------------------------------------------------------------- +Input "type" can be any number 0 to 6. If the input is 0 it sets the mnemonics back to normal. +If input "type" is set 1 it will adjust the few conflicting mask instructions to the K1OM instruction names used by the knights corner processor. +If input "type" is set 2 it will adjust the mnemonic array to decode Larrabee instructions. +If input "type" is set 3 it will adjust the mnemonic array to decode Cyrix instructions which are now deprecated from the architecture. +If input "type" is set 4 it will adjust the mnemonic array to decode Geode instructions which are now deprecated from the architecture. +If input "type" is set 5 it will adjust the mnemonic array to decode Centaur instructions which are now deprecated from the architecture. +If input "type" is set 6 it will adjust the mnemonic array to decode instruction for the X86/486 CPU which conflict with the vector unit instructions with UMOV. +-------------------------------------------------------------------------------------------------------------------------*/ + +export function CompatibilityMode( type ) +{ + //Reset the changeable sections of the Mnemonics array, and operand encoding array. + + Mnemonics[0x062] = ["BOUND","BOUND",""]; + Mnemonics[0x110] = [["MOVUPS","MOVUPD","MOVSS","MOVSD"],["MOVUPS","MOVUPD","MOVSS","MOVSD"]]; + Mnemonics[0x111] = [["MOVUPS","MOVUPD","MOVSS","MOVSD"],["MOVUPS","MOVUPD","MOVSS","MOVSD"]]; + Mnemonics[0x112] = [["MOVLPS","MOVLPD","MOVSLDUP","MOVDDUP"],["MOVHLPS","???","MOVSLDUP","MOVDDUP"]]; + Mnemonics[0x113] = [["MOVLPS","MOVLPD","???","???"],"???"]; + Mnemonics[0x138] = ""; Mnemonics[0x139] = "???"; Mnemonics[0x13A] = ""; Mnemonics[0x13B] = "???"; Mnemonics[0x13C] = "???"; Mnemonics[0x13D] = "???"; Mnemonics[0x13F] = "???"; + Mnemonics[0x141] = [["CMOVNO",["KANDW","","KANDQ"],"",""],["CMOVNO",["KANDB","","KANDD"],"",""],"",""]; + Mnemonics[0x142] = [["CMOVB",["KANDNW","","KANDNQ"],"",""],["CMOVB",["KANDNB","","KANDND"],"",""],"",""]; + Mnemonics[0x144] = [["CMOVE",["KNOTW","","KNOTQ"],"",""],["CMOVE",["KNOTB","","KNOTD"],"",""],"",""]; + Mnemonics[0x145] = [["CMOVNE",["KORW","","KORQ"],"",""],["CMOVNE",["KORB","","KORD"],"",""],"",""]; + Mnemonics[0x146] = [["CMOVBE",["KXNORW","","KXNORQ"],"",""],["CMOVBE",["KXNORB","","KXNORD"],"",""],"",""]; + Mnemonics[0x147] = [["CMOVA",["KXORW","","KXORQ"],"",""],["CMOVA",["KXORB","","KXORD"],"",""],"",""]; + Mnemonics[0x150] = ["???",[["MOVMSKPS","MOVMSKPS","",""],["MOVMSKPD","MOVMSKPD","",""],"???","???"]]; + Mnemonics[0x151] = ["SQRTPS","SQRTPD","SQRTSS","SQRTSD"]; + Mnemonics[0x152] = [["RSQRTPS","RSQRTPS","",""],"???",["RSQRTSS","RSQRTSS","",""],"???"]; + Mnemonics[0x154] = ["ANDPS","ANDPD","???","???"]; + Mnemonics[0x155] = ["ANDNPS","ANDNPD","???","???"]; + Mnemonics[0x158] = [["ADDPS","ADDPS","ADDPS","ADDPS"],["ADDPD","ADDPD","ADDPD","ADDPD"],"ADDSS","ADDSD"]; + Mnemonics[0x159] = [["MULPS","MULPS","MULPS","MULPS"],["MULPD","MULPD","MULPD","MULPD"],"MULSS","MULSD"]; + Mnemonics[0x15A] = [["CVTPS2PD","CVTPS2PD","CVTPS2PD","CVTPS2PD"],["CVTPD2PS","CVTPD2PS","CVTPD2PS","CVTPD2PS"],"CVTSS2SD","CVTSD2SS"]; + Mnemonics[0x15B] = [[["CVTDQ2PS","","CVTQQ2PS"],"CVTPS2DQ",""],"???","CVTTPS2DQ","???"]; + Mnemonics[0x15C] = [["SUBPS","SUBPS","SUBPS","SUBPS"],["SUBPD","SUBPD","SUBPD","SUBPD"],"SUBSS","SUBSD"]; + Mnemonics[0x15D] = ["MINPS","MINPD","MINSS","MINSD"]; + Mnemonics[0x15E] = ["DIVPS","DIVPD","DIVSS","DIVSD"]; + Mnemonics[0x178] = [["VMREAD","",["CVTTPS2UDQ","","CVTTPD2UDQ"],""],["EXTRQ","",["CVTTPS2UQQ","","CVTTPD2UQQ"],""],["???","","CVTTSS2USI",""],["INSERTQ","","CVTTSD2USI",""]]; + Mnemonics[0x179] = [["VMWRITE","",["CVTPS2UDQ","","CVTPD2UDQ"],""],["EXTRQ","",["CVTPS2UQQ","","CVTPD2UQQ"],""],["???","","CVTSS2USI",""],["INSERTQ","","CVTSD2USI",""]]; + Mnemonics[0x17A] = ["???",["","",["CVTTPS2QQ","","CVTTPD2QQ"],""],["","",["CVTUDQ2PD","","CVTUQQ2PD"],"CVTUDQ2PD"],["","",["CVTUDQ2PS","","CVTUQQ2PS"],""]]; + Mnemonics[0x17B] = ["???",["","",["CVTPS2QQ","","CVTPD2QQ"],""],["","","CVTUSI2SS",""],["","","CVTUSI2SD",""]]; + Mnemonics[0x17C] = ["???",["HADDPD","HADDPD","",""],"???",["HADDPS","HADDPS","",""]]; + Mnemonics[0x17D] = ["???",["HSUBPD","HSUBPD","",""],"???",["HSUBPS","HSUBPS","",""]]; + Mnemonics[0x17E] = [["MOVD","","",""],["MOVD","","MOVQ"],["MOVQ","MOVQ",["???","","MOVQ"],""],"???"], + Mnemonics[0x190] = [["SETO",["KMOVW","","KMOVQ"],"",""],["SETO",["KMOVB","","KMOVD"],"",""],"",""]; + Mnemonics[0x192] = [["SETB",["KMOVW","","???"],"",""],["SETB",["KMOVB","","???"],"",""],"",["SETB",["KMOVD","","KMOVQ"],"",""]]; + Mnemonics[0x193] = [["SETAE",["KMOVW","","???"],"",""],["SETAE",["KMOVB","","???"],"",""],"",["SETAE",["KMOVD","","KMOVQ"],"",""]]; + Mnemonics[0x198] = [["SETS",["KORTESTW","","KORTESTQ"],"",""],["SETS",["KORTESTB","","KORTESTD"],"",""],"",""]; + Mnemonics[0x1A6] = "XBTS"; + Mnemonics[0x1A7] = "IBTS"; + + Operands[0x110] = [["0B700770","0B700770","0A040603","0A040609"],["0B700770","0B700770","0A0412040604","0A0412040604"]]; + Operands[0x111] = [["07700B70","07700B70","06030A04","06090A04"],["07700B70","07700B70","060412040A04","060412040A04"]]; + Operands[0x112] = [["0A0412040606","0A0412040606","0B700770","0B700768"],["0A0412040604","","0B700770","0B700770"]]; + Operands[0x113] = [["06060A04","06060A04","",""],""]; + Operands[0x141] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; + Operands[0x142] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; + Operands[0x144] = [["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""],["0B0E070E0180",["0A0F06FF","","0A0F06FF"],"",""],"",""]; + Operands[0x145] = [["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0A02070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; + Operands[0x146] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],"",""]; + Operands[0x147] = [["0B0E070E0180",["0A0F120F06FF","","0A0F120F06FF"],"",""],["0B0E070E0180",["0A0F120F06FF","",""],"",""],"",""]; + Operands[0x150] = ["",[["0B0C0648","0B0C0730","",""],["0B0C0648","0B0C0730","",""],"",""]]; + Operands[0x151] = ["0B7007700112","0B7007700112","0A04120406430102","0A04120406490102"]; + Operands[0x152] = [["0A040648","0A040648","",""],"",["0A040643","0A0412040643","",""],""]; + Operands[0x154] = ["0B70137007700110","0B70137007700110","",""]; + Operands[0x155] = ["0B70137007700110","0B70137007700110","",""]; + Operands[0x158] = [["0A040648","0B3013300730","0B70137007700112","0A061206066C0172"],["0A040648","0B3013300730","0B70137007700112","0A061206066C0112"],"0A04120406430102","0A04120406460102"]; + Operands[0x159] = [["0A040648","0B3013300730","0B70137007700112","0A061206066C0172"],["0A040648","0B3013300730","0B70137007700112","0A061206066C0112"],"0A04120406430102","0A04120406460102"]; + Operands[0x15A] = [["0A040648","0B300718","0B7007380111","0A06065A0111"],["0A040648","0B180730","0B3807700112","0A05066C0112"],"0A04120406430101","0A04120406460102"]; + Operands[0x15B] = [[["0B7007700112","","0B380770011A"],"0B700770011A","",""],"","0B7007700111",""]; + Operands[0x15C] = [["0A060648","0B3013300730","0B70137007700112","0A061206066C0172"],["0A060648","0B3013300730","0B70137007700112","0A061206066C0112"],"0A04120406430102","0A04120406460102"]; + Operands[0x15D] = ["0B70137007700111","0B70137007700111","0A04120406430101","0A04120406460101"]; + Operands[0x15E] = ["0B70137007700112","0B70137007700112","0A04120406430102","0A04120406460102"]; + Operands[0x178] = [["07080B080180","",["0B7007700111","","0B3807700119"],""],["064F0C000C00","",["0B7007380119","","0B7007700111"],""],["","","0B0C06440109",""],["0A04064F0C000C00","","0B0C06460109",""]]; + Operands[0x179] = [["0B0807080180","",["0B7007700112","","0B380770011A"],""],["0A04064F","",["0B700738011A","","0B7007700112"],""],["","","0B0C0644010A",""],["0A04064F","","0B0C0646010A",""]]; + Operands[0x17A] = ["",["","",["0B7007380119","","0B7007700111"],""],["","",["0B7007380112","","0B700770011A"],"0A06065A0112"],["","",["0B700770011A","","0B3807700112"],""]]; + Operands[0x17B] = ["",["","",["0B700738011A","","0B7007700112"],""],["","","0A041204070C010A",""],["","","0A041204070C010A",""]]; + Operands[0x17C] = ["",["0A040604","0B7013700770","",""],"",["0A040604","0B7013700770","",""]]; + Operands[0x17D] = ["",["0A040604","0B7013700770","",""],"",["0A040604","0B7013700770","",""]]; + Operands[0x17E] = [["070C0A0A","","",""],["06240A040108","","06360A040108"],["0A040646","0A040646",["","","0A0406460108"],""],""]; + Operands[0x190] = [["0600",["0A0F0612","","0A0F0636"],"",""],["0600",["0A0F0600","","0A0F0624"],"",""],"",""]; + Operands[0x192] = [["0600",["0A0F06F4","",""],"",""],["0600",["0A0F06F4","",""],"",""],"",["0600",["0A0F06F6","","0A0F06F6"],"",""]]; + Operands[0x193] = [["0600",["06F40A0F","",""],"",""],["0600",["06F40A0F","",""],"",""],"",["0600",["06F60A0F","","06F60A0F"],"",""]]; + Operands[0x198] = [["0600",["0A0F06FF","","0A0F06FF"],"",""],["0600",["0A0F06FF","","0A0F06FF"],"",""],"",""]; + Operands[0x1A6] = "0B0E070E"; + Operands[0x1A7] = "070E0B0E"; + + //Adjust the VEX mask instructions for K1OM (Knights corner) which conflict with the enhanced AVX512 versions. + + if( type === 1 ) + { + Mnemonics[0x141] = [["CMOVNO","KAND","",""],"","",""]; + Mnemonics[0x142] = [["CMOVB","KANDN","",""],"","",""]; + Mnemonics[0x144] = [["CMOVE","KNOT","",""],"","",""]; + Mnemonics[0x145] = [["CMOVNE","KOR","",""],"","",""]; + Mnemonics[0x146] = [["CMOVBE","KXNOR","",""],"","",""]; + Mnemonics[0x147] = [["CMOVA","KXOR","",""],"","",""]; + Mnemonics[0x190] = [["SETO","KMOV","",""],"","",""]; + Mnemonics[0x192] = [["SETB","KMOV","",""],"","",""]; + Mnemonics[0x193] = [["SETAE","KMOV","",""],"","",""]; + Mnemonics[0x198] = [["SETS","KORTEST","",""],"","",""]; + Operands[0x141] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; + Operands[0x142] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; + Operands[0x144] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; + Operands[0x145] = [["0A02070E0180","0A0F06FF","",""],"","",""]; + Operands[0x146] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; + Operands[0x147] = [["0B0E070E0180","0A0F06FF","",""],"","",""]; + Operands[0x190] = [["0600","0A0F06FF","",""],"","",""]; + Operands[0x192] = [["0600","06FF0B06","",""],"","",""]; + Operands[0x193] = [["0600","07060A0F","",""],"","",""]; + Operands[0x198] = [["0600","0A0F06FF","",""],"","",""]; + } + + //Disable Knights corner, and AVX512, for L1OM (Intel Larrabee). + + if( type === 2 ) + { + Mnemonics[0x62] = ""; + } + + //Adjust the Mnemonics, and Operand encoding, for the Cyrix processors. + + if( type === 3 ) + { + Mnemonics[0x138] = "SMINT"; Mnemonics[0x13A] = "BB0_RESET"; Mnemonics[0x13B] = "BB1_RESET"; Mnemonics[0x13C] = "CPU_WRITE"; Mnemonics[0x13D] = "CPU_READ"; + Mnemonics[0x150] = "PAVEB"; Mnemonics[0x151] = "PADDSIW"; Mnemonics[0x152] = "PMAGW"; + Mnemonics[0x154] = "PDISTIB"; Mnemonics[0x155] = "PSUBSIW"; + Mnemonics[0x158] = "PMVZB"; Mnemonics[0x159] = "PMULHRW"; Mnemonics[0x15A] = "PMVNZB"; + Mnemonics[0x15B] = "PMVLZB"; Mnemonics[0x15C] = "PMVGEZB"; Mnemonics[0x15D] = "PMULHRIW"; + Mnemonics[0x15E] = "PMACHRIW"; + Mnemonics[0x178] = "SVDC"; Mnemonics[0x179] = "RSDC"; Mnemonics[0x17A] = "SVLDT"; + Mnemonics[0x17B] = "RSLDT"; Mnemonics[0x17C] = "SVTS"; Mnemonics[0x17D] = "RSTS"; + Mnemonics[0x17E] = "SMINT"; + Operands[0x150] = "0A0A06A9"; Operands[0x151] = "0A0A06A9"; Mnemonics[0x152] = "0A0A06A9"; + Operands[0x154] = "0A0A06AF"; Operands[0x155] = "0A0A06A9"; + Operands[0x158] = "0A0A06AF"; Operands[0x159] = "0A0A06A9"; Mnemonics[0x15A] = "0A0A06AF"; + Operands[0x15B] = "0A0A06AF"; Operands[0x15C] = "0A0A06AF"; Mnemonics[0x15D] = "0A0A06A9"; + Operands[0x15E] = "0A0A06AF"; + Operands[0x178] = "30000A08"; Operands[0x179] = "0A083000"; Operands[0x17A] = "3000"; + Operands[0x17B] = "3000"; Operands[0x17C] = "3000"; Operands[0x17D] = "3000"; + Operands[0x17E] = ""; + } + + //Adjust the Mnemonics, and Operand encoding, for the Geode processor. + + if( type === 4 ) + { + Mnemonics[0x138] = "SMINT"; Mnemonics[0x139] = "DMINT"; Mnemonics[0x13A] = "RDM"; + } + + //Adjust the Mnemonics, for the Centaur processor. + + if( type === 5 ) + { + Mnemonics[0x13F] = "ALTINST"; + Mnemonics[0x1A6] = ["???",["MONTMUL","XSA1","XSA256","???","???","???","???","???"]]; + Mnemonics[0x1A7] = [ + "???", + [ + "XSTORE", + ["???","???","XCRYPT-ECB","???"], + ["???","???","XCRYPT-CBC","???"], + ["???","???","XCRYPT-CTR","???"], + ["???","???","XCRYPT-CFB","???"], + ["???","???","XCRYPT-OFB","???"], + "???", + "???" + ] + ]; + Operands[0x1A6] = ["",["","","","","","","",""]]; + Operands[0x1A7] = [ + "", + [ + "", + ["","","",""], + ["","","",""], + ["","","",""], + ["","","",""], + ["","","",""], + "", + "" + ] + ]; + } + + //Adjust the Mnemonics, for the X86/486 processor and older. + + if( type === 6 ) + { + Mnemonics[0x110] = "UMOV"; Mnemonics[0x111] = "UMOV"; Mnemonics[0x112] = "UMOV"; Mnemonics[0x113] = "UMOV"; + Mnemonics[0x1A6] = "CMPXCHG"; Mnemonics[0x1A7] = "CMPXCHG"; + Operands[0x110] = "06000A00"; Operands[0x111] = "070E0B0E"; Operands[0x112] = "0A000600"; Operands[0x113] = "0B0E070E"; + Operands[0x1A6] = ""; Operands[0x1A7] = ""; + } + +} + +/*------------------------------------------------------------------------------------------------------------------------- +This function loads the BinCode array using an hex string as input, and Resets the Code position along the array, but does not +reset the base address. This allows programs to be decoded in sections well maintaining the accurate 64 bit base address. +--------------------------------------------------------------------------------------------------------------------------- +The function "SetBasePosition()" sets the location that the Code is from in memory. +The function "GotoPosition()" tests if the address is within rage of the current loaded binary. +The function "GetPosition()" Gives back the current base address in it's proper format for the current BitMode. +--------------------------------------------------------------------------------------------------------------------------- +If the hex input is invalid returns false. +-------------------------------------------------------------------------------------------------------------------------*/ + +export function LoadBinCode( HexStr ) +{ + //Clear BinCode, and Reset Code Position in Bin Code array. + + BinCode = []; CodePos = 0; + + //Iterate though the hex string and covert to 0 to 255 byte values into the BinCode array. + + var len = HexStr.length; + + for( var i = 0, el = 0, Sing = 0, int32 = 0; i < len; i += 8 ) + { + //It is faster to read 8 hex digits at a time if possible. + + int32 = parseInt( HexStr.slice( i, i + 8 ), 16 ); + + //If input is invalid return false. + + if( isNaN( int32 ) ){ return ( false ); } + + //If the end of the Hex string is reached and is not 8 digits the number has to be lined up. + + ( ( len - i ) < 8 ) && ( int32 <<= ( 8 - len - i ) << 2 ); + + //The variable sing corrects the unusable sing bits during the 4 byte rotation algorithm. + + Sing = int32; + + //Remove the Sing bit value if active for when the number is changed to int32 during rotation. + + int32 ^= int32 & 0x80000000; + + //Rotate the 32 bit int so that each number is put in order in the BinCode array. Add the Sing Bit positions back though each rotation. + + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( ( Sing >> 24 ) & 0x80 ) | int32 ) & 0xFF; + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( ( Sing >> 16 ) & 0x80 ) | int32 ) & 0xFF; + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( ( Sing >> 8 ) & 0x80 ) | int32 ) & 0xFF; + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( Sing & 0x80 ) | int32 ) & 0xFF; + } + + //Remove elements past the Number of bytes in HexStr because int 32 is always 4 bytes it is possible to end in an uneven number. + + len >>= 1; + + for(; len < BinCode.length; BinCode.pop() ); + + //Return true for that the binary code loaded properly. + + return ( true ); +} + +/*------------------------------------------------------------------------------------------------------------------------- +This function moves the address by one and caries to 64 section for the Base address. The BitMode settings limit how much of +the 64 bit address is used in functions "GetPosition()", and "GotoPosition()", for the type of binary being disassemble. +This function also moves the binary code array position CodePos by one basically this function is used to progress the +disassembler as it is decoding a sequence of bytes. +-------------------------------------------------------------------------------------------------------------------------*/ + +function NextByte() +{ + //Add the current byte as hex to InstructionHex which will be displayed beside the decoded instruction. + //After an instruction decodes InstructionHex is only added beside the instruction if ShowInstructionHex is active. + + if ( CodePos < BinCode.length ) //If not out of bounds. + { + //Convert current byte to String, and pad. + var t; + + ( ( t = BinCode[CodePos++].toString(16) ).length === 1) && ( t = "0" + t ); + + //Add it to the current bytes used for the decode instruction. + + InstructionHex += t; + + //Continue the Base address. + + ( ( Pos32 += 1 ) > 0xFFFFFFFF ) && ( Pos32 = 0, ( ( Pos64 += 1 ) > 0xFFFFFFFF ) && ( Pos64 = 0 ) ); + } +} + +/*------------------------------------------------------------------------------------------------------------------------- +Takes a 64/32/16 bit hex string and sets it as the address position depending on the address format it is split into an +segment, and offset address. Note that the Code Segment is used in 16 bit code. Also code segment is also used in 32 bit +if set 36, or higher. Effects instruction location in memory when decoding a program. +-------------------------------------------------------------------------------------------------------------------------*/ + +export function SetBasePosition( Address ) +{ + //Split the Segment:offset. + + var t = Address.split(":"); + + //Set the 16 bit code segment position if there is one. + + if ( typeof t[1] !== "undefined" ){ CodeSeg = parseInt( t[0].slice( t[0].length - 4 ), 16 ); Address = t[1]; } + + //Adjust the Instruction pointer 16(IP)/32(EIP)/64(RIP). Also varies based on Bit Mode. + + var Len = Address.length; + + if( Len >= 9 && BitMode == 2 ){ Pos64 = parseInt( Address.slice( Len - 16, Len - 8 ), 16 ); } + if( Len >= 5 && BitMode >= 1 && !( BitMode == 1 & CodeSeg >= 36 ) ){ Pos32 = parseInt( Address.slice( Len - 8 ), 16 ); } + else if( Len >= 1 && BitMode >= 0 ){ Pos32 = ( Pos32 & 0xFFFF0000 ) | ( parseInt( Address.slice( Len - 4 ), 16 ) ); } + + //Convert Pos32 to undignified integer. + + if ( Pos32 < 0 ) { Pos32 += 0x100000000; } +} + +/*------------------------------------------------------------------------------------------------------------------------- +Gives back the current Instruction address position. +In 16 bit an instruction location is Bound to the code segment location in memory, and the first 16 bit of the instruction pointer 0 to 65535. +In 32 bit an instruction location uses the first 32 bit's of the instruction pointer. +-------------------------------------------------------------------------------------------------------------------------*/ + +function GetPosition() +{ + //If 16 bit Seg:Offset, or if 32 bit and CodeSeg is 36, or higher. + + if( BitMode === 0 | ( BitMode === 1 & CodeSeg >= 36 ) ) + { + for ( var S16 = ( Pos32 & 0xFFFF ).toString(16); S16.length < 4; S16 = "0" + S16 ); + for ( var Seg = ( CodeSeg ).toString(16); Seg.length < 4; Seg = "0" + Seg ); + return( ( Seg + ":" + S16 ).toUpperCase() ); + } + + //32 bit, and 64 bit section. + + var S64="", S32=""; + + //If 32 bit or higher. + + if( BitMode >= 1 ) + { + for ( S32 = Pos32.toString(16); S32.length < 8; S32 = "0" + S32 ); + } + + //If 64 bit. + + if( BitMode === 2 ) + { + for ( S64 = Pos64.toString(16); S64.length < 8; S64 = "0" + S64 ); + } + + //Return the 32/64 address. + + return ( ( S64 + S32 ).toUpperCase() ); +} + +/*------------------------------------------------------------------------------------------------------------------------- +Moves the dissembler 64 bit address, and CodePos to correct address. Returns false if address location is out of bounds. +-------------------------------------------------------------------------------------------------------------------------*/ + +function GotoPosition( Address ) +{ + //Current address location. + + var LocPos32 = Pos32; + var LocPos64 = Pos64; + var LocCodeSeg = CodeSeg; + + //Split the by Segment:offset address format. + + var t = Address.split(":"); + + //Set the 16 bit code segment location if there is one. + + if ( typeof t[1] !== "undefined" ) + { + LocCodeSeg = parseInt(t[0].slice( t[0].length - 4 ), 16); + Address = t[1]; + } + + var len = Address.length; + + //If the address is 64 bit's long, and bit mode is 64 bit adjust the 64 bit location. + + if( len >= 9 && BitMode === 2 ) + { + LocPos64 = parseInt( Address.slice( len - 16, len - 8 ), 16 ); + } + + //If the address is 32 bit's long, and bit mode is 32 bit, or higher adjust the 32 bit location. + + if( len >= 5 && BitMode >= 1 & !( BitMode === 1 & CodeSeg >= 36 ) ) + { + LocPos32 = parseInt( Address.slice( len - 8 ), 16 ); + } + + //Else If the address is 16 bit's long, and bit mode is 16 bit, or higher adjust the first 16 bit's in location 32. + + else if( len >= 1 && BitMode >= 0 ) + { + LocPos32 = ( LocPos32 - LocPos32 + parseInt( Address.slice( len - 4 ), 16 ) ); + } + + //Find the difference between the current base address and the selected address location. + + var Dif32 = Pos32 - LocPos32, Dif64 = Pos64 - LocPos64; + + //Only calculate the Code Segment location if The program uses 16 bit address mode otherwise the + //code segment does not affect the address location. + + if( ( BitMode === 1 & CodeSeg >= 36 ) || BitMode === 0 ) + { + Dif32 += ( CodeSeg - LocCodeSeg ) << 4; + } + + //Before adjusting the Code Position Backup the Code Position in case that the address is out of bounds. + + t = CodePos; + + //Subtract the difference to the CodePos position. + + CodePos -= Dif64 * 4294967296 + Dif32; + + //If code position is out of bound for the loaded binary in the BinCode array, or + //is a negative index return false and reset CodePos. + + if( CodePos < 0 || CodePos > BinCode.length ) + { + CodePos = t; return ( false ); + } + + //Set the base address so that it matches the Selected address location that Code position is moved to in relative space in the BinCode Array. + + CodeSeg = LocCodeSeg; + Pos32 = LocPos32 + Pos64 = LocPos64; + + //Return true for that the address Position is moved in range correctly. + + return ( true ); +} + +/*------------------------------------------------------------------------------------------------------------------------- +Finds bit positions to the Size attribute indexes in REG array, and the Pointer Array. For the Size Attribute variations. +--------------------------------------------------------------------------------------------------------------------------- +The SizeAttribute settings is 8 digits big consisting of 1, or 0 to specify the the extended size that an operand can be made. +In which an value of 01100100 is decoded as "0 = 1024, 1 = 512, 1 = 256, 0 = 128, 0 = 64, 1 = 32, 0 = 16, 0 = 8". +In which the largest bit position is 512, and is the 6th number "0 = 7, 1 = 6, 1 = 5, 0 = 4, 0 = 3, 1 = 2, 0 = 1, 0 = 0". +In which 6 is the bit position for 512 as the returned Size . Each size is in order from 0 to 7, thus the size given back +from this function Lines up With the Pinter array, and Register array indexes for the register names by size, and Pointers. +--------------------------------------------------------------------------------------------------------------------------- +The variable SizeAttrSelect is separate from this function it is adjusted by prefixes that adjust Vector size, and General purpose registers. +-------------------------------------------------------------------------------------------------------------------------*/ + +function GetOperandSize( SizeAttribute ) +{ + /*---------------------------------------------------------------------------------------------------------------------------------------- + Each S value goes in order to the vector length value in EVEX, and VEX Smallest to biggest in perfect alignment. + SizeAttrSelect is set 1 by default, unless it is set 0 to 3 by the vector length bit's in the EVEX prefix, or 0 to 1 in the VEX prefix. + In which if it is not an Vector instruction S2 acts as the mid default size attribute in 32 bit mode, and 64 bit mode for all instructions. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + var S4 = 0, S3 = 0, S2 = 0, S1 = 0, S0 = -1; //Note S0 is Vector size 1024, which is unused. + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Lookup the Highest active bit in the SizeAttribute value giving the position the bit is in the number. S1 will be the biggest size attribute. + In which this size attribute is only used when the extended size is active from the Rex prefix using the W (width) bit setting. + In which sets variable SizeAttrSelect to 2 in value when the Width bit prefix setting is decoded, or if it is an Vector this is the + Max vector size 512 in which when the EVEX.L'L bit's are set 10 = 2 sets SizeAttrSelect 2, note 11 = 3 is reserved for vectors 1024 in size. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + S1 = SizeAttribute; S1 = ( ( S1 & 0xF0 ) !== 0 ? ( S1 >>= 4, 4 ) : 0 ) | ( ( S1 & 0xC ) !== 0 ? ( S1 >>= 2, 2 ) : 0 ) | ( ( S1 >>= 1 ) !== 0 ); + + /*---------------------------------------------------------------------------------------------------------------------------------------- + If there is no size attributes then set S1 to -1 then the rest are set to S1 as they should have no size setting. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + if( SizeAttribute === 0 ) { S1 = -1; } + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Convert the Bit Position of S1 into it's value and remove it by subtracting it into the SizeAttribute settings. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + SizeAttribute -= ( 1 << S1 ); + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Lookup the Highest Second active bit in the SizeAttribute value giving the position the bit is in the number. + In which S2 will be the default size attribute when SizeAttrSelect is 1 and has not been changed by prefixes, or If this is an vector + SizeAttrSelect is set one by the EVEX.L'L bit's 01 = 1, or VEX.L is active 1 = 1 in which the Mid vector size is used. + In which 256 is the Mid vector size some vectors are smaller some go 64/128/256 in which the mid size is 128. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + S2 = SizeAttribute; S2 = ( ( S2 & 0xF0 ) !== 0 ? ( S2 >>= 4, 4 ) : 0 ) | ( ( S2 & 0xC ) !== 0 ? ( S2 >>= 2, 2 ) : 0 ) | ( ( S2 >>= 1 ) !== 0 ); + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Convert the Bit Position of S2 into it's value and remove it by subtracting it if it is not 0. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + if( S2 !== 0 ) { SizeAttribute -= ( 1 << S2 ); } + + /*---------------------------------------------------------------------------------------------------------------------------------------- + If it is 0 The highest size attribute is set as the default operand size. So S2 is aliased to S1, if there is no other Size setting attributes. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + else { S2 = S1; } + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Lookup the Highest third active bit in the SizeAttribute value giving the position the bit is in the number. + The third Size is only used if the Operand override prefix is used setting SizeAttrSelect to 0, or if this is an vector the + EVEX.L'L bit's are 00 = 0 sets SizeAttrSelect 0, or VEX.L = 0 in which SizeAttrSelect is 0 using the smallest vector size. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + S3 = SizeAttribute; S3 = ( ( S3 & 0xF0 ) !== 0 ? ( S3 >>= 4, 4 ) : 0 ) | ( ( S3 & 0xC ) !== 0 ? ( S3 >>= 2, 2 ) : 0 ) | ( ( S3 >>= 1 ) !== 0 ); + + /*---------------------------------------------------------------------------------------------------------------------------------------- + Convert the Bit Position of S3 into it's value and remove it by subtracting it if it is not 0. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + if( S3 !== 0 ) { SizeAttribute -= ( 1 << S3 ); } + + /*---------------------------------------------------------------------------------------------------------------------------------------- + If it is 0 The second size attribute is set as the operand size. So S3 is aliased to S2, if there is no other Size setting attributes. + ----------------------------------------------------------------------------------------------------------------------------------------*/ + + else { S3 = S2; if( S2 !== 2 ) { S2 = S1; } }; + + //In 32/16 bit mode the operand size must never exceed 32. + + if ( BitMode <= 1 && S2 >= 3 && !Vect ) + { + if( ( S1 | S2 | S3 ) === S3 ){ S1 = 2; S3 = 2; } //If single size all adjust 32. + S2 = 2; //Default operand size 32. + } + + //In 16 bit mode The operand override is always active until used. This makes all operands 16 bit size. + //When Operand override is used it is the default 32 size. Flip S3 with S2. + + if( BitMode === 0 && !Vect ) { var t = S3; S3 = S2; S2 = t; } + + //If an Vect is active, then EVEX.W, VEX.W, or XOP.W bit acts as 32/64. + + if( ( Vect || Extension > 0 ) && ( ( S1 + S2 + S3 ) === 7 | ( S1 + S2 + S3 ) === 5 ) ) { Vect = false; return( ( [ S2, S1 ] )[ WidthBit & 1 ] ); } + + //If it is an vector, and Bround is active vector goes max size. + + if( Vect && ConversionMode === 1 ) + { + S0 = S1; S3 = S1; S2 = S1; + } + + //Note the fourth size that is -1 in the returned size attribute is Vector length 11=3 which is invalid unless Intel decides to add 1024 bit vectors. + //The only time S0 is not negative one is if vector broadcast round is active. + + return( ( [ S3, S2, S1, S0 ] )[ SizeAttrSelect ] ); + +} + +/*------------------------------------------------------------------------------------------------------------------------- +This function returns an array with three numbers. +--------------------------------------------------------------------------------------------------------------------------- +The first element is the two bits for the ModR/M byte for Register mode, Memory mode, and Displacement settings, or the SIB byte +scale as a number value 0 to 3 if it is not an ModR/M byte since they both use the same bit grouping. +The second element is the three bits for the ModR/M byte Opcode/Reg bits, or the SIB Index Register value as a number value 0 to 7. +The third element is the last three bits for the ModR/M byte the R/M bits, or the SIB Base Register value as a number value 0 to 7. +-------------------------------------------------------------------------------------------------------------------------*/ + +function Decode_ModRM_SIB_Value() +{ + //Get the current position byte value + + var v = BinCode[CodePos]; + + //The first tow binary digits of the read byte is the Mode bits of the ModR/M byte or + //The first tow binary digits of the byte is the Scale bits of the SIB byte. + + var ModeScale = (v >> 6) & 0x03; //value 0 to 3 + + //The three binary digits of the read byte after the first two digits is the OpcodeReg Value of the ModR/M byte or + //The three binary digits of the read byte after the first two digits is the Index Register value for the SIB byte. + + var OpcodeRegIndex = (v >> 3) & 0x07; //value 0 to 7 + + //The three binary digits at the end of the read byte is the R/M (Register,or Memory) Value of the ModR/M byte or + //The three binary digits at the end of the read byte is the Base Register Value of the SIB byte. + + var RMBase = v & 0x07; //value 0 to 7 + + //Put the array together containing the three indexes with the value + //Note both the ModR/M byte and SIB byte use the same bit value pattern + + var ByteValueArray = [ + ModeScale,//Index 0 is the first tow bits for the Mode, or Scale Depending on what the byte value is used for. + OpcodeRegIndex,//Index 1 is the three bits for the OpcodeReg, or Index Depending on what the byte value is used for. + RMBase //Index 2 is the three bits for the RM, or BASE bits Depending on what the byte value is used for. + ]; + + //Move the Decoders Position by one. + + NextByte(); + + //return the array containing the decoded values of the byte. + + return (ByteValueArray); + +} + +/*------------------------------------------------------------------------------------------------------------------------- +When input type is value 0 decode the immediate input regularly to it's size setting for accumulator Arithmetic, and IO. +When input type is value 1 decode the immediate input regularly, but zeros out the upper 4 bits for Register encoding. +When input type is value 2 decode the immediate as a relative address used by jumps, and function calls. +When input type is value 3 decode the immediate as a Integer Used by Displacements. +--------------------------------------------------------------------------------------------------------------------------- +The function argument SizeSetting is the size attributes of the IMM that is decoded using the GetOperandSize function. +The Imm uses two size setting, the first 4 bits are used for the Immediate actual adjustable sizes 8,16,32,64. +--------------------------------------------------------------------------------------------------------------------------- +If BySize is false the SizeSetting is used numerically as a single size selection as +0=8,1=16,2=32,3=64 by size setting value. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeImmediate( type, BySize, SizeSetting ) +{ + + /*------------------------------------------------------------------------------------------------------------------------- + Initialize V32, and V64 which will store the Immediate value. + JavaScript Float64 numbers can not accurately work with numbers 64 bit's long. + So numbers are split into two numbers that should never exceed an 32 bit value though calculation. + Numbers that are too big for the first 32 bit's are stored as the next 32 bit's in V64. + -------------------------------------------------------------------------------------------------------------------------*/ + + var V32 = 0, V64 = 0; + + //*Initialize the Pad Size for V32, and V64 depending On the Immediate type Calculation they use. + + var Pad32 = 0, Pad64 = 0; + + //*Initialize the Sing value that is only set for Negative, or Positive Relative displacements. + + var Sing = 0; + + //*Initialize the Sing Extend variable size as 0 Some Immediate numbers Sing extend. + + var Extend = 0; + + //*The variable S is the size of the Immediate. + + var S = SizeSetting & 0x0F; + + //*Extend size. + + Extend = SizeSetting >> 4; + + //*If by Size attributes is set true. + + if ( BySize ) + { + S = GetOperandSize( S ); + + if ( Extend > 0 ) + { + Extend = GetOperandSize( Extend ); + } + } + + /*------------------------------------------------------------------------------------------------------------------------- + The possible values of S (Calculated Size) are S=0 is IMM8, S=1 is IMM16, S=2 is IMM32, S=3 is IMM64. + Calculate how many bytes that are going to have to be read based on the value of S. + S=0 is 1 byte, S=1 is 2 bytes, S=2 is 4 bytes, S=3 is 8 bytes. + The Number of bytes to read is 2 to the power of S. + -------------------------------------------------------------------------------------------------------------------------*/ + + var n = 1 << S; + + //Adjust Pad32, and Pad64. + + Pad32 = Math.min( n, 4 ); ( n >= 8 ) && ( Pad64 = 8 ); + + //Store the first byte of the immediate because IMM8 can use different encodings. + + IMMValue = BinCode[CodePos]; + + //*Loop and Move the Decoder to the next byte Code position to the number of bytes to read for V32, and V64. + + for ( var i = 0, v = 1; i < Pad32; V32 += BinCode[CodePos] * v, i++, v *= 256, NextByte() ); + for ( v = 1; i < Pad64; V64 += BinCode[CodePos] * v, i++, v *= 256, NextByte() ); + + //*Adjust Pad32 so it matches the length the Immediate should be in hex for number of bytes read. + + Pad32 <<= 1; Pad64 <<= 1; + + /*--------------------------------------------------------------------------------------------------------------------------- + If the IMM type is used with an register operand on the upper four bit's then the IMM byte does not use the upper 4 bit's. + ---------------------------------------------------------------------------------------------------------------------------*/ + + if( type === 1 ) { V32 &= ( 1 << ( ( n << 3 ) - 4 ) ) - 1; } + + /*--------------------------------------------------------------------------------------------------------------------------- + If the Immediate is an relative address calculation. + ---------------------------------------------------------------------------------------------------------------------------*/ + + if ( type === 2 ) + { + //Calculate the Padded size for at the end of the function an Relative is padded to the size of the address based on bit mode. + + Pad32 = ( Math.min( BitMode, 1 ) << 2 ) + 4; Pad64 = Math.max( Math.min( BitMode, 2 ), 1 ) << 3; + + //Add the 32 bit section to V32. + + var C64 = 0; V32 += Pos32; + + //If bit mode is 16 bits only the first 16 bits are used, or if Size Attribute is 16 bit. + + ( BitMode <= 0 || SizeAttrSelect <= 0 ) && ( V32 &= 0xFFFF ); + + //Adjust the 32 bit relative address section if it was not cropped to 16 bit's. + + ( C64 = ( ( V32 ) > 0xFFFFFFFF ) ) && ( V32 -= 0x100000000 ); + + //Add the 64 bit address section if in 64 bit mode, or higher. + + ( BitMode >= 2 ) && ( ( V64 += Pos64 + C64 ) > 0xFFFFFFFF ) && ( V64 -= 0x100000000 ); + } + + /*--------------------------------------------------------------------------------------------------------------------------- + If the Immediate is an displacement calculation. + ---------------------------------------------------------------------------------------------------------------------------*/ + + if ( type === 3 ) + { + /*------------------------------------------------------------------------------------------------------------------------- + Calculate the displacement center point based on Immediate size. + -------------------------------------------------------------------------------------------------------------------------*/ + + //An displacement can not be bigger than 32 bit's, so Pad64 is set 0. + + Pad64 = 0; + + //Now calculate the Center Point. + + var Center = 2 * ( 1 << ( n << 3 ) - 2 ); + + //By default the Sing is Positive. + + Sing = 1; + + /*------------------------------------------------------------------------------------------------------------------------- + Calculate the VSIB displacement size if it is a VSIB Disp8. + -------------------------------------------------------------------------------------------------------------------------*/ + + if ( VSIB && S === 0 ) + { + var VScale = WidthBit | 2; + Center <<= VScale; V32 <<= VScale; + } + + //When the value is higher than the center it is negative. + + if ( V32 >= Center ) + { + //Convert the number to the negative side of the center point. + + V32 = Center * 2 - V32; + + //The Sing is negative. + + Sing = 2; + } + } + + /*--------------------------------------------------------------------------------------------------------------------------- + Pad Imm based on the calculated Immediate size, because when an value is converted to an number as text that can be displayed + the 0 digits to the left are removed. Think of this as like the number 000179 the actual length of the number is 6 digits, + but is displayed as 179, because the unused digits are not displayed, but they still exist in the memory. + ---------------------------------------------------------------------------------------------------------------------------*/ + + for( var Imm = V32.toString(16), L = Pad32; Imm.length < L; Imm = "0" + Imm ); + if( Pad64 > 8 ) { for( Imm = V64.toString(16) + Imm, L = Pad64; Imm.length < L; Imm = "0" + Imm ); } + + /*--------------------------------------------------------------------------------------------------------------------------- + Extend Imm if it's extend size is bigger than the Current Imm size. + ---------------------------------------------------------------------------------------------------------------------------*/ + + if ( Extend !== S ) + { + //Calculate number of bytes to Extend till by size. + + Extend = Math.pow( 2, Extend ) * 2; + + //Setup the Signified pad value. + + var spd = "00"; ( ( ( parseInt( Imm.substring(0, 1), 16) & 8 ) >> 3 ) ) && ( spd = "FF" ); + + //Start padding. + + for (; Imm.length < Extend; Imm = spd + Imm); + } + + //*Return the Imm. + + return ( ( Sing > 0 ? ( Sing > 1 ? "-" : "+" ) : "" ) + Imm.toUpperCase() ); + +} + +/*------------------------------------------------------------------------------------------------------------------------- +Decode registers by Size attributes, or a select register index. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeRegValue( RValue, BySize, Setting ) +{ + //If the instruction is a Vector instruction, and no extension is active like EVEX, VEX Make sure Size attribute uses the default vector size. + + if( Vect && Extension === 0 ) + { + SizeAttrSelect = 0; + } + + //If By size is true Use the Setting with the GetOperandSize + + if ( BySize ) + { + Setting = GetOperandSize( Setting ); //get decoded size value. + + //Any Vector register smaller than 128 has to XMM because XMM is the smallest SIMD Vector register. + + if( Vect && Setting < 4 ) { Setting = 4; } + } + + //If XOP only vector 0 to 15 are usable. + + if( Opcode >= 0x400 ) { RValue &= 15; } + + //Else If 16/32 bit mode in VEX/EVEX/MVEX vctor register can only go 0 though 7. + + else if( BitMode <= 1 && Extension >= 1 ) { RValue &= 7; } + + //If L1OM ZMM to V reg. + + if ( Opcode >= 0x700 && Setting === 6 ) + { + Setting = 16; + } + + //Else if 8 bit high/low Registers + + else if ( Setting === 0 ) + { + return (REG[0][RexActive][ RValue ]); + } + + //Return the Register. + + return (REG[Setting][ RValue ]); +} + +/*------------------------------------------------------------------------------------------------------------------------- +Decode the ModR/M pointer, and Optional SIB if used. +Note if by size attributes is false the lower four bits is the selected Memory pointer, +and the higher four bits is the selected register. +-------------------------------------------------------------------------------------------------------------------------*/ + +function Decode_ModRM_SIB_Address( ModRM, BySize, Setting ) +{ + var out = ""; //the variable out is what stores the decoded address pointer, or Register if Register mode. + var S_C = "{"; //L1OM, and K1OM {SSS,CCCCC} setting decoding, or EVEX broadcast round. + + //------------------------------------------------------------------------------------------------------------------------- + //If the ModR/M is not in register mode decode it as an Effective address. + //------------------------------------------------------------------------------------------------------------------------- + + if( ModRM[0] !== 3 ) + { + + //If the instruction is a Vector instruction, and no extension is active like EVEX, VEX Make sure Size attribute uses the default vector size. + + if( Vect && Extension === 0 ) + { + SizeAttrSelect = 0; + } + + //------------------------------------------------------------------------------------------------------------------------- + //The Selected Size is setting unless BySize attribute is true. + //------------------------------------------------------------------------------------------------------------------------- + + if ( BySize ) + { + //------------------------------------------------------------------------------------------------------------------------- + //Check if it is not the non vectorized 128 which uses "Oword ptr". + //------------------------------------------------------------------------------------------------------------------------- + + if ( Setting !== 16 || Vect ) + { + Setting = ( GetOperandSize( Setting ) << 1 ) | FarPointer; + } + + //------------------------------------------------------------------------------------------------------------------------- + //Non vectorized 128 uses "Oword ptr" alaises to "QWord ptr" in 32 bit mode, or lower. + //------------------------------------------------------------------------------------------------------------------------- + + else if ( !Vect ) { Setting = 11 - ( ( BitMode <= 1 ) * 5 ); } + } + + //------------------------------------------------------------------------------------------------------------------------- + //If By size attributes is false the selected Memory pointer is the first four bits of the size setting for all pointer indexes 0 to 15. + //Also if By size attribute is also true the selected by size index can not exceed 15 anyways which is the max combination the first four bits. + //------------------------------------------------------------------------------------------------------------------------- + + Setting = Setting & 0x0F; + + //If Vector extended then MM is changed to QWORD. + + if( Extension !== 0 && Setting === 9 ){ Setting = 6; } + + //Bround control, or 32/64 VSIB. + + if ( ConversionMode === 1 || ConversionMode === 2 || VSIB ) { out += PTR[WidthBit > 0 ? 6 : 4]; } + + //------------------------------------------------------------------------------------------------------------------------- + //Get the pointer size by Size setting. + //------------------------------------------------------------------------------------------------------------------------- + + else{ out = PTR[Setting]; } + + //Add the Segment override left address bracket if any segment override was used otherwise the SegOverride string should be just a normal left bracket. + + out += SegOverride; + + //------------------------------------------------------------------------------------------------------------------------- + //calculate the actual address size according to the Address override and the CPU bit mode. + //------------------------------------------------------------------------------------------------------------------------- + //AddressSize 1 is 16, AddressSize 2 is 32, AddressSize 3 is 64. + //The Bit mode is the address size except AddressOverride reacts differently in different bit modes. + //In 16 bit AddressOverride switches to the 32 bit ModR/M effective address system. + //In both 32/64 the Address size goes down by one is size. + //------------------------------------------------------------------------------------------------------------------------- + + var AddressSize = BitMode + 1; + + if (AddressOverride) + { + AddressSize = AddressSize - 1; + + //the only time the address size is 0 is if the BitMode is 16 bit's and is subtracted by one resulting in 0. + + if(AddressSize === 0) + { + AddressSize = 2; //set the address size to 32 bit from the 16 bit address mode. + } + } + + /*------------------------------------------------------------------------------------------------------------------------- + The displacement size calculation. + --------------------------------------------------------------------------------------------------------------------------- + In 16/32/64 the mode setting 1 will always add a Displacement of 8 to the address. + In 16 the Mode setting 2 adds a displacement of 16 to the address. + In 32/64 the Mode Setting 2 for the effective address adds an displacement of 32 to the effective address. + -------------------------------------------------------------------------------------------------------------------------*/ + + var Disp = ModRM[0] - 1; //Let disp relate size to mode value of the ModR/M. + + //if 32 bit and above, and if Mode is 2 then disp size is disp32. + + if(AddressSize >= 2 && ModRM[0] === 2) + { + Disp += 1; //Only one more higher in size is 32. + } + + /*------------------------------------------------------------------------------------------------------------------------- + End of calculation. + -------------------------------------------------------------------------------------------------------------------------*/ + /*------------------------------------------------------------------------------------------------------------------------- + Normally the displacement type is an relative Immediate that is added ("+"), + or subtracted ("-") from the center point to the selected base register, + and the size depends on mode settings 1, and 2, and also Address bit mode (Displacement calculation). + Because the normal ModR/M format was limited to Relative addresses, and unfixed locations, + so some modes, and registers combinations where used for different Immediate displacements. + -------------------------------------------------------------------------------------------------------------------------*/ + + var DispType = 3; //by default the displacement size is added to the selected base register, or Index register if SIB byte combination is used. + + //-------------------------------------------16 Bit ModR/M address decode logic------------------------------------------- + + if( AddressSize === 1 ) + { + + //if ModR/M mode bits 0, and Base Register value is 6 then disp16 with DispType mode 0. + + if(AddressSize === 1 && ModRM[0] === 0 && ModRM[2] === 6) + { + Disp = 1; + DispType = 0; + } + + //BX , BP switch based on bit 2 of the Register value + + if( ModRM[2] < 4 ){ out += REG[ AddressSize ][ 3 + ( ModRM[2] & 2 ) ] + "+"; } + + //The first bit switches between Destination index, and source index + + if( ModRM[2] < 6 ){ out += REG[ AddressSize ][ 6 + ( ModRM[2] & 1 ) ]; } + + //[BP], and [BX] as long as Mode is not 0, and Register is not 6 which sets DispType 0. + + else if ( DispType !== 0 ) { out += REG[ AddressSize ][ 17 - ( ModRM[2] << 1 ) ]; } + } //End of 16 bit ModR/M decode logic. + + //-------------------------------------------Else 32/64 ModR/M------------------------------------------- + + else + { + + //if Mode is 0 and Base Register value is 5 then it uses an Relative (RIP) disp32. + + if( ModRM[0] === 0 && ModRM[2] === 5 ) + { + Disp = 2; + DispType = 2; + } + + //check if Base Register is 4 which goes into the SIB address system + + if( ModRM[2] === 4 ) + { + //Decode the SIB byte. + + var SIB = Decode_ModRM_SIB_Value(); + + //Calculate the Index register with it's Extended value because the index register will only cancel out if 4 in value. + + var IndexReg = IndexExtend | SIB[1]; + + //check if the base register is 5 in value in the SIB without it's added extended value, and that the ModR/M Mode is 0 this activates Disp32 + + if ( ModRM[0] === 0 && SIB[2] === 5 && !VSIB ) + { + Disp = 2; //Set Disp32 + + //check if the Index register is canceled out as well + + if (IndexReg === 4) //if the Index is canceled out then + { + DispType = 0; //a regular IMM32 is used as the address. + + //*if the Address size is 64 then the 32 bit Immediate must pad to the full 64 bit address. + + if( AddressSize === 3 ) { Disp = 50; } + } + } + + //Else Base register is not 5, and the Mode is not 0 then decode the base register normally. + + else + { + out += REG[ AddressSize ][ BaseExtend & 8 | SIB[2] ]; + + //If the Index Register is not Canceled out (Note this is only reachable if base register was decoded and not canceled out) + + if ( IndexReg !== 4 || VSIB ) + { + out = out + "+"; //Then add the Plus in front of the Base register to add the index register + } + } + + //if Index Register is not Canceled, and that it is not an Vector register then decode the Index with the possibility of the base register. + + if ( IndexReg !== 4 && !VSIB ) + { + out += REG[ AddressSize ][ IndexExtend | IndexReg ]; + + //add what the scale bits decode to the Index register by the value of the scale bits which select the name from the scale array. + + out = out + scale[SIB[0]]; + } + + //Else if it is an vector register. + + else if ( VSIB ) + { + Setting = ( Setting < 8 ) ? 4 : Setting >> 1; + + if( Opcode < 0x700 ) { IndexReg |= ( VectorRegister & 0x10 ); } + + out += DecodeRegValue( IndexExtend | IndexReg, false, Setting ); //Decode Vector register by length setting and the V' extension. + + //add what the scale bits decode to the Index register by the value of the scale bits which select the name from the scale array. + + out = out + scale[SIB[0]]; + } + } //END OF THE SIB BYTE ADDRESS DECODE. + + //else Base register is not 4 and does not go into the SIB ADDRESS. + //Decode the Base register regularly plus it's Extended value if relative (RIP) disp32 is not used. + + else if(DispType !== 2) + { + out += REG[ AddressSize ][ BaseExtend & 8 | ModRM[2] ]; + } + } + + + //Finally the Immediate displacement is put into the Address last. + + if( Disp >= 0 ) { out += DecodeImmediate( DispType, false, Disp ); } + + //Put the right bracket on the address. + + out += "]"; + + //----------------------L1OM/MVEX/EVEX memory conversion mode, or broadcast round----------------------- + + if( + ( ConversionMode !== 0 ) && //Not used if set 0. + !( + ( ConversionMode === 3 && ( Opcode >= 0x700 || !( Opcode >= 0x700 ) && !Float ) ) || //If bad L1OM/K1OM float conversion. + ( !( Opcode >= 0x700 ) && ( VectS === 0 || ( ConversionMode === 5 && VectS === 5 ) || //If K1OM UNorm conversion L1OM only. + ( ConversionMode !== 1 && VectS === 1 ) ^ ( ConversionMode < 3 && !Swizzle ) ) ) //Or K1OM broadcast Swizzle, and special case {4to16} only. + ) + ) + { + //Calculate Conversion. + + if( ConversionMode >= 4 ){ ConversionMode += 2; } + if( ConversionMode >= 8 ){ ConversionMode += 2; } + + //If L1OM. + + if( Opcode >= 0x700 ) + { + //If L1OM without Swizzle. + + if ( !Swizzle && ConversionMode > 2 ) { ConversionMode = 31; } + + //L1OM Float adjust. + + else if( Float ) + { + if( ConversionMode === 7 ) { ConversionMode++; } + if( ConversionMode === 10 ) { ConversionMode = 3; } + } + } + + //Set conversion. Note K1OM special case inverts width bit. + + out += S_C + ConversionModes[ ( ConversionMode << 1 ) | ( WidthBit ^ ( !( Opcode >= 0x700 ) & VectS === 7 ) ) & 1 ]; S_C = ","; + } + + //Else bad Conversion setting. + + else if( ConversionMode !== 0 ) { out += S_C + "Error"; S_C = ","; } + + //--------------------------------END of memory Conversion control logic-------------------------------- + + } //End of Memory address Modes 00, 01, 10 decode. + + //-----------------------------else the ModR/M mode bits are 11 register Mode----------------------------- + + else + { + //------------------------------------------------------------------------------------------------------------------------- + //The Selected Size is setting unless BySize attribute is true. + //------------------------------------------------------------------------------------------------------------------------- + + //MVEX/EVEX round mode. + + if ( ( Extension === 3 && HInt_ZeroMerg ) || ( Extension === 2 && ConversionMode === 1 ) ) + { + RoundMode |= RoundingSetting; + } + + //If the upper 4 bits are defined and by size is false then the upper four bits is the selected register. + + if( ( ( Setting & 0xF0 ) > 0 ) && !BySize ) { Setting >>= 4; } + + //Decode the register with Base expansion. + + out = DecodeRegValue( BaseExtend | ModRM[2], BySize, Setting ); + + //L1OM/K1OM Register swizzle modes. + + if( Opcode >= 0x700 || ( Extension === 3 && !HInt_ZeroMerg && Swizzle ) ) + { + if( Opcode >= 0x700 && ConversionMode >= 3 ){ ConversionMode++; } //L1OM skips swizzle type DACB. + if( ConversionMode !== 0 ){ out += S_C + RegSwizzleModes[ ConversionMode ]; S_C = ","; } + } + if( Extension !== 2 ){ HInt_ZeroMerg = false; } //Cache memory control is not possible in Register mode. + } + + //--------------------------------------------------L1OM.CCCCC conversions------------------------------------------------- + + if( Opcode >= 0x700 ) + { + //Swizzle Field control Int/Float, or Exponent adjustment. + + if(Swizzle) + { + if( Opcode === 0x79A ) { out += S_C + ConversionModes[ ( 18 | ( VectorRegister & 3 ) ) << 1 ]; S_C = "}"; } + else if( Opcode === 0x79B ) { out += S_C + ConversionModes[ ( 22 + ( VectorRegister & 3 ) ) << 1 ]; S_C = "}"; } + else if( ( RoundingSetting & 8 ) === 8 ) { out += S_C + RoundModes [ 24 | ( VectorRegister & 7 ) ]; S_C = "}"; } + } + + //Up/Down Conversion. + + else if( VectorRegister !== 0 ) + { + if( ( ( Up && VectorRegister !== 2 ) || //Up conversion "float16rz" is bad. + ( !Up && VectorRegister !== 3 && VectorRegister <= 15 ) ) //Down conversion "srgb8", and field control is bad. + ) { out += S_C + ConversionModes[ ( ( VectorRegister + 2 ) << 1 ) | WidthBit ]; S_C = "}"; } + else { out += S_C + "Error"; S_C = "}"; } //Else Invalid setting. + } + } + if ( S_C === "," ) { S_C = "}"; } + + //Right bracket if any SSS,CCCCC conversion mode setting. + + if( S_C === "}" ) { out += S_C; } + + //------------------------------------------L1OM/K1OM Hint cache memory control-------------------------------------------- + + if( HInt_ZeroMerg ) + { + if ( Extension === 3 ) { out += "{EH}"; } + else if ( Opcode >= 0x700 ) { out += "{NT}"; } + } + + //-------------------------------------------Return the Register/Memory address-------------------------------------------- + + return (out); +} + +/*------------------------------------------------------------------------------------------------------------------------- +Decode Prefix Mnemonic codes. Prefixes are instruction codes that do not do an operation instead adjust +controls in the CPU to be applied to an select instruction code that is not an Prefix instruction. +--------------------------------------------------------------------------------------------------------------------------- +At the end of this function "Opcode" should not hold any prefix code, so then Opcode contains an operation code. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodePrefixAdjustments() +{ + //------------------------------------------------------------------------------------------------------------------------- + Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //Add 8 bit opcode while bits 9, and 10 are used for opcode map. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //if 0F hex start at 256 for Opcode. + + if(Opcode === 0x0F) + { + Opcode = 0x100; //By starting at 0x100 with binary bit 9 set one then adding the 8 bit opcode then Opcode goes 256 to 511. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //if 38 hex while using two byte opcode. + + else if(Opcode === 0x138 && Mnemonics[0x138] === "") + { + Opcode = 0x200; //By starting at 0x200 with binary bit 10 set one then adding the 8 bit opcode then Opcode goes 512 to 767. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //if 3A hex while using two byte opcode go three byte opcodes. + + else if(Opcode === 0x13A && Mnemonics[0x13A] === "") + { + Opcode = 0x300; //By starting at 0x300 hex and adding the 8 bit opcode then Opcode goes 768 to 1023. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //Rex prefix decodes only in 64 bit mode. + + if( Opcode >= 0x40 & Opcode <= 0x4F && BitMode === 2 ) + { + RexActive = 1; //Set Rex active uses 8 bit registers in lower order as 0 to 15. + BaseExtend = ( Opcode & 0x01 ) << 3; //Base Register extend setting. + IndexExtend = ( Opcode & 0x02 ) << 2; //Index Register extend setting. + RegExtend = ( Opcode & 0x04 ) << 1; //Register Extend Setting. + WidthBit = ( Opcode & 0x08 ) >> 3; //Set The Width Bit setting if active. + SizeAttrSelect = WidthBit ? 2 : 1; //The width Bit open all 64 bits. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //The VEX2 Operation code Extension to SSE settings decoding. + + if( Opcode === 0xC5 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) ) + { + Extension = 1; + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read VEX2 byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //some bits are inverted, so uninvert them arithmetically. + + Opcode ^= 0xF8; + + //Decode bit settings. + + if( BitMode === 2 ) + { + RegExtend = ( Opcode & 0x80 ) >> 4; //Register Extend. + VectorRegister = ( Opcode & 0x78 ) >> 3; //The added in Vector register to SSE. + } + + SizeAttrSelect = ( Opcode & 0x04 ) >> 2; //The L bit for 256 vector size. + SIMD = Opcode & 0x03; //The SIMD mode. + + //Automatically uses the two byte opcode map starts at 256 goes to 511. + + Opcode = 0x100; + + //------------------------------------------------------------------------------------------------------------------------- + Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the opcode. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //Stop decoding prefixes. + + return(null); + } + + //The VEX3 prefix settings decoding. + + if( Opcode === 0xC4 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) ) + { + Extension = 1; + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read VEX3 byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + Opcode |= ( BinCode[CodePos] << 8 ); //Read next VEX3 byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //Some bits are inverted, so uninvert them arithmetically. + + Opcode ^= 0x78E0; + + //Decode bit settings. + + if( BitMode === 2 ) + { + RegExtend = ( Opcode & 0x0080 ) >> 4; //Extend Register Setting. + IndexExtend = ( Opcode & 0x0040 ) >> 3; //Extend Index register setting. + BaseExtend = ( Opcode & 0x0020 ) >> 2; //Extend base Register setting. + } + + WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit works as a separator. + VectorRegister = ( Opcode & 0x7800 ) >> 11; //The added in Vector register to SSE. + SizeAttrSelect = ( Opcode & 0x0400 ) >> 10; //Vector length for 256 setting. + SIMD = ( Opcode & 0x0300 ) >> 8; //The SIMD mode. + Opcode = ( Opcode & 0x001F ) << 8; //Change Operation code map. + + //------------------------------------------------------------------------------------------------------------------------- + Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map bit's. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + return(null); + } + + //The AMD XOP prefix. + + if( Opcode === 0x8F ) + { + //If XOP + + var Code = BinCode[ CodePos ] & 0x0F; + + if( Code >= 8 && Code <= 10 ) + { + Extension = 1; + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read XOP byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + Opcode |= ( BinCode[CodePos] << 8 ); //Read next XOP byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //Some bits are inverted, so uninvert them arithmetically. + + Opcode ^= 0x78E0; + + //Decode bit settings. + + RegExtend = ( Opcode & 0x0080 ) >> 4; //Extend Register Setting. + IndexExtend = ( Opcode & 0x0040 ) >> 3; //Extend Index register setting. + BaseExtend = ( Opcode & 0x0020 ) >> 2; //Extend base Register setting. + WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit works as a separator. + VectorRegister = ( Opcode & 0x7800 ) >> 11; //The added in Vector register to SSE. + SizeAttrSelect = ( Opcode & 0x0400 ) >> 10; //Vector length for 256 setting. + SIMD = ( Opcode & 0x0300 ) >> 8; //The SIMD mode. + if( SIMD > 0 ) { InvalidOp = true; } //If SIMD MODE is set anything other than 0 the instruction is invalid. + Opcode = 0x400 | ( ( Opcode & 0x0003 ) << 8 ); //Change Operation code map. + + //------------------------------------------------------------------------------------------------------------------------- + Opcode = ( Opcode & 0x700 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map bit's. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + return(null); + } + } + + //The L1OM vector prefix settings decoding. + + if( Opcode === 0xD6 ) + { + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read L1OM byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + Opcode |= ( BinCode[CodePos] << 8 ); //Read next L1OM byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + WidthBit = SIMD & 1; + VectorRegister = ( Opcode & 0xF800 ) >> 11; + RoundMode = VectorRegister >> 3; + MaskRegister = ( Opcode & 0x0700 ) >> 8; + HInt_ZeroMerg = ( Opcode & 0x0080 ) >> 7; + ConversionMode = ( Opcode & 0x0070 ) >> 4; + RegExtend = ( Opcode & 0x000C ) << 1; + BaseExtend = ( Opcode & 0x0003 ) << 3; + IndexExtend = ( Opcode & 0x0002 ) << 2; + + //------------------------------------------------------------------------------------------------------------------------- + Opcode = 0x700 | BinCode[CodePos]; //read the 8 bit opcode. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + return(null); + } + + //Only decode L1OM instead of MVEX/EVEX if L1OM compatibility mode is set. + + if( Mnemonics[0x62] === "" && Opcode === 0x62 ) + { + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read L1OM byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + Opcode ^= 0xF0; + + IndexExtend = ( Opcode & 0x80 ) >> 4; + BaseExtend = ( Opcode & 0x40 ) >> 3; + RegExtend = ( Opcode & 0x20 ) >> 2; + + if ( SIMD !== 1 ) { SizeAttrSelect = ( ( Opcode & 0x10 ) === 0x10 ) ? 2 : 1; } else { SIMD = 0; } + + Opcode = 0x800 | ( ( Opcode & 0x30 ) >> 4 ) | ( ( Opcode & 0x0F ) << 2 ); + + return(null); + } + + //The MVEX/EVEX prefix settings decoding. + + if ( Opcode === 0x62 && ( BinCode[CodePos] >= 0xC0 || BitMode === 2 ) ) + { + Extension = 2; + //------------------------------------------------------------------------------------------------------------------------- + Opcode = BinCode[CodePos]; //read MVEX/EVEX byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + Opcode |= ( BinCode[CodePos] << 8 ); //read next MVEX/EVEX byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + Opcode |= ( BinCode[CodePos] << 16 ); //read next MVEX/EVEX byte settings. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //Some bits are inverted, so uninvert them arithmetically. + + Opcode ^= 0x0878F0; + + //Check if Reserved bits are 0 if they are not 0 the MVEX/EVEX instruction is invalid. + + InvalidOp = ( Opcode & 0x00000C ) > 0; + + //Decode bit settings. + + if( BitMode === 2 ) + { + RegExtend = ( ( Opcode & 0x80 ) >> 4 ) | ( Opcode & 0x10 ); //The Double R'R bit decode for Register Extension 0 to 32. + BaseExtend = ( Opcode & 0x60 ) >> 2; //The X bit, and B Bit base register extend combination 0 to 32. + IndexExtend = ( Opcode & 0x40 ) >> 3; //The X extends the SIB Index by 8. + } + + VectorRegister = ( ( Opcode & 0x7800 ) >> 11 ) | ( ( Opcode & 0x080000 ) >> 15 ); //The Added in Vector Register for SSE under MVEX/EVEX. + + WidthBit = ( Opcode & 0x8000 ) >> 15; //The width bit separator for MVEX/EVEX. + SIMD = ( Opcode & 0x0300 ) >> 8; //decode the SIMD mode setting. + HInt_ZeroMerg = ( Opcode & 0x800000 ) >> 23; //Zero Merge to destination control, or MVEX EH control. + + //EVEX option bits take the place of Vector length control. + + if ( ( Opcode & 0x0400 ) > 0 ) + { + SizeAttrSelect = ( Opcode & 0x600000 ) >> 21; //The EVEX.L'L Size combination. + RoundMode = SizeAttrSelect | 4; //Rounding mode is Vector length if used. + ConversionMode = (Opcode & 0x100000 ) >> 20; //Broadcast Round Memory address system. + } + + //MVEX Vector Length, and Broadcast round. + + else + { + SizeAttrSelect = 2; //Max Size by default. + ConversionMode = ( Opcode & 0x700000 ) >> 20; //"MVEX.sss" Option bits. + RoundMode = ConversionMode; //Rounding mode selection is ConversionMode if used. + Extension = 3; + } + + MaskRegister = ( Opcode & 0x070000 ) >> 16; //Mask to destination. + Opcode = ( Opcode & 0x03 ) << 8; //Change Operation code map. + + //------------------------------------------------------------------------------------------------------------------------- + Opcode = ( Opcode & 0x300 ) | BinCode[CodePos]; //read the 8 bit opcode put them in the lower 8 bits away from opcode map extend bit's. + NextByte(); //Move to the next byte. + //------------------------------------------------------------------------------------------------------------------------- + + //Stop decoding prefixes. + + return(null); + } + + //Segment overrides + + if ( ( Opcode & 0x7E7 ) === 0x26 || ( Opcode & 0x7FE ) === 0x64 ) + { + SegOverride = Mnemonics[ Opcode ]; //Set the Left Bracket for the ModR/M memory address. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //Operand override Prefix + + if(Opcode === 0x66) + { + SIMD = 1; //sets SIMD mode 1 in case of SSE instruction opcode. + SizeAttrSelect = 0; //Adjust the size attribute setting for the size adjustment to the next instruction. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //Ram address size override. + + if(Opcode === 0x67) + { + AddressOverride = true; //Set the setting active for the ModR/M address size. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //if repeat Prefixes F2 hex REP,or F3 hex RENP + + if (Opcode === 0xF2 || Opcode === 0xF3) + { + SIMD = (Opcode & 0x02 ) | ( 1 - Opcode & 0x01 ); //F2, and F3 change the SIMD mode during SSE instructions. + PrefixG1 = Mnemonics[ Opcode ]; //set the Prefix string. + HLEFlipG1G2 = true; //set Filp HLE in case this is the last prefix read, and LOCK was set in string G2 first for HLE. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //if the lock prefix note the lock prefix is separate + + if (Opcode === 0xF0) + { + PrefixG2 = Mnemonics[ Opcode ]; //set the Prefix string + HLEFlipG1G2 = false; //set Flip HLE false in case this is the last prefix read, and REP, or REPNE was set in string G2 first for HLE. + return(DecodePrefixAdjustments()); //restart function decode more prefix settings that can effect the decode instruction. + } + + //Before ending the function "DecodePrefixAdjustments()" some opcode combinations are invalid in 64 bit mode. + + if ( BitMode === 2 ) + { + InvalidOp |= ( ( ( Opcode & 0x07 ) >= 0x06 ) & ( Opcode <= 0x40 ) ); + InvalidOp |= ( Opcode === 0x60 | Opcode === 0x61 ); + InvalidOp |= ( Opcode === 0xD4 | Opcode === 0xD5 ); + InvalidOp |= ( Opcode === 0x9A | Opcode === 0xEA ); + InvalidOp |= ( Opcode === 0x82 ); + } + +} + +/*------------------------------------------------------------------------------------------------------------------------- +The Decode opcode function gives back the operation name, and what it uses for input. +The input types are for example which registers it uses with the ModR/M, or which Immediate type is used. +The input types are stored into an operand string. This function gives back the instruction name, And what the operands use. +--------------------------------------------------------------------------------------------------------------------------- +This function is designed to be used after the Decode prefix adjustments function because the Opcode should contain an real instruction code. +This is because the Decode prefix adjustments function will only end if the Opcode value is not a prefix adjustment code to the ModR/M etc. +However DecodePrefixAdjustments can also prevent this function from being called next if the prefix settings are bad or an invalid instruction is +used for the bit mode the CPU is in as it will set InvalidOp true. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeOpcode() +{ + //get the Operation name by the operations opcode. + + Instruction = Mnemonics[Opcode]; + + //get the Operands for this opcode it follows the same array structure as Mnemonics array + + InsOperands = Operands[Opcode]; + + //Some Opcodes use the next byte automatically for extended opcode selection. Or current SIMD mode. + + var ModRMByte = BinCode[CodePos]; //Read the byte but do not move to the next byte. + + //If the current Mnemonic is an array two in size then Register Mode, and memory mode are separate from each other. + //Used in combination with Grouped opcodes, and Static opcodes. + + if(Instruction instanceof Array && Instruction.length == 2) { var bits = ( ModRMByte >> 6 ) & ( ModRMByte >> 7 ); Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; } + + //Arithmetic unit 8x8 combinational logic array combinations. + //If the current Mnemonic is an array 8 in length It is a group opcode instruction may repeat previous instructions in different forums. + + if(Instruction instanceof Array && Instruction.length == 8) { var bits = ( ModRMByte & 0x38 ) >> 3; Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; + + //if The select Group opcode is another array 8 in size it is a static opcode selection which makes the last three bits of the ModR/M byte combination. + + if(Instruction instanceof Array && Instruction.length == 8) { var bits = ( ModRMByte & 0x07 ); Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; NextByte(); } } + + //Vector unit 4x4 combinational array logic. + //if the current Mnemonic is an array 4 in size it is an SIMD instruction with four possible modes N/A, 66, F3, F2. + //The mode is set to SIMD, it could have been set by the EVEX.pp, VEX.pp bit combination, or by prefixes N/A, 66, F3, F2. + + if(Instruction instanceof Array && Instruction.length == 4) + { + Vect = true; //Set Vector Encoding true. + + //Reset the prefix string G1 because prefix codes F2, and F3 are used with SSE which forum the repeat prefix. + //Some SSE instructions can use the REP, RENP prefixes. + //The Vectors that do support the repeat prefix uses Packed Single format. + + if(Instruction[2] !== "" && Instruction[3] !== "") { PrefixG1 = ""; } else { SIMD = ( SIMD === 1 ) & 1; } + Instruction = Instruction[SIMD]; InsOperands = InsOperands[SIMD]; + + //If the SIMD instruction uses another array 4 in length in the Selected SIMD vector Instruction. + //Then each vector Extension is separate. The first extension is used if no extension is active for Regular instructions, and vector instruction septation. + //0=None. 1=VEX only. 2=EVEX only. 3=??? unused. + + if(Instruction instanceof Array && Instruction.length == 4) + { + //Get the correct Instruction for the Active Extension type. + + if(Instruction[Extension] !== "") { Instruction = Instruction[Extension]; InsOperands = InsOperands[Extension]; } + else{ Instruction = "???"; InsOperands = ""; } + } + else if( Extension === 3 ){ Instruction = "???"; InsOperands = ""; } + } + else if( Opcode >= 0x700 && SIMD > 0 ){ Instruction = "???"; InsOperands = ""; } + + //if Any Mnemonic is an array 3 in size the instruction name goes by size. + + if(Instruction instanceof Array && Instruction.length == 3) + { + var bits = ( Extension === 0 & BitMode !== 0 ) ^ ( SizeAttrSelect >= 1 ); //The first bit in SizeAttrSelect for size 32/16 Flips if 16 bit mode. + ( WidthBit ) && ( bits = 2 ); //Goes 64 using the Width bit. + ( Extension === 3 && HInt_ZeroMerg && Instruction[1] !== "" ) && ( HInt_ZeroMerg = false, bits = 1 ); //MVEX uses element 1 if MVEX.E is set for instruction that change name. + + if (Instruction[bits] !== "") { Instruction = Instruction[bits]; InsOperands = InsOperands[bits]; } + + //else no size prefix name then use the default size Mnemonic name. + + else { Instruction = Instruction[0]; InsOperands = InsOperands[0]; } + } + + //If Extension is not 0 then add the vector extend "V" to the instruction. + //During the decoding of the operands the instruction can be returned as invalid if it is an Arithmetic, or MM, ST instruction. + //Vector mask instructions start with K instead of V any instruction that starts with K and is an + //vector mask Instruction which starts with K instead of V. + + if( Opcode <= 0x400 && Extension > 0 && Instruction.charAt(0) !== "K" && Instruction !== "???" ) { Instruction = "V" + Instruction; } + + //In 32 bit mode, or bellow only one instruction MOVSXD is replaced with ARPL. + + if( BitMode <= 1 && Instruction === "MOVSXD" ) { Instruction = "ARPL"; InsOperands = "06020A01"; } +} + +/*------------------------------------------------------------------------------------------------------------------------- +Read each operand in the Operand String then set the correct operand in the X86 decoder array. +OpNum is the order the operands are read in the operand string. The Operand type is which operand will be set +active along the X86Decoder. The OpNum is the order the decoded operands will be positioned after they are decoded +in order along the X86 decoder. The order the operands display is different than the order they decode in sequence. +--------------------------------------------------------------------------------------------------------------------------- +This function is used after the function ^DecodeOpcode()^ because the function ^DecodeOpcode()^ gives back the +operand string for what the instruction takes as input. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeOperandString() +{ + //Variables that are used for decoding one operands across the operand string. + + var OperandValue = 0, Code = 0, BySize = 0, Setting = 0; + + //It does not matter which order the explicit operands decode as they do not require reading another byte. + //They start at 7 and are set in order, but the order they are displayed is the order they are read in the operand string because of OpNum. + + var ExplicitOp = 8, ImmOp = 3; + + //Each operand is 4 hex digits, and OpNum is added by one for each operand that is read per Iteration. + + for( var i = 0, OpNum = 0; i < InsOperands.length; i+=4 ) //Iterate though operand string. + { + OperandValue = parseInt( InsOperands.substring(i, (i + 4) ), 16 ); //Convert the four hex digits to a 16 bit number value. + Code = ( OperandValue & 0xFE00 ) >> 9; //Get the operand Code. + BySize = ( OperandValue & 0x0100 ) >> 8; //Get it's by size attributes setting for if Setting is used as size attributes. + Setting = ( OperandValue & 0x00FF ); //Get the 8 bit Size setting. + + //If code is 0 the next 8 bit value specifies which type of of prefix settings are active. + + if( Code === 0 ) + { + if(BySize) //Vector adjustment settings. + { + RoundingSetting = ( Setting & 0x03 ) << 3; + if( Opcode >= 0x700 && RoundingSetting >= 0x10 ){ RoundMode |= 0x10; } + VSIB = ( Setting >> 2 ) & 1; + IgnoresWidthbit = ( Setting >> 3 ) & 1; + VectS = ( Setting >> 4 ) & 7; + Swizzle = ( VectS >> 2 ) & 1; + Up = ( VectS >> 1 ) & 1; + Float = VectS & 1; + if( ( Setting & 0x80 ) == 0x80 ) { Vect = false; } //If Non vector instruction set Vect false. + } + else //Instruction Prefix types. + { + XRelease = Setting & 0x01; + XAcquire = ( Setting & 0x02 ) >> 1; + HT = ( Setting & 0x04 ) >> 2; + BND = ( Setting & 0x08 ) >> 3; + } + } + + //if it is a opcode Reg Encoding then first element along the decoder is set as this has to be decode first, before moving to the + //byte for modR/M. + + else if( Code === 1 ) + { + X86Decoder[0].set( 0, BySize, Setting, OpNum++ ); + } + + //if it is a ModR/M, or Far pointer ModR/M, or Moffs address then second decoder element is set. + + else if( Code >= 2 && Code <= 4 ) + { + X86Decoder[1].set( ( Code - 2 ), BySize, Setting, OpNum++ ); + if( Code == 4 ){ FarPointer = 1; } //If code is 4 it is a far pointer. + } + + //The ModR/M Reg bit's are separated from the address system above. The ModR/M register can be used as a different register with a + //different address pointer. The Reg bits of the ModR/M decode next as it would be inefficient to read the register value if the + //decoder moves to the immediate. + + else if( Code === 5 ) + { + X86Decoder[2].set( 0, BySize, Setting, OpNum++ ); + } + + //Immediate input one. The immediate input is just a number input it is decoded last unless the instruction does not use a + //ModR/M encoding, or Reg Opcode. + + else if( Code >= 6 && Code <= 8 && ImmOp <= 5 ) + { + X86Decoder[ImmOp++].set( ( Code - 6 ), BySize, Setting, OpNum++ ); + } + + //Vector register. If the instruction uses this register it will not be decoded or displayed unless one of the vector extension codes are + //decoded by the function ^DecodePrefixAdjustments()^. The Vector extension codes also have a Vector register value that is stored into + //the variable VectorRegister. The variable VectorRegister is given to the function ^DecodeRegValue()^. + + else if( Code === 9 && ( Extension > 0 || Opcode >= 0x700 ) ) + { + X86Decoder[6].set( 0, BySize, Setting, OpNum++ ); + } + + //The upper four bits of the Immediate is used as an register. The variable IMM stores the last immediate byte that is read by ^DecodeImmediate()^. + //The upper four bits of the IMM is given to the function ^DecodeRegValue()^. + + else if( Code === 10 ) + { + X86Decoder[7].set( 0, BySize, Setting, OpNum++ ); + } + + //Else any other encoding type higher than 13 is an explicit operand selection. + //And also there can only be an max of four explicit operands. + + else if( Code >= 11 && ExplicitOp <= 11) + { + X86Decoder[ExplicitOp].set( ( Code - 11 ), BySize, Setting, OpNum++ ); + ExplicitOp++; //move to the next Explicit operand. + } + } +} + +/*------------------------------------------------------------------------------------------------------------------------- +Decode each of the operands along the X86Decoder and deactivate them. +This function is used after ^DecodeOperandString()^ which sets up the X86 Decoder for the instructions operands. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeOperands() +{ + //The Operands array is a string array in which the operand number is the element the decoded operand is positioned. + + var out = []; + + //This holds the decoded ModR/M byte from the "Decode_ModRM_SIB_Value()" function because the Register, and Address can decode differently. + + var ModRMByte = [ -1, //Mode is set negative one used to check if the ModR/M has been decoded. + 0, //The register value is decoded separately if used. + 0 //the base register for the address location. + ]; + + //If no Immediate operand is used then the Immediate register encoding forces an IMM8 for the register even if the immediate is not used. + + var IMM_Used = false; //This is set true for if any Immediate is read because the last Immediate byte is used as the register on the upper four bits. + + //If reg opcode is active. + + if( X86Decoder[0].Active ) + { + out[ X86Decoder[0].OpNum ] = DecodeRegValue( + ( RegExtend | ( Opcode & 0x07 ) ), //Register value. + X86Decoder[0].BySizeAttrubute, //By size attribute or not. + X86Decoder[0].Size //Size settings. + ); + } + + //If ModR/M Address is active. + + if( X86Decoder[1].Active ) + { + //Decode the ModR/M byte Address which can end up reading another byte for SIB address, and including displacements. + + if(X86Decoder[1].Type !== 0) + { + ModRMByte = Decode_ModRM_SIB_Value(); //Decode the ModR/M byte. + out[ X86Decoder[1].OpNum ] = Decode_ModRM_SIB_Address( + ModRMByte, //The ModR/M byte. + X86Decoder[1].BySizeAttrubute, //By size attribute or not. + X86Decoder[1].Size //Size settings. + ); + } + + //Else If the ModR/M type is 0 then it is a moffs address. + + else + { + var s=0, AddrsSize = 0; + if( X86Decoder[1].BySizeAttrubute ) + { + AddrsSize = ( Math.pow( 2, BitMode ) << 1 ); + s = GetOperandSize( X86Decoder[1].Size, true ) << 1; + } + else + { + AddrsSize = BitMode + 1; + s = X86Decoder[1].Size; + } + out[ X86Decoder[1].OpNum ] = PTR[ s ]; + out[ X86Decoder[1].OpNum ] += SegOverride + DecodeImmediate( 0, X86Decoder[1].BySizeAttrubute, AddrsSize ) + "]"; + } + } + + //Decode the Register value of the ModR/M byte. + + if( X86Decoder[2].Active ) + { + //If the ModR/M address is not used, and ModR/M byte was not previously decoded then decode it. + + if( ModRMByte[0] === -1 ){ ModRMByte = Decode_ModRM_SIB_Value(); } + + //Decode only the Register Section of the ModR/M byte values. + + out[ X86Decoder[2].OpNum ] = DecodeRegValue( + ( RegExtend | ( ModRMByte[1] & 0x07 ) ), //Register value. + X86Decoder[2].BySizeAttrubute, //By size attribute or not. + X86Decoder[2].Size //Size settings. + ); + } + + //First Immediate if used. + + if( X86Decoder[3].Active ) + { + var t = DecodeImmediate( + X86Decoder[3].Type, //Immediate input type. + X86Decoder[3].BySizeAttrubute, //By size attribute or not. + X86Decoder[3].Size //Size settings. + ); + + //Check if Instruction uses condition codes. + + if( Instruction.slice(-1) === "," ) + { + Instruction = Instruction.split(","); + + if( ( Extension >= 1 && Extension <= 2 && Opcode <= 0x400 && IMMValue < 0x20 ) || IMMValue < 0x08 ) + { + IMMValue |= ( ( ( Opcode > 0x400 ) & 1 ) << 5 ); //XOP adjust. + Instruction = Instruction[0] + ConditionCodes[ IMMValue ] + Instruction[1]; + } + else { Instruction = Instruction[0] + Instruction[1]; out[ X86Decoder[3].OpNum ] = t; } + } + + //else add the Immediate byte encoding to the decoded instruction operands. + + else { out[ X86Decoder[3].OpNum ] = t; } + + IMM_Used = true; //Immediate byte is read. + } + + //Second Immediate if used. + + if( X86Decoder[4].Active ) + { + out[ X86Decoder[4].OpNum ] = DecodeImmediate( + X86Decoder[4].Type, //Immediate input type. + X86Decoder[4].BySizeAttrubute, //By size attribute or not. + X86Decoder[4].Size //Size settings. + ); + } + + //Third Immediate if used. + + if( X86Decoder[5].Active ) + { + out[ X86Decoder[5].OpNum ] = DecodeImmediate( + X86Decoder[5].Type, //Immediate input type. + X86Decoder[5].BySizeAttrubute, //By size attribute or not. + X86Decoder[5].Size //Size settings. + ); + } + + //Vector register if used from an SIMD vector extended instruction. + + if( X86Decoder[6].Active ) + { + out[ X86Decoder[6].OpNum ] = DecodeRegValue( + VectorRegister, //Register value. + X86Decoder[6].BySizeAttrubute, //By size attribute or not. + X86Decoder[6].Size //Size settings. + ); + } + + //Immediate register encoding. + + if( X86Decoder[7].Active ) + { + if( !IMM_Used ) { DecodeImmediate(0, false, 0); } //forces IMM8 if no Immediate has been used. + out[ X86Decoder[7].OpNum ] = DecodeRegValue( + ( ( ( IMMValue & 0xF0 ) >> 4 ) | ( ( IMMValue & 0x08 ) << 1 ) ), //Register value. + X86Decoder[7].BySizeAttrubute, //By size attribute or not. + X86Decoder[7].Size //Size settings. + ); + } + + //------------------------------------------------------------------------------------------------------------------------- + //Iterate though the 4 possible Explicit operands The first operands that is not active ends the Iteration. + //------------------------------------------------------------------------------------------------------------------------- + + for( var i = 8; i < 11; i++ ) + { + //------------------------------------------------------------------------------------------------------------------------- + //if Active Type is used as which Explicit operand. + //------------------------------------------------------------------------------------------------------------------------- + + if( X86Decoder[i].Active ) + { + //General use registers value 0 though 4 there size can change by size setting but can not be extended or changed. + + if( X86Decoder[i].Type <= 3 ) + { + out[ X86Decoder[i].OpNum ] = DecodeRegValue( + X86Decoder[i].Type, //register by value for Explicit Registers A, C, D, B. + X86Decoder[i].BySizeAttrubute, //By size attribute or not. + X86Decoder[i].Size //Size attribute. + ); + } + + //RBX address Explicit Operands prefixes can extend the registers and change pointer size RegMode 0. + + else if( X86Decoder[i].Type === 4 ) + { + s = 3; //If 32, or 64 bit ModR/M. + if( ( BitMode === 0 && !AddressOverride ) || ( BitMode === 1 && AddressOverride ) ){ s = 7; } //If 16 bit ModR/M. + out[ X86Decoder[i].OpNum ] = Decode_ModRM_SIB_Address( + [ 0, 0, s ], //the RBX register only for the pointer. + X86Decoder[i].BySizeAttrubute, //By size attribute or not. + X86Decoder[i].Size //size attributes. + ); + } + + //source and destination address Explicit Operands prefixes can extend the registers and change pointer size. + + else if( X86Decoder[i].Type === 5 | X86Decoder[i].Type === 6 ) + { + s = 1; //If 32, or 64 bit ModR/M. + if( ( BitMode === 0 && !AddressOverride ) || ( BitMode === 1 & AddressOverride ) ) { s = -1; } //If 16 bit ModR/M. + out[ X86Decoder[i].OpNum ] = Decode_ModRM_SIB_Address( + [ 0, 0, ( X86Decoder[i].Type + s ) ], //source and destination pointer register by type value. + X86Decoder[i].BySizeAttrubute, //By size attribute or not. + X86Decoder[i].Size //size attributes. + ); + } + + //The ST only Operand, and FS, GS. + + else if( X86Decoder[i].Type >= 7 ) + { + out[ X86Decoder[i].OpNum ] = ["ST", "FS", "GS", "1", "3", "XMM0", "M10"][ ( X86Decoder[i].Type - 7 ) ]; + } + } + + //------------------------------------------------------------------------------------------------------------------------- + //else inactive end iteration. + //------------------------------------------------------------------------------------------------------------------------- + + else { break; } + + } + + /*------------------------------------------------------------------------------------------------------------------------- + If the EVEX vector extension is active the Mask, and Zero merge control are inserted into operand 0 (Destination operand). + -------------------------------------------------------------------------------------------------------------------------*/ + + //Mask Register is used if it is not 0 in value. + + if( MaskRegister !== 0 ){ out[0] += "{K" + MaskRegister + "}"; } + + //EVEX Zero Merge control. + + if( Extension === 2 && HInt_ZeroMerg ) { out[0] += "{Z}"; } + + //convert the operand array to a string and return it. + + InsOperands = out.toString(); +} + +/*------------------------------------------------------------------------------------------------------------------------- +The main Instruction decode function plugs everything in together for the steps required to decode a full X86 instruction. +-------------------------------------------------------------------------------------------------------------------------*/ + +function DecodeInstruction() +{ + //Reset Prefix adjustments, and vector setting adjustments. + + Reset(); + + var out = ""; //The instruction code that will be returned back from this function. + + //Record the starting position. + + InstructionPos = GetPosition(); + + //First read any opcodes (prefix) that act as adjustments to the main three operand decode functions ^DecodeRegValue()^, + //^Decode_ModRM_SIB_Address()^, and ^DecodeImmediate()^. + + DecodePrefixAdjustments(); + + //Only continue if an invalid opcode is not read by DecodePrefixAdjustments() for cpu bit mode setting. + + if( !InvalidOp ) + { + //Decode the instruction. + + DecodeOpcode(); + + //------------------------------------------------------------------------------------------------------------------------- + //Intel Larrabee CCCCC condition codes. + //------------------------------------------------------------------------------------------------------------------------- + + if( Opcode >= 0x700 && Instruction.slice(-1) === "," ) + { + Instruction = Instruction.split(","); + + //CMP conditions. + + if( Opcode >= 0x720 && Opcode <= 0x72F ) + { + IMMValue = VectorRegister >> 2; + + if( Float || ( IMMValue !== 3 && IMMValue !== 7 ) ) + { + Instruction = Instruction[0] + ConditionCodes[IMMValue] + Instruction[1]; + } + else { Instruction = Instruction[0] + Instruction[1]; } + + IMMValue = 0; VectorRegister &= 0x03; + } + + //Else High/Low. + + else + { + Instruction = Instruction[0] + ( ( ( VectorRegister & 1 ) === 1 ) ? "H" : "L" ) + Instruction[1]; + } + } + + //Setup the X86 Decoder for which operands the instruction uses. + + DecodeOperandString(); + + //Now only some instructions can vector extend, and that is only if the instruction is an SIMD Vector format instruction. + + if( !Vect && Extension > 0 && Opcode <= 0x400 ) { InvalidOp = true; } + + //The Width Bit setting must match the vector numbers size otherwise this create an invalid operation code in MVEX/EVEX unless the Width bit is ignored. + + if( Vect && !IgnoresWidthbit && Extension >= 2 ) + { + InvalidOp = ( ( SIMD & 1 ) !== ( WidthBit & 1 ) ); //Note use, and ignore width bit pastern EVEX. + } + if( Opcode >= 0x700 ) { WidthBit ^= IgnoresWidthbit; } //L1OM Width bit invert. + } + + //If the instruction is invalid then set the instruction to "???" + + if( InvalidOp ) + { + out = "???" //set the returned instruction to invalid + } + + //Else finish decoding the valid instruction. + + else + { + //Decode each operand along the Decoder array in order, and deactivate them. + + DecodeOperands(); + + /*------------------------------------------------------------------------------------------------------------------------- + 3DNow Instruction name is encoded by the next byte after the ModR/M, and Reg operands. + -------------------------------------------------------------------------------------------------------------------------*/ + + if( Opcode === 0x10F ) + { + //Lookup operation code. + + Instruction = M3DNow[ BinCode[CodePos] ]; NextByte(); + + //If Invalid instruction. + + if( Instruction === "" || Instruction == null ) + { + Instruction = "???"; InsOperands = ""; + } + } + + /*------------------------------------------------------------------------------------------------------------------------- + Synthetic virtual machine operation codes. + -------------------------------------------------------------------------------------------------------------------------*/ + + else if( Instruction === "SSS" ) + { + //The Next two bytes after the static opcode is the select synthetic virtual machine operation code. + + var Code1 = BinCode[CodePos]; NextByte(); + var Code2 = BinCode[CodePos]; NextByte(); + + //No operations exist past 4 in value for both bytes that combine to the operation code. + + if( Code1 >= 5 || Code2 >= 5 ) { Instruction = "???"; } + + //Else calculate the operation code in the 5x5 map. + + else + { + Instruction = MSynthetic[ ( Code1 * 5 ) + Code2 ]; + + //If Invalid instruction. + + if( Instruction === "" || Instruction == null ) + { + Instruction = "???"; + } + } + } + + //32/16 bit instructions 9A, and EA use Segment, and offset with Immediate format. + + if( Opcode === 0x9A || Opcode === 0xEA ) + { + var t = InsOperands.split(","); + InsOperands = t[1] + ":" +t[0]; + } + + //**Depending on the operation different prefixes replace others for HLE, or MPX, and branch prediction. + //if REP prefix, and LOCK prefix are used together, and the current decoded operation allows HLE XRELEASE. + + if(PrefixG1 === Mnemonics[0xF3] && PrefixG2 === Mnemonics[0xF0] && XRelease) + { + PrefixG1 = "XRELEASE"; //Then change REP to XRELEASE. + } + + //if REPNE prefix, and LOCK prefix are used together, and the current decoded operation allows HLE XACQUIRE. + + if(PrefixG1 === Mnemonics[0xF2] && PrefixG2 === Mnemonics[0xF0] && XAcquire) + { + PrefixG1 = "XACQUIRE"; //Then change REP to XACQUIRE + } + + //Depending on the order that the Repeat prefix, and Lock prefix is used flip Prefix G1, and G2 if HLEFlipG1G2 it is true. + + if((PrefixG1 === "XRELEASE" || PrefixG1 === "XACQUIRE") && HLEFlipG1G2) + { + t = PrefixG1; PrefixG1 = PrefixG2; PrefixG2 = t; + } + + //if HT is active then it is a jump instruction check and adjust for the HT,and HNT prefix. + + if(HT) + { + if (SegOverride === Mnemonics[0x2E]) + { + PrefixG1 = "HNT"; + } + else if (SegOverride === Mnemonics[0x3E]) + { + PrefixG1 = "HT"; + } + } + + //else if Prefix is REPNE switch it to BND if operation is a MPX instruction. + + if(PrefixG1 === Mnemonics[0xF2] && BND) + { + PrefixG1 = "BND"; + } + + //Before the Instruction is put together check the length of the instruction if it is longer than 15 bytes the instruction is undefined. + + if ( InstructionHex.length > 30 ) + { + //Calculate how many bytes over. + + var Dif32 = ( ( InstructionHex.length - 30 ) >> 1 ); + + //Limit the instruction hex output to 15 bytes. + + InstructionHex = InstructionHex.substring( 0, 30 ); + + //Calculate the Difference between the Disassembler current position. + + Dif32 = Pos32 - Dif32; + + //Convert Dif to unsignified numbers. + + if( Dif32 < 0 ) { Dif32 += 0x100000000; } + + //Convert to strings. + + for (var S32 = Dif32.toString(16) ; S32.length < 8; S32 = "0" + S32); + for (var S64 = Pos64.toString(16) ; S64.length < 8; S64 = "0" + S64); + + //Go to the Calculated address right after the Instruction UD. + + GotoPosition( S64 + S32 ); + + //Set prefixes, and operands to empty strings, and set Instruction to UD. + + PrefixG1 = "";PrefixG2 = ""; Instruction = "???"; InsOperands = ""; + } + + //Put the Instruction sequence together. + + out = PrefixG1 + " " + PrefixG2 + " " + Instruction + " " + InsOperands; + + //Remove any trailing spaces because of unused prefixes. + + out = out.replace(/^[ ]+|[ ]+$/g,''); + + //Add error suppression if used. + + if( Opcode >= 0x700 || RoundMode !== 0 ) + { + out += RoundModes[ RoundMode ]; + } + + //Return the instruction. + } + + return( out ); +} + +/*------------------------------------------------------------------------------------------------------------------------- +This function Resets the Decoder in case of error, or an full instruction has been decoded. +-------------------------------------------------------------------------------------------------------------------------*/ + +function Reset() +{ + //Reset Opcode, and Size attribute selector. + + Opcode = 0; SizeAttrSelect = 1; + + //Reset Operands and instruction. + + Instruction = ""; InsOperands = ""; + + //Reset ModR/M. + + RexActive = 0; RegExtend = 0; BaseExtend = 0; IndexExtend = 0; + SegOverride = "["; AddressOverride = false; FarPointer = 0; + + //Reset Vector extensions controls. + + Extension = 0; SIMD = 0; Vect = false; ConversionMode = 0; WidthBit = false; + VectorRegister = 0; MaskRegister = 0; HInt_ZeroMerg = false; RoundMode = 0x00; + + //Reset vector format settings. + + IgnoresWidthbit = false; VSIB = false; RoundingSetting = 0; + Swizzle = false; Up = false; Float = false; VectS = 0x00; + + //Reset IMMValue used for Imm register encoding, and Condition codes. + + IMMValue = 0; + + //Reset instruction Prefixes. + + PrefixG1 = "", PrefixG2 = ""; + XRelease = false; XAcquire = false; HLEFlipG1G2 = false; + HT = false; + BND = false; + + //Reset Invalid operation code. + + InvalidOp = false; + + //Reset instruction hex because it is used to check if the instruction is longer than 15 bytes which is impossible for the X86 Decoder Circuit. + + InstructionHex = ""; + + //Deactivate all operands along the X86Decoder. + + for( var i = 0; i < X86Decoder.length; X86Decoder[i++].Deactivate() ); +} + +/*------------------------------------------------------------------------------------------------------------------------- +do an linear disassemble. +-------------------------------------------------------------------------------------------------------------------------*/ + +export function LDisassemble() +{ + var Instruction = ""; //Stores the Decoded instruction. + var Out = ""; //The Disassemble output + + //Disassemble binary code using an linear pass. + + var len = BinCode.length; + + //Backup the base address. + + var BPos64 = Pos64, BPos32 = Pos32; + + while( CodePos < len ) + { + Instruction = DecodeInstruction(); + + //Add the 64 bit address of the output if ShowInstructionPos decoding is active. + + if( ShowInstructionPos ) + { + Out += InstructionPos + " "; + } + + //Show Each byte that was read to decode the instruction if ShowInstructionHex decoding is active. + + if(ShowInstructionHex) + { + InstructionHex = InstructionHex.toUpperCase(); + for(; InstructionHex.length < 32; InstructionHex = InstructionHex + " " ); + Out += InstructionHex + ""; + } + + //Put the decoded instruction into the output and make a new line. + + Out += Instruction + "\r\n"; + + //Reset instruction Pos and Hex. + + InstructionPos = ""; InstructionHex = ""; + } + + CodePos = 0; //Reset the Code position + Pos32 = BPos32; Pos64 = BPos64; //Reset Base address. + + //return the decoded binary code + + return(Out); + +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +/* + * The following code has been added to expose public methods for use in CyberChef + */ + +export function setBitMode (val) { + BitMode = val; +}; +export function setShowInstructionHex (val) { + ShowInstructionHex = val; +}; +export function setShowInstructionPos (val) { + ShowInstructionPos = val; +}; + diff --git a/src/core/lib/bzip2.js b/src/core/vendor/bzip2.mjs similarity index 99% rename from src/core/lib/bzip2.js rename to src/core/vendor/bzip2.mjs index 03c8c97c..df0573d8 100755 --- a/src/core/lib/bzip2.js +++ b/src/core/vendor/bzip2.mjs @@ -261,3 +261,5 @@ bzip2.decompress = function(bits, size, len){ } return output; } + +export default bzip2; diff --git a/src/core/lib/js-codepage/cptable.js b/src/core/vendor/js-codepage/cptable.js similarity index 100% rename from src/core/lib/js-codepage/cptable.js rename to src/core/vendor/js-codepage/cptable.js diff --git a/src/core/lib/js-codepage/cputils.js b/src/core/vendor/js-codepage/cputils.js similarity index 100% rename from src/core/lib/js-codepage/cputils.js rename to src/core/vendor/js-codepage/cputils.js diff --git a/src/core/vendor/remove-exif.mjs b/src/core/vendor/remove-exif.mjs new file mode 100644 index 00000000..6377c674 --- /dev/null +++ b/src/core/vendor/remove-exif.mjs @@ -0,0 +1,151 @@ +/* piexifjs +The MIT License (MIT) +Copyright (c) 2014, 2015 hMatoba(https://github.com/hMatoba) +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import Utils from "../Utils"; + +// Param jpeg should be a binaryArray +export function removeEXIF(jpeg) { + // Convert binaryArray to char string + jpeg = Utils.byteArrayToChars(jpeg); + if (jpeg.slice(0, 2) != "\xff\xd8") { + throw ("Given data is not jpeg."); + } + + var segments = splitIntoSegments(jpeg); + if (segments[1].slice(0, 2) == "\xff\xe1" && + segments[1].slice(4, 10) == "Exif\x00\x00") { + segments = [segments[0]].concat(segments.slice(2)); + } else if (segments[2].slice(0, 2) == "\xff\xe1" && + segments[2].slice(4, 10) == "Exif\x00\x00") { + segments = segments.slice(0, 2).concat(segments.slice(3)); + } else { + throw ("Exif not found."); + } + + var new_data = segments.join(""); + + // Convert back to binaryArray + new_data = Utils.strToCharcode(new_data); + + return new_data; +}; + +function splitIntoSegments(data) { + if (data.slice(0, 2) != "\xff\xd8") { + throw ("Given data isn't JPEG."); + } + + var head = 2; + var segments = ["\xff\xd8"]; + while (true) { + if (data.slice(head, head + 2) == "\xff\xda") { + segments.push(data.slice(head)); + break; + } else { + var length = unpack(">H", data.slice(head + 2, head + 4))[0]; + var endPoint = head + length + 2; + segments.push(data.slice(head, endPoint)); + head = endPoint; + } + + if (head >= data.length) { + throw ("Wrong JPEG data."); + } + } + return segments; +} + +function unpack(mark, str) { + if (typeof(str) != "string") { + throw ("'unpack' error. Got invalid type argument."); + } + var l = 0; + for (var markPointer = 1; markPointer < mark.length; markPointer++) { + if (mark[markPointer].toLowerCase() == "b") { + l += 1; + } else if (mark[markPointer].toLowerCase() == "h") { + l += 2; + } else if (mark[markPointer].toLowerCase() == "l") { + l += 4; + } else { + throw ("'unpack' error. Got invalid mark."); + } + } + + if (l != str.length) { + throw ("'unpack' error. Mismatch between symbol and string length. " + l + ":" + str.length); + } + + var littleEndian; + if (mark[0] == "<") { + littleEndian = true; + } else if (mark[0] == ">") { + littleEndian = false; + } else { + throw ("'unpack' error."); + } + var unpacked = []; + var strPointer = 0; + var p = 1; + var val = null; + var c = null; + var length = null; + var sliced = ""; + + while (c = mark[p]) { + if (c.toLowerCase() == "b") { + length = 1; + sliced = str.slice(strPointer, strPointer + length); + val = sliced.charCodeAt(0); + if ((c == "b") && (val >= 0x80)) { + val -= 0x100; + } + } else if (c == "H") { + length = 2; + sliced = str.slice(strPointer, strPointer + length); + if (littleEndian) { + sliced = sliced.split("").reverse().join(""); + } + val = sliced.charCodeAt(0) * 0x100 + + sliced.charCodeAt(1); + } else if (c.toLowerCase() == "l") { + length = 4; + sliced = str.slice(strPointer, strPointer + length); + if (littleEndian) { + sliced = sliced.split("").reverse().join(""); + } + val = sliced.charCodeAt(0) * 0x1000000 + + sliced.charCodeAt(1) * 0x10000 + + sliced.charCodeAt(2) * 0x100 + + sliced.charCodeAt(3); + if ((c == "l") && (val >= 0x80000000)) { + val -= 0x100000000; + } + } else { + throw ("'unpack' error. " + c); + } + + unpacked.push(val); + strPointer += length; + p += 1; + } + + return unpacked; +} diff --git a/src/node/index.js b/src/node/index.js deleted file mode 100644 index 77eed31f..00000000 --- a/src/node/index.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Node view for CyberChef. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - */ -require("babel-polyfill"); - -const Chef = require("../core/Chef.js").default; - -const CyberChef = module.exports = { - - bake: function(input, recipeConfig) { - this.chef = new Chef(); - return this.chef.bake( - input, - recipeConfig, - {}, - 0, - false - ); - } - -}; diff --git a/src/node/index.mjs b/src/node/index.mjs new file mode 100644 index 00000000..c6e86c68 --- /dev/null +++ b/src/node/index.mjs @@ -0,0 +1,39 @@ +/** + * Node view for CyberChef. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ +import "babel-polyfill"; + +// Define global environment functions +global.ENVIRONMENT_IS_WORKER = function() { + return typeof importScripts === "function"; +}; +global.ENVIRONMENT_IS_NODE = function() { + return typeof process === "object" && typeof require === "function"; +}; +global.ENVIRONMENT_IS_WEB = function() { + return typeof window === "object"; +}; + +import Chef from "../core/Chef"; + +const CyberChef = { + + bake: function(input, recipeConfig) { + this.chef = new Chef(); + return this.chef.bake( + input, + recipeConfig, + {}, + 0, + false + ); + } + +}; + +export default CyberChef; +export {CyberChef}; diff --git a/src/web/App.js b/src/web/App.js deleted file mode 100755 index 2b3f3638..00000000 --- a/src/web/App.js +++ /dev/null @@ -1,732 +0,0 @@ -import Utils from "../core/Utils.js"; -import Chef from "../core/Chef.js"; -import Manager from "./Manager.js"; -import HTMLCategory from "./HTMLCategory.js"; -import HTMLOperation from "./HTMLOperation.js"; -import Split from "split.js"; - - -/** - * HTML view for CyberChef responsible for building the web page and dealing with all user - * interactions. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {CatConf[]} categories - The list of categories and operations to be populated. - * @param {Object.} operations - The list of operation configuration objects. - * @param {String[]} defaultFavourites - A list of default favourite operations. - * @param {Object} options - Default setting for app options. - */ -const App = function(categories, operations, defaultFavourites, defaultOptions) { - this.categories = categories; - this.operations = operations; - this.dfavourites = defaultFavourites; - this.doptions = defaultOptions; - this.options = Utils.extend({}, defaultOptions); - - this.chef = new Chef(); - this.manager = new Manager(this); - - this.baking = false; - this.autoBake_ = false; - this.progress = 0; - this.ingId = 0; - - window.chef = this.chef; -}; - - -/** - * This function sets up the stage and creates listeners for all events. - * - * @fires Manager#appstart - */ -App.prototype.setup = function() { - document.dispatchEvent(this.manager.appstart); - this.initialiseSplitter(); - this.loadLocalStorage(); - this.populateOperationsList(); - this.manager.setup(); - this.resetLayout(); - this.setCompileMessage(); - this.loadURIParams(); - this.loaded(); -}; - - -/** - * Fires once all setup activities have completed. - */ -App.prototype.loaded = function() { - // Trigger CSS animations to remove preloader - document.body.classList.add("loaded"); - - // Wait for animations to complete then remove the preloader and loaded style - // so that the animations for existing elements don't play again. - setTimeout(function() { - document.getElementById("loader-wrapper").remove(); - document.body.classList.remove("loaded"); - }, 1000); - - // Clear the loading message interval - clearInterval(window.loadingMsgInt); -}; - - -/** - * An error handler for displaying the error to the user. - * - * @param {Error} err - */ -App.prototype.handleError = function(err) { - console.error(err); - const msg = err.displayStr || err.toString(); - this.alert(msg, "danger", this.options.errorTimeout, !this.options.showErrors); -}; - - -/** - * Updates the UI to show if baking is in process or not. - * - * @param {bakingStatus} - */ -App.prototype.setBakingStatus = function(bakingStatus) { - this.baking = bakingStatus; - - let inputLoadingIcon = document.querySelector("#input .title .loading-icon"), - outputLoadingIcon = document.querySelector("#output .title .loading-icon"), - outputElement = document.querySelector("#output-text"); - - if (bakingStatus) { - inputLoadingIcon.style.display = "inline-block"; - outputLoadingIcon.style.display = "inline-block"; - outputElement.classList.add("disabled"); - outputElement.disabled = true; - } else { - inputLoadingIcon.style.display = "none"; - outputLoadingIcon.style.display = "none"; - outputElement.classList.remove("disabled"); - outputElement.disabled = false; - } -}; - - -/** - * Calls the Chef to bake the current input using the current recipe. - * - * @param {boolean} [step] - Set to true if we should only execute one operation instead of the - * whole recipe. - */ -App.prototype.bake = async function(step) { - let response; - - if (this.baking) return; - - this.setBakingStatus(true); - - try { - response = await this.chef.bake( - this.getInput(), // The user's input - this.getRecipeConfig(), // The configuration of the recipe - this.options, // Options set by the user - this.progress, // The current position in the recipe - step // Whether or not to take one step or execute the whole recipe - ); - } catch (err) { - this.handleError(err); - } - - this.setBakingStatus(false); - - if (!response) return; - - if (response.error) { - this.handleError(response.error); - } - - this.options = response.options; - this.dishStr = response.type === "html" ? Utils.stripHtmlTags(response.result, true) : response.result; - this.progress = response.progress; - this.manager.recipe.updateBreakpointIndicator(response.progress); - this.manager.output.set(response.result, response.type, response.duration); - - // If baking took too long, disable auto-bake - if (response.duration > this.options.autoBakeThreshold && this.autoBake_) { - this.manager.controls.setAutoBake(false); - this.alert("Baking took longer than " + this.options.autoBakeThreshold + - "ms, Auto Bake has been disabled.", "warning", 5000); - } -}; - - -/** - * Runs Auto Bake if it is set. - */ -App.prototype.autoBake = function() { - if (this.autoBake_) { - this.bake(); - } -}; - - -/** - * Runs a silent bake forcing the browser to load and cache all the relevant JavaScript code needed - * to do a real bake. - * - * The output will not be modified (hence "silent" bake). This will only actually execute the - * recipe if auto-bake is enabled, otherwise it will just load the recipe, ingredients and dish. - * - * @returns {number} - The number of miliseconds it took to run the silent bake. - */ -App.prototype.silentBake = function() { - let startTime = new Date().getTime(), - recipeConfig = this.getRecipeConfig(); - - if (this.autoBake_) { - this.chef.silentBake(recipeConfig); - } - - return new Date().getTime() - startTime; -}; - - -/** - * Gets the user's input data. - * - * @returns {string} - */ -App.prototype.getInput = function() { - const input = this.manager.input.get(); - - // Save to session storage in case we need to restore it later - sessionStorage.setItem("inputLength", input.length); - sessionStorage.setItem("input", input); - - return input; -}; - - -/** - * Sets the user's input data. - * - * @param {string} input - The string to set the input to - */ -App.prototype.setInput = function(input) { - sessionStorage.setItem("inputLength", input.length); - sessionStorage.setItem("input", input); - this.manager.input.set(input); -}; - - -/** - * Populates the operations accordion list with the categories and operations specified in the - * view constructor. - * - * @fires Manager#oplistcreate - */ -App.prototype.populateOperationsList = function() { - // Move edit button away before we overwrite it - document.body.appendChild(document.getElementById("edit-favourites")); - - let html = ""; - let i; - - for (i = 0; i < this.categories.length; i++) { - let catConf = this.categories[i], - selected = i === 0, - cat = new HTMLCategory(catConf.name, selected); - - for (let j = 0; j < catConf.ops.length; j++) { - let opName = catConf.ops[j], - op = new HTMLOperation(opName, this.operations[opName], this, this.manager); - cat.addOperation(op); - } - - html += cat.toHtml(); - } - - document.getElementById("categories").innerHTML = html; - - const opLists = document.querySelectorAll("#categories .op-list"); - - for (i = 0; i < opLists.length; i++) { - opLists[i].dispatchEvent(this.manager.oplistcreate); - } - - // Add edit button to first category (Favourites) - document.querySelector("#categories a").appendChild(document.getElementById("edit-favourites")); -}; - - -/** - * Sets up the adjustable splitter to allow the user to resize areas of the page. - */ -App.prototype.initialiseSplitter = function() { - this.columnSplitter = Split(["#operations", "#recipe", "#IO"], { - sizes: [20, 30, 50], - minSize: [240, 325, 440], - gutterSize: 4, - onDrag: function() { - this.manager.controls.adjustWidth(); - this.manager.output.adjustWidth(); - }.bind(this) - }); - - this.ioSplitter = Split(["#input", "#output"], { - direction: "vertical", - gutterSize: 4, - }); - - this.resetLayout(); -}; - - -/** - * Loads the information previously saved to the HTML5 local storage object so that user options - * and favourites can be restored. - */ -App.prototype.loadLocalStorage = function() { - // Load options - let lOptions; - if (localStorage.options !== undefined) { - lOptions = JSON.parse(localStorage.options); - } - this.manager.options.load(lOptions); - - // Load favourites - this.loadFavourites(); -}; - - -/** - * Loads the user's favourite operations from the HTML5 local storage object and populates the - * Favourites category with them. - * If the user currently has no saved favourites, the defaults from the view constructor are used. - */ -App.prototype.loadFavourites = function() { - let favourites = localStorage.favourites && - localStorage.favourites.length > 2 ? - JSON.parse(localStorage.favourites) : - this.dfavourites; - - favourites = this.validFavourites(favourites); - this.saveFavourites(favourites); - - const favCat = this.categories.filter(function(c) { - return c.name === "Favourites"; - })[0]; - - if (favCat) { - favCat.ops = favourites; - } else { - this.categories.unshift({ - name: "Favourites", - ops: favourites - }); - } -}; - - -/** - * Filters the list of favourite operations that the user had stored and removes any that are no - * longer available. The user is notified if this is the case. - - * @param {string[]} favourites - A list of the user's favourite operations - * @returns {string[]} A list of the valid favourites - */ -App.prototype.validFavourites = function(favourites) { - const validFavs = []; - for (let i = 0; i < favourites.length; i++) { - if (this.operations.hasOwnProperty(favourites[i])) { - validFavs.push(favourites[i]); - } else { - this.alert("The operation \"" + Utils.escapeHtml(favourites[i]) + - "\" is no longer available. It has been removed from your favourites.", "info"); - } - } - return validFavs; -}; - - -/** - * Saves a list of favourite operations to the HTML5 local storage object. - * - * @param {string[]} favourites - A list of the user's favourite operations - */ -App.prototype.saveFavourites = function(favourites) { - localStorage.setItem("favourites", JSON.stringify(this.validFavourites(favourites))); -}; - - -/** - * Resets favourite operations back to the default as specified in the view constructor and - * refreshes the operation list. - */ -App.prototype.resetFavourites = function() { - this.saveFavourites(this.dfavourites); - this.loadFavourites(); - this.populateOperationsList(); - this.manager.recipe.initialiseOperationDragNDrop(); -}; - - -/** - * Adds an operation to the user's favourites. - * - * @param {string} name - The name of the operation - */ -App.prototype.addFavourite = function(name) { - const favourites = JSON.parse(localStorage.favourites); - - if (favourites.indexOf(name) >= 0) { - this.alert("'" + name + "' is already in your favourites", "info", 2000); - return; - } - - favourites.push(name); - this.saveFavourites(favourites); - this.loadFavourites(); - this.populateOperationsList(); - this.manager.recipe.initialiseOperationDragNDrop(); -}; - - -/** - * Checks for input and recipe in the URI parameters and loads them if present. - */ -App.prototype.loadURIParams = function() { - // Load query string from URI - this.queryString = (function(a) { - if (a === "") return {}; - const b = {}; - for (let i = 0; i < a.length; i++) { - const p = a[i].split("="); - if (p.length !== 2) { - b[a[i]] = true; - } else { - b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); - } - } - return b; - })(window.location.search.substr(1).split("&")); - - // Turn off auto-bake while loading - const autoBakeVal = this.autoBake_; - this.autoBake_ = false; - - // Read in recipe from query string - if (this.queryString.recipe) { - try { - const recipeConfig = JSON.parse(this.queryString.recipe); - this.setRecipeConfig(recipeConfig); - } catch (err) {} - } else if (this.queryString.op) { - // If there's no recipe, look for single operations - this.manager.recipe.clearRecipe(); - try { - this.manager.recipe.addOperation(this.queryString.op); - } catch (err) { - // If no exact match, search for nearest match and add that - const matchedOps = this.manager.ops.filterOperations(this.queryString.op, false); - if (matchedOps.length) { - this.manager.recipe.addOperation(matchedOps[0].name); - } - - // Populate search with the string - const search = document.getElementById("search"); - - search.value = this.queryString.op; - search.dispatchEvent(new Event("search")); - } - } - - // Read in input data from query string - if (this.queryString.input) { - try { - const inputData = Utils.fromBase64(this.queryString.input); - this.setInput(inputData); - } catch (err) {} - } - - // Restore auto-bake state - this.autoBake_ = autoBakeVal; - this.autoBake(); -}; - - -/** - * Returns the next ingredient ID and increments it for next time. - * - * @returns {number} - */ -App.prototype.nextIngId = function() { - return this.ingId++; -}; - - -/** - * Gets the current recipe configuration. - * - * @returns {Object[]} - */ -App.prototype.getRecipeConfig = function() { - const recipeConfig = this.manager.recipe.getConfig(); - sessionStorage.setItem("recipeConfig", JSON.stringify(recipeConfig)); - return recipeConfig; -}; - - -/** - * Given a recipe configuration, sets the recipe to that configuration. - * - * @param {Object[]} recipeConfig - The recipe configuration - */ -App.prototype.setRecipeConfig = function(recipeConfig) { - sessionStorage.setItem("recipeConfig", JSON.stringify(recipeConfig)); - document.getElementById("rec-list").innerHTML = null; - - for (let i = 0; i < recipeConfig.length; i++) { - const item = this.manager.recipe.addOperation(recipeConfig[i].op); - - // Populate arguments - const args = item.querySelectorAll(".arg"); - for (let j = 0; j < args.length; j++) { - if (args[j].getAttribute("type") === "checkbox") { - // checkbox - args[j].checked = recipeConfig[i].args[j]; - } else if (args[j].classList.contains("toggle-string")) { - // toggleString - args[j].value = recipeConfig[i].args[j].string; - args[j].previousSibling.children[0].innerHTML = - Utils.escapeHtml(recipeConfig[i].args[j].option) + - " "; - } else { - // all others - args[j].value = recipeConfig[i].args[j]; - } - } - - // Set disabled and breakpoint - if (recipeConfig[i].disabled) { - item.querySelector(".disable-icon").click(); - } - if (recipeConfig[i].breakpoint) { - item.querySelector(".breakpoint").click(); - } - - this.progress = 0; - } -}; - - -/** - * Resets the splitter positions to default. - */ -App.prototype.resetLayout = function() { - this.columnSplitter.setSizes([20, 30, 50]); - this.ioSplitter.setSizes([50, 50]); - - this.manager.controls.adjustWidth(); - this.manager.output.adjustWidth(); -}; - - -/** - * Sets the compile message. - */ -App.prototype.setCompileMessage = function() { - // Display time since last build and compile message - let now = new Date(), - timeSinceCompile = Utils.fuzzyTime(now.getTime() - window.compileTime), - compileInfo = "Last build: " + - timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1) + " ago"; - - if (window.compileMessage !== "") { - compileInfo += " - " + window.compileMessage; - } - - compileInfo += ""; - document.getElementById("notice").innerHTML = compileInfo; -}; - - -/** - * Pops up a message to the user and writes it to the console log. - * - * @param {string} str - The message to display (HTML supported) - * @param {string} style - The colour of the popup - * "danger" = red - * "warning" = amber - * "info" = blue - * "success" = green - * @param {number} timeout - The number of milliseconds before the popup 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 - * - * @example - * // Pops up a red box with the message "[current time] Error: Something has gone wrong!" - * // that will need to be dismissed by the user. - * this.alert("Error: Something has gone wrong!", "danger", 0); - * - * // Pops up a blue information box with the message "[current time] Happy Christmas!" - * // that will disappear after 5 seconds. - * this.alert("Happy Christmas!", "info", 5000); - */ -App.prototype.alert = function(str, style, timeout, silent) { - const time = new Date(); - - console.log("[" + time.toLocaleString() + "] " + str); - if (silent) return; - - style = style || "danger"; - timeout = timeout || 0; - - let alertEl = document.getElementById("alert"), - alertContent = document.getElementById("alert-content"); - - alertEl.classList.remove("alert-danger"); - alertEl.classList.remove("alert-warning"); - alertEl.classList.remove("alert-info"); - alertEl.classList.remove("alert-success"); - alertEl.classList.add("alert-" + style); - - // If the box hasn't been closed, append to it rather than replacing - if (alertEl.style.display === "block") { - alertContent.innerHTML += - "

[" + time.toLocaleTimeString() + "] " + str; - } else { - alertContent.innerHTML = - "[" + time.toLocaleTimeString() + "] " + str; - } - - // Stop the animation if it is in progress - $("#alert").stop(); - alertEl.style.display = "block"; - alertEl.style.opacity = 1; - - if (timeout > 0) { - clearTimeout(this.alertTimeout); - this.alertTimeout = setTimeout(function(){ - $("#alert").slideUp(100); - }, timeout); - } -}; - - -/** - * Pops up a box asking the user a question and sending the answer to a specified callback function. - * - * @param {string} title - The title of the box - * @param {string} body - The question (HTML supported) - * @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);}); - */ -App.prototype.confirm = function(title, body, callback, scope) { - scope = scope || this; - document.getElementById("confirm-title").innerHTML = title; - document.getElementById("confirm-body").innerHTML = body; - document.getElementById("confirm-modal").style.display = "block"; - - this.confirmClosed = false; - $("#confirm-modal").modal() - .one("show.bs.modal", function(e) { - this.confirmClosed = false; - }.bind(this)) - .one("click", "#confirm-yes", function() { - this.confirmClosed = true; - callback.bind(scope)(true); - $("#confirm-modal").modal("hide"); - }.bind(this)) - .one("hide.bs.modal", function(e) { - if (!this.confirmClosed) - callback.bind(scope)(false); - this.confirmClosed = true; - }.bind(this)); -}; - - -/** - * Handler for the alert close button click event. - * Closes the alert box. - */ -App.prototype.alertCloseClick = function() { - document.getElementById("alert").style.display = "none"; -}; - - -/** - * Handler for CyerChef statechange events. - * Fires whenever the input or recipe changes in any way. - * - * @listens Manager#statechange - * @param {event} e - */ -App.prototype.stateChange = function(e) { - this.autoBake(); - - // Update the current history state (not creating a new one) - if (this.options.updateUrl) { - this.lastStateUrl = this.manager.controls.generateStateUrl(true, true); - window.history.replaceState({}, "CyberChef", this.lastStateUrl); - } -}; - - -/** - * Handler for the history popstate event. - * Reloads parameters from the URL. - * - * @param {event} e - */ -App.prototype.popState = function(e) { - if (window.location.href.split("#")[0] !== this.lastStateUrl) { - this.loadURIParams(); - } -}; - - -/** - * Function to call an external API from this view. - */ -App.prototype.callApi = function(url, type, data, dataType, contentType) { - type = type || "POST"; - data = data || {}; - dataType = dataType || undefined; - contentType = contentType || "application/json"; - - let response = null, - success = false; - - $.ajax({ - url: url, - async: false, - type: type, - data: data, - dataType: dataType, - contentType: contentType, - success: function(data) { - success = true; - response = data; - }, - error: function(data) { - success = false; - response = data; - }, - }); - - return { - success: success, - response: response - }; -}; - -export default App; diff --git a/src/web/App.mjs b/src/web/App.mjs new file mode 100755 index 00000000..846803a1 --- /dev/null +++ b/src/web/App.mjs @@ -0,0 +1,688 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "../core/Utils"; +import {fromBase64} from "../core/lib/Base64"; +import Manager from "./Manager"; +import HTMLCategory from "./HTMLCategory"; +import HTMLOperation from "./HTMLOperation"; +import Split from "split.js"; +import moment from "moment-timezone"; + + +/** + * HTML view for CyberChef responsible for building the web page and dealing with all user + * interactions. + */ +class App { + + /** + * App constructor. + * + * @param {CatConf[]} categories - The list of categories and operations to be populated. + * @param {Object.} operations - The list of operation configuration objects. + * @param {String[]} defaultFavourites - A list of default favourite operations. + * @param {Object} options - Default setting for app options. + */ + constructor(categories, operations, defaultFavourites, defaultOptions) { + this.categories = categories; + this.operations = operations; + this.dfavourites = defaultFavourites; + this.doptions = defaultOptions; + this.options = Object.assign({}, defaultOptions); + + this.manager = new Manager(this); + + this.baking = false; + this.autoBake_ = false; + this.autoBakePause = false; + this.progress = 0; + this.ingId = 0; + } + + + /** + * This function sets up the stage and creates listeners for all events. + * + * @fires Manager#appstart + */ + setup() { + document.dispatchEvent(this.manager.appstart); + this.initialiseSplitter(); + this.loadLocalStorage(); + this.populateOperationsList(); + this.manager.setup(); + this.resetLayout(); + this.setCompileMessage(); + + log.debug("App loaded"); + this.appLoaded = true; + + this.loadURIParams(); + this.loaded(); + } + + + /** + * Fires once all setup activities have completed. + * + * @fires Manager#apploaded + */ + loaded() { + // Check that both the app and the worker have loaded successfully, and that + // we haven't already loaded before attempting to remove the loading screen. + if (!this.workerLoaded || !this.appLoaded || + !document.getElementById("loader-wrapper")) return; + + // Trigger CSS animations to remove preloader + document.body.classList.add("loaded"); + + // Wait for animations to complete then remove the preloader and loaded style + // so that the animations for existing elements don't play again. + setTimeout(function() { + document.getElementById("loader-wrapper").remove(); + document.body.classList.remove("loaded"); + }, 1000); + + // Clear the loading message interval + clearInterval(window.loadingMsgsInt); + + // Remove the loading error handler + window.removeEventListener("error", window.loadingErrorHandler); + + document.dispatchEvent(this.manager.apploaded); + } + + + /** + * An error handler for displaying the error to the user. + * + * @param {Error} err + * @param {boolean} [logToConsole=false] + */ + handleError(err, logToConsole) { + if (logToConsole) log.error(err); + const msg = err.displayStr || err.toString(); + this.alert(msg, this.options.errorTimeout, !this.options.showErrors); + } + + + /** + * Asks the ChefWorker to bake the current input using the current recipe. + * + * @param {boolean} [step] - Set to true if we should only execute one operation instead of the + * whole recipe. + */ + bake(step=false) { + if (this.baking) return; + + // Reset attemptHighlight flag + this.options.attemptHighlight = true; + + this.manager.worker.bake( + this.getInput(), // The user's input + this.getRecipeConfig(), // The configuration of the recipe + this.options, // Options set by the user + this.progress, // The current position in the recipe + step // Whether or not to take one step or execute the whole recipe + ); + } + + + /** + * Runs Auto Bake if it is set. + */ + autoBake() { + // If autoBakePause is set, we are loading a full recipe (and potentially input), so there is no + // need to set the staleness indicator. Just exit and wait until auto bake is called after loading + // has completed. + if (this.autoBakePause) return false; + + if (this.autoBake_ && !this.baking) { + log.debug("Auto-baking"); + this.bake(); + } else { + this.manager.controls.showStaleIndicator(); + } + } + + + /** + * Runs a silent bake, forcing the browser to load and cache all the relevant JavaScript code needed + * to do a real bake. + * + * The output will not be modified (hence "silent" bake). This will only actually execute the recipe + * if auto-bake is enabled, otherwise it will just wake up the ChefWorker with an empty recipe. + */ + silentBake() { + let recipeConfig = []; + + if (this.autoBake_) { + // If auto-bake is not enabled we don't want to actually run the recipe as it may be disabled + // for a good reason. + recipeConfig = this.getRecipeConfig(); + } + + this.manager.worker.silentBake(recipeConfig); + } + + + /** + * Gets the user's input data. + * + * @returns {string} + */ + getInput() { + return this.manager.input.get(); + } + + + /** + * Sets the user's input data. + * + * @param {string} input - The string to set the input to + * @param {boolean} [silent=false] - Suppress statechange event + */ + setInput(input, silent=false) { + this.manager.input.set(input, silent); + } + + + /** + * Populates the operations accordion list with the categories and operations specified in the + * view constructor. + * + * @fires Manager#oplistcreate + */ + populateOperationsList() { + // Move edit button away before we overwrite it + document.body.appendChild(document.getElementById("edit-favourites")); + + let html = ""; + let i; + + for (i = 0; i < this.categories.length; i++) { + const catConf = this.categories[i], + selected = i === 0, + cat = new HTMLCategory(catConf.name, selected); + + for (let j = 0; j < catConf.ops.length; j++) { + const opName = catConf.ops[j]; + if (!this.operations.hasOwnProperty(opName)) { + log.warn(`${opName} could not be found.`); + continue; + } + + const op = new HTMLOperation(opName, this.operations[opName], this, this.manager); + cat.addOperation(op); + } + + html += cat.toHtml(); + } + + document.getElementById("categories").innerHTML = html; + + const opLists = document.querySelectorAll("#categories .op-list"); + + for (i = 0; i < opLists.length; i++) { + opLists[i].dispatchEvent(this.manager.oplistcreate); + } + + // Add edit button to first category (Favourites) + document.querySelector("#categories a").appendChild(document.getElementById("edit-favourites")); + } + + + /** + * Sets up the adjustable splitter to allow the user to resize areas of the page. + * + * @param {boolean} [minimise=false] - Set this flag if attempting to minimuse frames to 0 width + */ + initialiseSplitter(minimise=false) { + if (this.columnSplitter) this.columnSplitter.destroy(); + if (this.ioSplitter) this.ioSplitter.destroy(); + + this.columnSplitter = Split(["#operations", "#recipe", "#IO"], { + sizes: [20, 30, 50], + minSize: minimise ? [0, 0, 0] : [240, 370, 450], + gutterSize: 4, + expandToMin: false, + onDrag: function() { + this.manager.recipe.adjustWidth(); + }.bind(this) + }); + + this.ioSplitter = Split(["#input", "#output"], { + direction: "vertical", + gutterSize: 4, + minSize: minimise ? [0, 0] : [100, 100] + }); + + this.resetLayout(); + } + + + /** + * Loads the information previously saved to the HTML5 local storage object so that user options + * and favourites can be restored. + */ + loadLocalStorage() { + // Load options + let lOptions; + if (this.isLocalStorageAvailable() && localStorage.options !== undefined) { + lOptions = JSON.parse(localStorage.options); + } + this.manager.options.load(lOptions); + + // Load favourites + this.loadFavourites(); + } + + + /** + * Loads the user's favourite operations from the HTML5 local storage object and populates the + * Favourites category with them. + * If the user currently has no saved favourites, the defaults from the view constructor are used. + */ + loadFavourites() { + let favourites; + + if (this.isLocalStorageAvailable()) { + favourites = localStorage.favourites && localStorage.favourites.length > 2 ? + JSON.parse(localStorage.favourites) : + this.dfavourites; + favourites = this.validFavourites(favourites); + this.saveFavourites(favourites); + } else { + favourites = this.dfavourites; + } + + const favCat = this.categories.filter(function(c) { + return c.name === "Favourites"; + })[0]; + + if (favCat) { + favCat.ops = favourites; + } else { + this.categories.unshift({ + name: "Favourites", + ops: favourites + }); + } + } + + + /** + * Filters the list of favourite operations that the user had stored and removes any that are no + * longer available. The user is notified if this is the case. + + * @param {string[]} favourites - A list of the user's favourite operations + * @returns {string[]} A list of the valid favourites + */ + validFavourites(favourites) { + const validFavs = []; + for (let i = 0; i < favourites.length; i++) { + if (this.operations.hasOwnProperty(favourites[i])) { + validFavs.push(favourites[i]); + } else { + this.alert(`The operation "${Utils.escapeHtml(favourites[i])}" is no longer available. ` + + "It has been removed from your favourites."); + } + } + return validFavs; + } + + + /** + * Saves a list of favourite operations to the HTML5 local storage object. + * + * @param {string[]} favourites - A list of the user's favourite operations + */ + saveFavourites(favourites) { + if (!this.isLocalStorageAvailable()) { + this.alert( + "Your security settings do not allow access to local storage so your favourites cannot be saved.", + 5000 + ); + return false; + } + + localStorage.setItem("favourites", JSON.stringify(this.validFavourites(favourites))); + } + + + /** + * Resets favourite operations back to the default as specified in the view constructor and + * refreshes the operation list. + */ + resetFavourites() { + this.saveFavourites(this.dfavourites); + this.loadFavourites(); + this.populateOperationsList(); + this.manager.recipe.initialiseOperationDragNDrop(); + } + + + /** + * Adds an operation to the user's favourites. + * + * @param {string} name - The name of the operation + */ + addFavourite(name) { + const favourites = JSON.parse(localStorage.favourites); + + if (favourites.indexOf(name) >= 0) { + this.alert(`'${name}' is already in your favourites`, 3000); + return; + } + + favourites.push(name); + this.saveFavourites(favourites); + this.loadFavourites(); + this.populateOperationsList(); + this.manager.recipe.initialiseOperationDragNDrop(); + } + + + /** + * Checks for input and recipe in the URI parameters and loads them if present. + */ + loadURIParams() { + // Load query string or hash from URI (depending on which is populated) + // We prefer getting the hash by splitting the href rather than referencing + // location.hash as some browsers (Firefox) automatically URL decode it, + // which cause issues. + const params = window.location.search || + window.location.href.split("#")[1] || + window.location.hash; + this.uriParams = Utils.parseURIParams(params); + this.autoBakePause = true; + + // Read in recipe from URI params + if (this.uriParams.recipe) { + try { + const recipeConfig = Utils.parseRecipeConfig(this.uriParams.recipe); + this.setRecipeConfig(recipeConfig); + } catch (err) {} + } else if (this.uriParams.op) { + // If there's no recipe, look for single operations + this.manager.recipe.clearRecipe(); + + // Search for nearest match and add it + const matchedOps = this.manager.ops.filterOperations(this.uriParams.op, false); + if (matchedOps.length) { + this.manager.recipe.addOperation(matchedOps[0].name); + } + + // Populate search with the string + const search = document.getElementById("search"); + + search.value = this.uriParams.op; + search.dispatchEvent(new Event("search")); + } + + // Read in input data from URI params + if (this.uriParams.input) { + try { + const inputData = fromBase64(this.uriParams.input); + this.setInput(inputData, true); + } catch (err) {} + } + + this.autoBakePause = false; + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Returns the next ingredient ID and increments it for next time. + * + * @returns {number} + */ + nextIngId() { + return this.ingId++; + } + + + /** + * Gets the current recipe configuration. + * + * @returns {Object[]} + */ + getRecipeConfig() { + return this.manager.recipe.getConfig(); + } + + + /** + * Given a recipe configuration, sets the recipe to that configuration. + * + * @fires Manager#statechange + * @param {Object[]} recipeConfig - The recipe configuration + */ + setRecipeConfig(recipeConfig) { + document.getElementById("rec-list").innerHTML = null; + + // Pause auto-bake while loading but don't modify `this.autoBake_` + // otherwise `manualBake` cannot trigger. + this.autoBakePause = true; + + for (let i = 0; i < recipeConfig.length; i++) { + const item = this.manager.recipe.addOperation(recipeConfig[i].op); + + // Populate arguments + const args = item.querySelectorAll(".arg"); + for (let j = 0; j < args.length; j++) { + if (recipeConfig[i].args[j] === undefined) continue; + if (args[j].getAttribute("type") === "checkbox") { + // checkbox + args[j].checked = recipeConfig[i].args[j]; + } else if (args[j].classList.contains("toggle-string")) { + // toggleString + args[j].value = recipeConfig[i].args[j].string; + args[j].parentNode.parentNode.querySelector("button").innerHTML = + Utils.escapeHtml(recipeConfig[i].args[j].option); + } else { + // all others + args[j].value = recipeConfig[i].args[j]; + } + } + + // Set disabled and breakpoint + if (recipeConfig[i].disabled) { + item.querySelector(".disable-icon").click(); + } + if (recipeConfig[i].breakpoint) { + item.querySelector(".breakpoint").click(); + } + + this.progress = 0; + } + + // Unpause auto bake + this.autoBakePause = false; + } + + + /** + * Resets the splitter positions to default. + */ + resetLayout() { + this.columnSplitter.setSizes([20, 30, 50]); + this.ioSplitter.setSizes([50, 50]); + this.manager.recipe.adjustWidth(); + } + + + /** + * Sets the compile message. + */ + setCompileMessage() { + // Display time since last build and compile message + const now = new Date(), + msSinceCompile = now.getTime() - window.compileTime, + timeSinceCompile = moment.duration(msSinceCompile, "milliseconds").humanize(); + + // Calculate previous version to compare to + const prev = PKG_VERSION.split(".").map(n => { + return parseInt(n, 10); + }); + if (prev[2] > 0) prev[2]--; + else if (prev[1] > 0) prev[1]--; + else prev[0]--; + + //const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`; + + let compileInfo = `Last build: ${timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1)} ago`; + + if (window.compileMessage !== "") { + compileInfo += " - " + window.compileMessage; + } + + const notice = document.getElementById("notice"); + notice.innerHTML = compileInfo; + notice.setAttribute("title", Utils.stripHtmlTags(window.compileMessage)); + } + + + /** + * Determines whether the browser supports Local Storage and if it is accessible. + * + * @returns {boolean} + */ + isLocalStorageAvailable() { + try { + if (!localStorage) return false; + return true; + } catch (err) { + // Access to LocalStorage is denied + return false; + } + } + + + /** + * 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 + * 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 + * + * @example + * // Pops up a box with the message "Error: Something has gone wrong!" that will need to be + * // dismissed by the user. + * this.alert("Error: Something has gone wrong!", 0); + * + * // Pops up a box with the message "Happy Christmas!" that will disappear after 5 seconds. + * this.alert("Happy Christmas!", 5000); + */ + alert(str, timeout, silent) { + const time = new Date(); + + log.info("[" + time.toLocaleString() + "] " + str); + if (silent) return; + + timeout = timeout || 0; + + this.currentSnackbar = $.snackbar({ + content: str, + timeout: timeout, + htmlAllowed: true, + onClose: () => { + this.currentSnackbar.remove(); + } + }); + } + + + /** + * Pops up a box asking the user a question and sending the answer to a specified callback function. + * + * @param {string} title - The title of the box + * @param {string} body - The question (HTML supported) + * @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);}); + */ + confirm(title, body, callback, scope) { + scope = scope || this; + document.getElementById("confirm-title").innerHTML = title; + document.getElementById("confirm-body").innerHTML = body; + document.getElementById("confirm-modal").style.display = "block"; + + this.confirmClosed = false; + $("#confirm-modal").modal() + .one("show.bs.modal", function(e) { + this.confirmClosed = false; + }.bind(this)) + .one("click", "#confirm-yes", function() { + this.confirmClosed = true; + callback.bind(scope)(true); + $("#confirm-modal").modal("hide"); + }.bind(this)) + .one("hide.bs.modal", function(e) { + if (!this.confirmClosed) + callback.bind(scope)(false); + this.confirmClosed = true; + }.bind(this)); + } + + + /** + * Handler for CyerChef statechange events. + * Fires whenever the input or recipe changes in any way. + * + * @listens Manager#statechange + * @param {event} e + */ + stateChange(e) { + this.progress = 0; + this.autoBake(); + + // Set title + const recipeConfig = this.getRecipeConfig(); + let title = "CyberChef"; + if (recipeConfig.length === 1) { + title = `${recipeConfig[0].op} - ${title}`; + } else if (recipeConfig.length > 1) { + // See how long the full recipe is + const ops = recipeConfig.map(op => op.op).join(", "); + if (ops.length < 45) { + title = `${ops} - ${title}`; + } else { + // If it's too long, just use the first one and say how many more there are + title = `${recipeConfig[0].op}, ${recipeConfig.length - 1} more - ${title}`; + } + } + document.title = title; + + // Update the current history state (not creating a new one) + if (this.options.updateUrl) { + this.lastStateUrl = this.manager.controls.generateStateUrl(true, true, recipeConfig); + window.history.replaceState({}, title, this.lastStateUrl); + } + } + + + /** + * Handler for the history popstate event. + * Reloads parameters from the URL. + * + * @param {event} e + */ + popState(e) { + this.loadURIParams(); + } + +} + +export default App; diff --git a/src/web/BackgroundWorkerWaiter.mjs b/src/web/BackgroundWorkerWaiter.mjs new file mode 100644 index 00000000..b7b259be --- /dev/null +++ b/src/web/BackgroundWorkerWaiter.mjs @@ -0,0 +1,157 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker"; + +/** + * Waiter to handle conversations with a ChefWorker in the background. + */ +class BackgroundWorkerWaiter { + + /** + * BackgroundWorkerWaiter 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; + + this.callbacks = {}; + this.callbackID = 0; + this.completedCallback = -1; + this.timeout = null; + } + + + /** + * Sets up the ChefWorker and associated listeners. + */ + registerChefWorker() { + log.debug("Registering new background ChefWorker"); + this.chefWorker = new ChefWorker(); + this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this)); + + let docURL = document.location.href.split(/[#?]/)[0]; + const index = docURL.lastIndexOf("/"); + if (index > 0) { + docURL = docURL.substring(0, index); + } + this.chefWorker.postMessage({"action": "docURL", "data": docURL}); + } + + + /** + * Handler for messages sent back by the ChefWorker. + * + * @param {MessageEvent} e + */ + handleChefMessage(e) { + const r = e.data; + log.debug("Receiving '" + r.action + "' from ChefWorker in the background"); + + switch (r.action) { + case "bakeComplete": + case "bakeError": + if (typeof r.data.id !== "undefined") { + clearTimeout(this.timeout); + this.callbacks[r.data.id].bind(this)(r.data); + this.completedCallback = r.data.id; + } + break; + case "workerLoaded": + log.debug("Background ChefWorker loaded"); + break; + case "optionUpdate": + case "statusMessage": + // Ignore these messages + break; + default: + log.error("Unrecognised message from background ChefWorker", e); + break; + } + } + + + /** + * Cancels the current bake by terminating the ChefWorker and creating a new one. + */ + cancelBake() { + if (this.chefWorker) + this.chefWorker.terminate(); + this.registerChefWorker(); + } + + + /** + * Asks the ChefWorker to bake the input using the specified recipe. + * + * @param {string} input + * @param {Object[]} recipeConfig + * @param {Object} options + * @param {number} progress + * @param {boolean} step + * @param {Function} callback + */ + bake(input, recipeConfig, options, progress, step, callback) { + const id = this.callbackID++; + this.callbacks[id] = callback; + + this.chefWorker.postMessage({ + action: "bake", + data: { + input: input, + recipeConfig: recipeConfig, + options: options, + progress: progress, + step: step, + id: id + } + }); + } + + + /** + * Asks the Magic operation what it can do with the input data. + * + * @param {string|ArrayBuffer} input + */ + magic(input) { + // If we're still working on the previous bake, cancel it before starting a new one. + if (this.completedCallback + 1 < this.callbackID) { + clearTimeout(this.timeout); + this.cancelBake(); + } + + this.bake(input, [ + { + "op": "Magic", + "args": [3, false, false] + } + ], {}, 0, false, this.magicComplete); + + // Cancel this bake if it takes too long. + this.timeout = setTimeout(this.cancelBake.bind(this), 3000); + } + + + /** + * Handler for completed Magic bakes. + * + * @param {Object} response + */ + magicComplete(response) { + log.debug("--- Background Magic Bake complete ---"); + if (!response || response.error) return; + + this.manager.output.backgroundMagicResult(response.dish.value); + } + +} + + +export default BackgroundWorkerWaiter; diff --git a/src/web/BindingsWaiter.mjs b/src/web/BindingsWaiter.mjs new file mode 100755 index 00000000..74262c61 --- /dev/null +++ b/src/web/BindingsWaiter.mjs @@ -0,0 +1,224 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.) + */ +class BindingsWaiter { + + /** + * BindingsWaiter 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; + } + + + /** + * Handler for all keydown events + * Checks whether valid keyboard shortcut has been instated + * + * @fires Manager#statechange + * @param {event} e + */ + parseInput(e) { + const modKey = this.app.options.useMetaKey ? e.metaKey : e.altKey; + + if (e.ctrlKey && modKey) { + let elem; + switch (e.code) { + case "KeyF": // Focus search + e.preventDefault(); + document.getElementById("search").focus(); + break; + case "KeyI": // Focus input + e.preventDefault(); + document.getElementById("input-text").focus(); + break; + case "KeyO": // Focus output + e.preventDefault(); + document.getElementById("output-text").focus(); + break; + case "Period": // Focus next operation + e.preventDefault(); + try { + elem = document.activeElement.closest(".operation") || document.querySelector("#rec-list .operation"); + if (elem.parentNode.lastChild === elem) { + // If operation is last in recipe, loop around to the top operation's first argument + elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus(); + } else { + // Focus first argument of next operation + elem.nextSibling.querySelectorAll(".arg")[0].focus(); + } + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "KeyB": // Set breakpoint + e.preventDefault(); + try { + elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0]; + if (elem.getAttribute("break") === "false") { + elem.setAttribute("break", "true"); // add break point if not already enabled + elem.classList.add("breakpoint-selected"); + } else { + elem.setAttribute("break", "false"); // remove break point if already enabled + elem.classList.remove("breakpoint-selected"); + } + window.dispatchEvent(this.manager.statechange); + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "KeyD": // Disable operation + e.preventDefault(); + try { + elem = document.activeElement.closest(".operation").querySelectorAll(".disable-icon")[0]; + if (elem.getAttribute("disabled") === "false") { + elem.setAttribute("disabled", "true"); // disable operation if enabled + elem.classList.add("disable-elem-selected"); + elem.parentNode.parentNode.classList.add("disabled"); + } else { + elem.setAttribute("disabled", "false"); // enable operation if disabled + elem.classList.remove("disable-elem-selected"); + elem.parentNode.parentNode.classList.remove("disabled"); + } + this.app.progress = 0; + window.dispatchEvent(this.manager.statechange); + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "Space": // Bake + e.preventDefault(); + this.app.bake(); + break; + case "Quote": // Step through + e.preventDefault(); + this.app.bake(true); + break; + case "KeyC": // Clear recipe + e.preventDefault(); + this.manager.recipe.clearRecipe(); + break; + case "KeyS": // Save output to file + e.preventDefault(); + this.manager.output.saveClick(); + break; + case "KeyL": // Load recipe + e.preventDefault(); + this.manager.controls.loadClick(); + break; + case "KeyM": // Switch input and output + e.preventDefault(); + this.manager.output.switchClick(); + break; + default: + if (e.code.match(/Digit[0-9]/g)) { // Select nth operation + e.preventDefault(); + try { + // Select the first argument of the operation corresponding to the number pressed + document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus(); + } catch (e) { + // do nothing, just don't throw an error + } + } + break; + } + } + } + + + /** + * Updates keybinding list when metaKey option is toggled + */ + updateKeybList() { + let modWinLin = "Alt"; + let modMac = "Opt"; + if (this.app.options.useMetaKey) { + modWinLin = "Win"; + modMac = "Cmd"; + } + document.getElementById("keybList").innerHTML = ` + + Command + Shortcut (Win/Linux) + Shortcut (Mac) + + + Place cursor in search field + Ctrl+${modWinLin}+f + Ctrl+${modMac}+f + + Place cursor in input box + Ctrl+${modWinLin}+i + Ctrl+${modMac}+i + + + Place cursor in output box + Ctrl+${modWinLin}+o + Ctrl+${modMac}+o + + + Place cursor in first argument field of the next operation in the recipe + Ctrl+${modWinLin}+. + Ctrl+${modMac}+. + + + Place cursor in first argument field of the nth operation in the recipe + Ctrl+${modWinLin}+[1-9] + Ctrl+${modMac}+[1-9] + + + Disable current operation + Ctrl+${modWinLin}+d + Ctrl+${modMac}+d + + + Set/clear breakpoint + Ctrl+${modWinLin}+b + Ctrl+${modMac}+b + + + Bake + Ctrl+${modWinLin}+Space + Ctrl+${modMac}+Space + + + Step + Ctrl+${modWinLin}+' + Ctrl+${modMac}+' + + + Clear recipe + Ctrl+${modWinLin}+c + Ctrl+${modMac}+c + + + Save to file + Ctrl+${modWinLin}+s + Ctrl+${modMac}+s + + + Load recipe + Ctrl+${modWinLin}+l + Ctrl+${modMac}+l + + + Move output to input + Ctrl+${modWinLin}+m + Ctrl+${modMac}+m + + `; + } + +} + +export default BindingsWaiter; diff --git a/src/web/ControlsWaiter.js b/src/web/ControlsWaiter.js deleted file mode 100755 index 3ba4f86b..00000000 --- a/src/web/ControlsWaiter.js +++ /dev/null @@ -1,363 +0,0 @@ -import Utils from "../core/Utils.js"; - - -/** - * Waiter to handle events related to the CyberChef controls (i.e. Bake, Step, Save, Load etc.) - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const ControlsWaiter = function(app, manager) { - this.app = app; - this.manager = manager; -}; - - -/** - * Adjusts the display properties of the control buttons so that they fit within the current width - * without wrapping or overflowing. - */ -ControlsWaiter.prototype.adjustWidth = function() { - const controls = document.getElementById("controls"); - const step = document.getElementById("step"); - const clrBreaks = document.getElementById("clr-breaks"); - const saveImg = document.querySelector("#save img"); - const loadImg = document.querySelector("#load img"); - const stepImg = document.querySelector("#step img"); - const clrRecipImg = document.querySelector("#clr-recipe img"); - const clrBreaksImg = document.querySelector("#clr-breaks img"); - - if (controls.clientWidth < 470) { - step.childNodes[1].nodeValue = " Step"; - } else { - step.childNodes[1].nodeValue = " Step through"; - } - - if (controls.clientWidth < 400) { - saveImg.style.display = "none"; - loadImg.style.display = "none"; - stepImg.style.display = "none"; - clrRecipImg.style.display = "none"; - clrBreaksImg.style.display = "none"; - } else { - saveImg.style.display = "inline"; - loadImg.style.display = "inline"; - stepImg.style.display = "inline"; - clrRecipImg.style.display = "inline"; - clrBreaksImg.style.display = "inline"; - } - - if (controls.clientWidth < 330) { - clrBreaks.childNodes[1].nodeValue = " Clear breaks"; - } else { - clrBreaks.childNodes[1].nodeValue = " Clear breakpoints"; - } -}; - - -/** - * Checks or unchecks the Auto Bake checkbox based on the given value. - * - * @param {boolean} value - The new value for Auto Bake. - */ -ControlsWaiter.prototype.setAutoBake = function(value) { - const autoBakeCheckbox = document.getElementById("auto-bake"); - - if (autoBakeCheckbox.checked !== value) { - autoBakeCheckbox.click(); - } -}; - - -/** - * Handler to trigger baking. - */ -ControlsWaiter.prototype.bakeClick = function() { - this.app.bake(); - const outputText = document.getElementById("output-text"); - outputText.focus(); - outputText.setSelectionRange(0, 0); -}; - - -/** - * Handler for the 'Step through' command. Executes the next step of the recipe. - */ -ControlsWaiter.prototype.stepClick = function() { - this.app.bake(true); - const outputText = document.getElementById("output-text"); - outputText.focus(); - outputText.setSelectionRange(0, 0); -}; - - -/** - * Handler for changes made to the Auto Bake checkbox. - */ -ControlsWaiter.prototype.autoBakeChange = function() { - const autoBakeLabel = document.getElementById("auto-bake-label"); - const autoBakeCheckbox = document.getElementById("auto-bake"); - - this.app.autoBake_ = autoBakeCheckbox.checked; - - if (autoBakeCheckbox.checked) { - autoBakeLabel.classList.add("btn-success"); - autoBakeLabel.classList.remove("btn-default"); - } else { - autoBakeLabel.classList.add("btn-default"); - autoBakeLabel.classList.remove("btn-success"); - } -}; - - -/** - * Handler for the 'Clear recipe' command. Removes all operations from the recipe. - */ -ControlsWaiter.prototype.clearRecipeClick = function() { - this.manager.recipe.clearRecipe(); -}; - - -/** - * Handler for the 'Clear breakpoints' command. Removes all breakpoints from operations in the - * recipe. - */ -ControlsWaiter.prototype.clearBreaksClick = function() { - const bps = document.querySelectorAll("#rec-list li.operation .breakpoint"); - - for (let i = 0; i < bps.length; i++) { - bps[i].setAttribute("break", "false"); - bps[i].classList.remove("breakpoint-selected"); - } -}; - - -/** - * Populates the save disalog box with a URL incorporating the recipe and input. - * - * @param {Object[]} [recipeConfig] - The recipe configuration object array. - */ -ControlsWaiter.prototype.initialiseSaveLink = function(recipeConfig) { - recipeConfig = recipeConfig || this.app.getRecipeConfig(); - - const includeRecipe = document.getElementById("save-link-recipe-checkbox").checked; - const includeInput = document.getElementById("save-link-input-checkbox").checked; - const saveLinkEl = document.getElementById("save-link"); - const saveLink = this.generateStateUrl(includeRecipe, includeInput, recipeConfig); - - saveLinkEl.innerHTML = Utils.truncate(saveLink, 120); - saveLinkEl.setAttribute("href", saveLink); -}; - - -/** - * Generates a URL containing the current recipe and input state. - * - * @param {boolean} includeRecipe - Whether to include the recipe in the URL. - * @param {boolean} includeInput - Whether to include the input in the URL. - * @param {Object[]} [recipeConfig] - The recipe configuration object array. - * @param {string} [baseURL] - The CyberChef URL, set to the current URL if not included - * @returns {string} - */ -ControlsWaiter.prototype.generateStateUrl = function(includeRecipe, includeInput, recipeConfig, baseURL) { - recipeConfig = recipeConfig || this.app.getRecipeConfig(); - - const link = baseURL || window.location.protocol + "//" + - window.location.host + - window.location.pathname; - const recipeStr = JSON.stringify(recipeConfig); - const inputStr = Utils.toBase64(this.app.getInput(), "A-Za-z0-9+/"); // B64 alphabet with no padding - - includeRecipe = includeRecipe && (recipeConfig.length > 0); - includeInput = includeInput && (inputStr.length > 0) && (inputStr.length < 8000); - - const params = [ - includeRecipe ? ["recipe", recipeStr] : undefined, - includeInput ? ["input", inputStr] : undefined, - ]; - - const query = params - .filter(v => v) - .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) - .join("&"); - - if (query) { - return `${link}?${query}`; - } - - return link; -}; - - -/** - * Handler for changes made to the save dialog text area. Re-initialises the save link. - */ -ControlsWaiter.prototype.saveTextChange = function() { - try { - const recipeConfig = JSON.parse(document.getElementById("save-text").value); - this.initialiseSaveLink(recipeConfig); - } catch (err) {} -}; - - -/** - * Handler for the 'Save' command. Pops up the save dialog box. - */ -ControlsWaiter.prototype.saveClick = function() { - const recipeConfig = this.app.getRecipeConfig(); - const recipeStr = JSON.stringify(recipeConfig).replace(/},{/g, "},\n{"); - - document.getElementById("save-text").value = recipeStr; - - this.initialiseSaveLink(recipeConfig); - $("#save-modal").modal(); -}; - - -/** - * Handler for the save link recipe checkbox change event. - */ -ControlsWaiter.prototype.slrCheckChange = function() { - this.initialiseSaveLink(); -}; - - -/** - * Handler for the save link input checkbox change event. - */ -ControlsWaiter.prototype.sliCheckChange = function() { - this.initialiseSaveLink(); -}; - - -/** - * Handler for the 'Load' command. Pops up the load dialog box. - */ -ControlsWaiter.prototype.loadClick = function() { - this.populateLoadRecipesList(); - $("#load-modal").modal(); -}; - - -/** - * Saves the recipe specified in the save textarea to local storage. - */ -ControlsWaiter.prototype.saveButtonClick = function() { - const recipeName = Utils.escapeHtml(document.getElementById("save-name").value); - const recipeStr = document.getElementById("save-text").value; - - if (!recipeName) { - this.app.alert("Please enter a recipe name", "danger", 2000); - return; - } - - let savedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : [], - recipeId = localStorage.recipeId || 0; - - savedRecipes.push({ - id: ++recipeId, - name: recipeName, - recipe: recipeStr - }); - - localStorage.savedRecipes = JSON.stringify(savedRecipes); - localStorage.recipeId = recipeId; - - this.app.alert("Recipe saved as \"" + recipeName + "\".", "success", 2000); -}; - - -/** - * Populates the list of saved recipes in the load dialog box from local storage. - */ -ControlsWaiter.prototype.populateLoadRecipesList = function() { - const loadNameEl = document.getElementById("load-name"); - - // Remove current recipes from select - let i = loadNameEl.options.length; - while (i--) { - loadNameEl.remove(i); - } - - // Add recipes to select - const savedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : []; - - for (i = 0; i < savedRecipes.length; i++) { - const opt = document.createElement("option"); - opt.value = savedRecipes[i].id; - // Unescape then re-escape in case localStorage has been corrupted - opt.innerHTML = Utils.escapeHtml(Utils.unescapeHtml(savedRecipes[i].name)); - - loadNameEl.appendChild(opt); - } - - // Populate textarea with first recipe - document.getElementById("load-text").value = savedRecipes.length ? savedRecipes[0].recipe : ""; -}; - - -/** - * Removes the currently selected recipe from local storage. - */ -ControlsWaiter.prototype.loadDeleteClick = function() { - const id = parseInt(document.getElementById("load-name").value, 10); - const rawSavedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : []; - - const savedRecipes = rawSavedRecipes.filter(r => r.id !== id); - - localStorage.savedRecipes = JSON.stringify(savedRecipes); - this.populateLoadRecipesList(); -}; - - -/** - * Displays the selected recipe in the load text box. - */ -ControlsWaiter.prototype.loadNameChange = function(e) { - const el = e.target; - const savedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : []; - const id = parseInt(el.value, 10); - - const recipe = savedRecipes.find(r => r.id === id); - - document.getElementById("load-text").value = recipe.recipe; -}; - - -/** - * Loads the selected recipe and populates the Recipe with its operations. - */ -ControlsWaiter.prototype.loadButtonClick = function() { - try { - const recipeConfig = JSON.parse(document.getElementById("load-text").value); - this.app.setRecipeConfig(recipeConfig); - - $("#rec-list [data-toggle=popover]").popover(); - } catch (e) { - this.app.alert("Invalid recipe", "danger", 2000); - } -}; - - -/** - * Populates the bug report information box with useful technical info. - */ -ControlsWaiter.prototype.supportButtonClick = function() { - const reportBugInfo = document.getElementById("report-bug-info"); - const saveLink = this.generateStateUrl(true, true, null, "https://gchq.github.io/CyberChef/"); - - reportBugInfo.innerHTML = "* CyberChef compile time: " + COMPILE_TIME + "\n" + - "* User-Agent: \n" + navigator.userAgent + "\n" + - "* [Link to reproduce](" + saveLink + ")\n\n"; -}; - -export default ControlsWaiter; diff --git a/src/web/ControlsWaiter.mjs b/src/web/ControlsWaiter.mjs new file mode 100755 index 00000000..a3f74e29 --- /dev/null +++ b/src/web/ControlsWaiter.mjs @@ -0,0 +1,394 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "../core/Utils"; +import {toBase64} from "../core/lib/Base64"; + + +/** + * Waiter to handle events related to the CyberChef controls (i.e. Bake, Step, Save, Load etc.) + */ +class ControlsWaiter { + + /** + * ControlsWaiter 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; + } + + + /** + * Initialise Bootstrap componenets + */ + initComponents() { + $("body").bootstrapMaterialDesign(); + $("[data-toggle=tooltip]").tooltip({ + animation: false, + container: "body", + boundary: "viewport", + trigger: "hover" + }); + } + + + /** + * Checks or unchecks the Auto Bake checkbox based on the given value. + * + * @param {boolean} value - The new value for Auto Bake. + */ + setAutoBake(value) { + const autoBakeCheckbox = document.getElementById("auto-bake"); + + if (autoBakeCheckbox.checked !== value) { + autoBakeCheckbox.click(); + } + } + + + /** + * Handler to trigger baking. + */ + bakeClick() { + if (document.getElementById("bake").textContent.indexOf("Bake") > 0) { + this.app.bake(); + } else { + this.manager.worker.cancelBake(); + } + } + + + /** + * Handler for the 'Step through' command. Executes the next step of the recipe. + */ + stepClick() { + this.app.bake(true); + } + + + /** + * Handler for changes made to the Auto Bake checkbox. + */ + autoBakeChange() { + this.app.autoBake_ = document.getElementById("auto-bake").checked; + } + + + /** + * Handler for the 'Clear recipe' command. Removes all operations from the recipe. + */ + clearRecipeClick() { + this.manager.recipe.clearRecipe(); + } + + + /** + * Populates the save disalog box with a URL incorporating the recipe and input. + * + * @param {Object[]} [recipeConfig] - The recipe configuration object array. + */ + initialiseSaveLink(recipeConfig) { + recipeConfig = recipeConfig || this.app.getRecipeConfig(); + + const includeRecipe = document.getElementById("save-link-recipe-checkbox").checked; + const includeInput = document.getElementById("save-link-input-checkbox").checked; + const saveLinkEl = document.getElementById("save-link"); + const saveLink = this.generateStateUrl(includeRecipe, includeInput, recipeConfig); + + saveLinkEl.innerHTML = Utils.truncate(saveLink, 120); + saveLinkEl.setAttribute("href", saveLink); + } + + + /** + * Generates a URL containing the current recipe and input state. + * + * @param {boolean} includeRecipe - Whether to include the recipe in the URL. + * @param {boolean} includeInput - Whether to include the input in the URL. + * @param {Object[]} [recipeConfig] - The recipe configuration object array. + * @param {string} [baseURL] - The CyberChef URL, set to the current URL if not included + * @returns {string} + */ + generateStateUrl(includeRecipe, includeInput, recipeConfig, baseURL) { + recipeConfig = recipeConfig || this.app.getRecipeConfig(); + + const link = baseURL || window.location.protocol + "//" + + window.location.host + + window.location.pathname; + const recipeStr = Utils.generatePrettyRecipe(recipeConfig); + const inputStr = toBase64(this.app.getInput(), "A-Za-z0-9+/"); // B64 alphabet with no padding + + includeRecipe = includeRecipe && (recipeConfig.length > 0); + // Only inlcude input if it is less than 50KB (51200 * 4/3 as it is Base64 encoded) + includeInput = includeInput && (inputStr.length > 0) && (inputStr.length <= 68267); + + const params = [ + includeRecipe ? ["recipe", recipeStr] : undefined, + includeInput ? ["input", inputStr] : undefined, + ]; + + const hash = params + .filter(v => v) + .map(([key, value]) => `${key}=${Utils.encodeURIFragment(value)}`) + .join("&"); + + if (hash) { + return `${link}#${hash}`; + } + + return link; + } + + + /** + * Handler for changes made to the save dialog text area. Re-initialises the save link. + */ + saveTextChange(e) { + try { + const recipeConfig = Utils.parseRecipeConfig(e.target.value); + this.initialiseSaveLink(recipeConfig); + } catch (err) {} + } + + + /** + * Handler for the 'Save' command. Pops up the save dialog box. + */ + saveClick() { + const recipeConfig = this.app.getRecipeConfig(); + const recipeStr = JSON.stringify(recipeConfig); + + document.getElementById("save-text-chef").value = Utils.generatePrettyRecipe(recipeConfig, true); + document.getElementById("save-text-clean").value = JSON.stringify(recipeConfig, null, 2) + .replace(/{\n\s+"/g, "{ \"") + .replace(/\[\n\s{3,}/g, "[") + .replace(/\n\s{3,}]/g, "]") + .replace(/\s*\n\s*}/g, " }") + .replace(/\n\s{6,}/g, " "); + document.getElementById("save-text-compact").value = recipeStr; + + this.initialiseSaveLink(recipeConfig); + $("#save-modal").modal(); + } + + + /** + * Handler for the save link recipe checkbox change event. + */ + slrCheckChange() { + this.initialiseSaveLink(); + } + + + /** + * Handler for the save link input checkbox change event. + */ + sliCheckChange() { + this.initialiseSaveLink(); + } + + + /** + * Handler for the 'Load' command. Pops up the load dialog box. + */ + loadClick() { + this.populateLoadRecipesList(); + $("#load-modal").modal(); + } + + + /** + * Saves the recipe specified in the save textarea to local storage. + */ + saveButtonClick() { + if (!this.app.isLocalStorageAvailable()) { + this.app.alert( + "Your security settings do not allow access to local storage so your recipe cannot be saved.", + 5000 + ); + return false; + } + + const recipeName = Utils.escapeHtml(document.getElementById("save-name").value); + const recipeStr = document.querySelector("#save-texts .tab-pane.active textarea").value; + + if (!recipeName) { + this.app.alert("Please enter a recipe name", 3000); + return; + } + + const savedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + let recipeId = localStorage.recipeId || 0; + + savedRecipes.push({ + id: ++recipeId, + name: recipeName, + recipe: recipeStr + }); + + localStorage.savedRecipes = JSON.stringify(savedRecipes); + localStorage.recipeId = recipeId; + + this.app.alert(`Recipe saved as "${recipeName}".`, 3000); + } + + + /** + * Populates the list of saved recipes in the load dialog box from local storage. + */ + populateLoadRecipesList() { + if (!this.app.isLocalStorageAvailable()) return false; + + const loadNameEl = document.getElementById("load-name"); + + // Remove current recipes from select + let i = loadNameEl.options.length; + while (i--) { + loadNameEl.remove(i); + } + + // Add recipes to select + const savedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + + for (i = 0; i < savedRecipes.length; i++) { + const opt = document.createElement("option"); + opt.value = savedRecipes[i].id; + // Unescape then re-escape in case localStorage has been corrupted + opt.innerHTML = Utils.escapeHtml(Utils.unescapeHtml(savedRecipes[i].name)); + + loadNameEl.appendChild(opt); + } + + // Populate textarea with first recipe + const loadText = document.getElementById("load-text"); + const evt = new Event("change"); + loadText.value = savedRecipes.length ? savedRecipes[0].recipe : ""; + loadText.dispatchEvent(evt); + } + + + /** + * Removes the currently selected recipe from local storage. + */ + loadDeleteClick() { + if (!this.app.isLocalStorageAvailable()) return false; + + const id = parseInt(document.getElementById("load-name").value, 10); + const rawSavedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + + const savedRecipes = rawSavedRecipes.filter(r => r.id !== id); + + localStorage.savedRecipes = JSON.stringify(savedRecipes); + this.populateLoadRecipesList(); + } + + + /** + * Displays the selected recipe in the load text box. + */ + loadNameChange(e) { + if (!this.app.isLocalStorageAvailable()) return false; + + const el = e.target; + const savedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + const id = parseInt(el.value, 10); + + const recipe = savedRecipes.find(r => r.id === id); + + document.getElementById("load-text").value = recipe.recipe; + } + + + /** + * Loads the selected recipe and populates the Recipe with its operations. + */ + loadButtonClick() { + try { + const recipeConfig = Utils.parseRecipeConfig(document.getElementById("load-text").value); + this.app.setRecipeConfig(recipeConfig); + this.app.autoBake(); + + $("#rec-list [data-toggle=popover]").popover(); + } catch (e) { + this.app.alert("Invalid recipe", 2000); + } + } + + + /** + * Populates the bug report information box with useful technical info. + * + * @param {event} e + */ + supportButtonClick(e) { + e.preventDefault(); + + const reportBugInfo = document.getElementById("report-bug-info"); + const saveLink = this.generateStateUrl(true, true, null, "https://gchq.github.io/CyberChef/"); + + if (reportBugInfo) { + reportBugInfo.innerHTML = `* Version: ${PKG_VERSION + (typeof INLINE === "undefined" ? "" : "s")} +* Compile time: ${COMPILE_TIME} +* User-Agent: +${navigator.userAgent} +* [Link to reproduce](${saveLink}) + +`; + } + } + + + /** + * Shows the stale indicator to show that the input or recipe has changed + * since the last bake. + */ + showStaleIndicator() { + const staleIndicator = document.getElementById("stale-indicator"); + staleIndicator.classList.remove("hidden"); + } + + + /** + * Hides the stale indicator to show that the input or recipe has not changed + * since the last bake. + */ + hideStaleIndicator() { + const staleIndicator = document.getElementById("stale-indicator"); + staleIndicator.classList.add("hidden"); + } + + + /** + * Switches the Bake button between 'Bake' and 'Cancel' functions. + * + * @param {boolean} cancel - Whether to change to cancel or not + */ + toggleBakeButtonFunction(cancel) { + const bakeButton = document.getElementById("bake"), + btnText = bakeButton.querySelector("span"); + + if (cancel) { + btnText.innerText = "Cancel"; + bakeButton.classList.remove("btn-success"); + bakeButton.classList.add("btn-danger"); + } else { + btnText.innerText = "Bake!"; + bakeButton.classList.remove("btn-danger"); + bakeButton.classList.add("btn-success"); + } + } + +} + +export default ControlsWaiter; diff --git a/src/web/HTMLCategory.js b/src/web/HTMLCategory.js deleted file mode 100755 index e3a168ba..00000000 --- a/src/web/HTMLCategory.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Object to handle the creation of operation categories. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {string} name - The name of the category. - * @param {boolean} selected - Whether this category is pre-selected or not. - */ -const HTMLCategory = function(name, selected) { - this.name = name; - this.selected = selected; - this.opList = []; -}; - - -/** - * Adds an operation to this category. - * - * @param {HTMLOperation} operation - The operation to add. - */ -HTMLCategory.prototype.addOperation = function(operation) { - this.opList.push(operation); -}; - - -/** - * Renders the category and all operations within it in HTML. - * - * @returns {string} - */ -HTMLCategory.prototype.toHtml = function() { - const catName = "cat" + this.name.replace(/[\s/-:_]/g, ""); - let html = "
\ - \ - " + this.name + "\ - \ -
    "; - - for (let i = 0; i < this.opList.length; i++) { - html += this.opList[i].toStubHtml(); - } - - html += "
"; - return html; -}; - -export default HTMLCategory; diff --git a/src/web/HTMLCategory.mjs b/src/web/HTMLCategory.mjs new file mode 100755 index 00000000..95b8a4df --- /dev/null +++ b/src/web/HTMLCategory.mjs @@ -0,0 +1,59 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Object to handle the creation of operation categories. + */ +class HTMLCategory { + + /** + * HTMLCategory constructor. + * + * @param {string} name - The name of the category. + * @param {boolean} selected - Whether this category is pre-selected or not. + */ + constructor(name, selected) { + this.name = name; + this.selected = selected; + this.opList = []; + } + + + /** + * Adds an operation to this category. + * + * @param {HTMLOperation} operation - The operation to add. + */ + addOperation(operation) { + this.opList.push(operation); + } + + + /** + * Renders the category and all operations within it in HTML. + * + * @returns {string} + */ + toHtml() { + const catName = "cat" + this.name.replace(/[\s/-:_]/g, ""); + let html = `
+ + ${this.name} + +
+
    `; + + for (let i = 0; i < this.opList.length; i++) { + html += this.opList[i].toStubHtml(); + } + + html += "
"; + return html; + } + +} + +export default HTMLCategory; diff --git a/src/web/HTMLIngredient.js b/src/web/HTMLIngredient.js deleted file mode 100755 index 3f360f04..00000000 --- a/src/web/HTMLIngredient.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * Object to handle the creation of operation ingredients. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {Object} config - The configuration object for this ingredient. - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const HTMLIngredient = function(config, app, manager) { - this.app = app; - this.manager = manager; - - this.name = config.name; - this.type = config.type; - this.value = config.value; - this.disabled = config.disabled || false; - this.disableArgs = config.disableArgs || false; - this.placeholder = config.placeholder || false; - this.target = config.target; - this.toggleValues = config.toggleValues; - this.id = "ing-" + this.app.nextIngId(); -}; - - -/** - * Renders the ingredient in HTML. - * - * @returns {string} - */ -HTMLIngredient.prototype.toHtml = function() { - let inline = (this.type === "boolean" || - this.type === "number" || - this.type === "option" || - this.type === "shortString" || - this.type === "binaryShortString"), - html = inline ? "" : "
 
", - i, m; - - html += "
"; - - switch (this.type) { - case "string": - case "binaryString": - case "byteArray": - html += ""; - break; - case "shortString": - case "binaryShortString": - html += ""; - break; - case "toggleString": - html += "
\ -
"; - break; - case "number": - html += ""; - break; - case "boolean": - html += ""; - - if (this.disableArgs) { - this.manager.addDynamicListener("#" + this.id, "click", this.toggleDisableArgs, this); - } - break; - case "option": - html += ""; - break; - case "populateOption": - html += ""; - - this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this); - break; - case "editableOption": - html += "
"; - html += ""; - html += ""; - html += "
"; - - - this.manager.addDynamicListener("#sel-" + this.id, "change", this.editableOptionChange, this); - break; - case "text": - html += ""; - break; - default: - break; - } - html += "
"; - - return html; -}; - - -/** - * Handler for argument disable toggle. - * Toggles disabled state for all arguments in the disableArgs list for this ingredient. - * - * @param {event} e - */ -HTMLIngredient.prototype.toggleDisableArgs = function(e) { - const el = e.target; - const op = el.parentNode.parentNode; - const args = op.querySelectorAll(".arg-group"); - - for (let i = 0; i < this.disableArgs.length; i++) { - const els = args[this.disableArgs[i]].querySelectorAll("input, select, button"); - - for (let j = 0; j < els.length; j++) { - if (els[j].getAttribute("disabled")) { - els[j].removeAttribute("disabled"); - } else { - els[j].setAttribute("disabled", "disabled"); - } - } - } - - this.manager.recipe.ingChange(); -}; - - -/** - * Handler for populate option changes. - * Populates the relevant argument with the specified value. - * - * @param {event} e - */ -HTMLIngredient.prototype.populateOptionChange = function(e) { - const el = e.target; - const op = el.parentNode.parentNode; - const target = op.querySelectorAll(".arg-group")[this.target].querySelector("input, select, textarea"); - - target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value"); - - this.manager.recipe.ingChange(); -}; - - -/** - * Handler for editable option changes. - * Populates the input box with the selected value. - * - * @param {event} e - */ -HTMLIngredient.prototype.editableOptionChange = function(e) { - let select = e.target, - input = select.nextSibling; - - input.value = select.childNodes[select.selectedIndex].value; - - this.manager.recipe.ingChange(); -}; - -export default HTMLIngredient; diff --git a/src/web/HTMLIngredient.mjs b/src/web/HTMLIngredient.mjs new file mode 100755 index 00000000..ab7f682b --- /dev/null +++ b/src/web/HTMLIngredient.mjs @@ -0,0 +1,289 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "../core/Utils"; + +/** + * Object to handle the creation of operation ingredients. + */ +class HTMLIngredient { + + /** + * HTMLIngredient constructor. + * + * @param {Object} config - The configuration object for this ingredient. + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(config, app, manager) { + this.app = app; + this.manager = manager; + + this.name = config.name; + this.type = config.type; + this.value = config.value; + this.disabled = config.disabled || false; + this.hint = config.hint || false; + this.rows = config.rows || false; + this.target = config.target; + this.defaultIndex = config.defaultIndex || 0; + this.toggleValues = config.toggleValues; + this.id = "ing-" + this.app.nextIngId(); + } + + + /** + * Renders the ingredient in HTML. + * + * @returns {string} + */ + toHtml() { + let html = "", + i, m; + + switch (this.type) { + case "string": + case "binaryString": + case "byteArray": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + break; + case "shortString": + case "binaryShortString": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + break; + case "toggleString": + html += `
+
+ + + ${this.hint ? "" + this.hint + "" : ""} +
+
+ + +
+ +
`; + break; + case "number": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + break; + case "boolean": + html += `
+
+ + ${this.hint ? "" + this.hint + "" : ""} +
+
`; + break; + case "option": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + break; + case "populateOption": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + + this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this); + break; + case "editableOption": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
+ + +
+
`; + + this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this); + break; + case "editableOptionShort": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
+ + +
+
`; + + this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this); + break; + case "text": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + break; + default: + break; + } + + return html; + } + + + /** + * Handler for populate option changes. + * Populates the relevant argument with the specified value. + * + * @param {event} e + */ + populateOptionChange(e) { + const el = e.target; + const op = el.parentNode.parentNode; + const target = op.querySelectorAll(".arg")[this.target]; + + target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value"); + const evt = new Event("change"); + target.dispatchEvent(evt); + + this.manager.recipe.ingChange(); + } + + + /** + * Handler for editable option clicks. + * Populates the input box with the selected value. + * + * @param {event} e + */ + editableOptionClick(e) { + e.preventDefault(); + e.stopPropagation(); + + const link = e.target, + input = link.parentNode.parentNode.parentNode.querySelector("input"); + + input.value = link.getAttribute("value"); + const evt = new Event("change"); + input.dispatchEvent(evt); + + this.manager.recipe.ingChange(); + } + +} + +export default HTMLIngredient; diff --git a/src/web/HTMLOperation.js b/src/web/HTMLOperation.js deleted file mode 100755 index ea0d4397..00000000 --- a/src/web/HTMLOperation.js +++ /dev/null @@ -1,119 +0,0 @@ -import HTMLIngredient from "./HTMLIngredient.js"; - - -/** - * Object to handle the creation of operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {string} name - The name of the operation. - * @param {Object} config - The configuration object for this operation. - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const HTMLOperation = function(name, config, app, manager) { - this.app = app; - this.manager = manager; - - this.name = name; - this.description = config.description; - this.manualBake = config.manualBake || false; - this.config = config; - this.ingList = []; - - for (let i = 0; i < config.args.length; i++) { - const ing = new HTMLIngredient(config.args[i], this.app, this.manager); - this.ingList.push(ing); - } -}; - - -/** - * @constant - */ -HTMLOperation.INFO_ICON = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAByElEQVR4XqVTzWoaYRQ9KZJmoVaS1J1QiYTIuOgqi9lEugguQhYhdGs3hTyAi0CWJTvJIks30ZBNsimUtlqkVLoQCuJsphRriyFjabWtEyf/Rv3iWcwwymTlgQuH851z5hu43wRGkEwmXwCIA4hiGAUAmUQikQbhEHwyGCWVSglVVUW73RYmyKnxjB56ncJ6NpsVxHGrI/ZLuniVb3DIqQmCHnrNkgcggNeSJPlisRgyJR2b737j/TcDsQUPwv6H5NR4BnroZcb6Z16N2PvyX6yna9Z8qp6JQ0Uf0ughmGHWBSAuyzJqrQ7eqKewY/dzE363C71e39LoWQq5wUwul4uzIBoIBHD01RgyrkZ8eDbvwUWnj623v2DHx4qB51IAzLIAXq8XP/7W0bUVVJtXWIk8wvlN364TA+/1IDMLwmWK/Hq3axmhaBdoGLeklm73ElaBYRgIzkyifHIOO4QQJKM3oJcZq6CgaVp0OTyHw9K/kQI4FiyHfdC0n2CWe5ApFosIPZ7C2tNpXpcDOehGyD/FIbd0euhlhllzFxRzC3fydbG4XRYbB9/tQ41n9m1U7l3lyp9LkfygiZeZCoecmtMqj/+Yxn7Od3v0j50qCO3zAAAAAElFTkSuQmCC"; -/** - * @constant - */ -HTMLOperation.REMOVE_ICON = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABwklEQVR42qRTPU8CQRB9K2CCMRJ6NTQajOUaqfxIbLCRghhjQixosLAgFNBQ3l8wsabxLxBJbCyVUBiMCVQEQkOEKBbCnefM3p4eohWXzM3uvHlv52b2hG3bmOWZw4yPn1/XQkCQ9wFxcgZZ0QLKpifpN8Z1n1L13griBBjHhYK0nMT4b+wom53ClAAFQacZJ/m8rNfrSOZy0vxJjPP6IJ2WzWYTO6mUwiwtILiJJSHUKVSWkchkZK1WQzQaxU2pVGUglkjIbreLUCiEx0qlStlFCpfPiPstYDtVKJH9ZFI2Gw1FGA6H6LTbCAaDeGu1FJl6UuYjpwTGzucokZW1NfnS66kyfT4fXns9RaZmlgNcuhZQU+jowLzuOK/HgwEW3E5ZlhLXVWKk11P3wNYNWw+HZdA0sUgx1zjGmD05nckx0ilGjBJdUq3fr7K5e8bGf43RdL7fOPSQb4lI8SLbrUfkUIuY32VTI1bJn5BqDnh4Dodt9ryPUDzyD7aquWoKQohl2i9sAbubwPkTcHkP3FHsg+yT+7sN7G0AF3Xg6sHB3onbdgWWKBDQg/BcTuVt51dQA/JrnIcyIu6rmPV3/hJgACPc0BMEYTg+AAAAAElFTkSuQmCC"; - - -/** - * Renders the operation in HTML as a stub operation with no ingredients. - * - * @returns {string} - */ -HTMLOperation.prototype.toStubHtml = function(removeIcon) { - let html = "
  • "; - } - - if (this.description) { - html += ""; - } - - html += "
  • "; - - return html; -}; - - -/** - * Renders the operation in HTML as a full operation with ingredients. - * - * @returns {string} - */ -HTMLOperation.prototype.toFullHtml = function() { - let html = "
    " + this.name + "
    "; - - for (let i = 0; i < this.ingList.length; i++) { - html += this.ingList[i].toHtml(); - } - - html += "
    \ -
    \ -
    "; - - html += "
    \ -
     
    "; - - return html; -}; - - -/** - * Highlights the searched string in the name and description of the operation. - * - * @param {string} searchStr - * @param {number} namePos - The position of the search string in the operation name - * @param {number} descPos - The position of the search string in the operation description - */ -HTMLOperation.prototype.highlightSearchString = function(searchStr, namePos, descPos) { - if (namePos >= 0) { - this.name = this.name.slice(0, namePos) + "" + - this.name.slice(namePos, namePos + searchStr.length) + "" + - this.name.slice(namePos + searchStr.length); - } - - if (this.description && descPos >= 0) { - this.description = this.description.slice(0, descPos) + "" + - this.description.slice(descPos, descPos + searchStr.length) + "" + - this.description.slice(descPos + searchStr.length); - } -}; - -export default HTMLOperation; diff --git a/src/web/HTMLOperation.mjs b/src/web/HTMLOperation.mjs new file mode 100755 index 00000000..5373113a --- /dev/null +++ b/src/web/HTMLOperation.mjs @@ -0,0 +1,146 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import HTMLIngredient from "./HTMLIngredient"; + + +/** + * Object to handle the creation of operations. + */ +class HTMLOperation { + + /** + * HTMLOperation constructor. + * + * @param {string} name - The name of the operation. + * @param {Object} config - The configuration object for this operation. + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(name, config, app, manager) { + this.app = app; + this.manager = manager; + + this.name = name; + this.description = config.description; + this.infoURL = config.infoURL; + this.manualBake = config.manualBake || false; + this.config = config; + this.ingList = []; + + for (let i = 0; i < config.args.length; i++) { + const ing = new HTMLIngredient(config.args[i], this.app, this.manager); + this.ingList.push(ing); + } + } + + + /** + * Renders the operation in HTML as a stub operation with no ingredients. + * + * @returns {string} + */ + toStubHtml(removeIcon) { + let html = "
  • ${titleFromWikiLink(this.infoURL)}` : ""; + + html += ` data-container='body' data-toggle='popover' data-placement='right' + data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover' + data-boundary='viewport'`; + } + + html += ">" + this.name; + + if (removeIcon) { + html += "delete"; + } + + html += "
  • "; + + return html; + } + + + /** + * Renders the operation in HTML as a full operation with ingredients. + * + * @returns {string} + */ + toFullHtml() { + let html = `
    ${this.name}
    +
    `; + + for (let i = 0; i < this.ingList.length; i++) { + html += this.ingList[i].toHtml(); + } + + html += `
    +
    + pause + not_interested +
    +
     
    `; + + return html; + } + + + /** + * Highlights the searched string in the name and description of the operation. + * + * @param {string} searchStr + * @param {number} namePos - The position of the search string in the operation name + * @param {number} descPos - The position of the search string in the operation description + */ + highlightSearchString(searchStr, namePos, descPos) { + if (namePos >= 0) { + this.name = this.name.slice(0, namePos) + "" + + this.name.slice(namePos, namePos + searchStr.length) + "" + + this.name.slice(namePos + searchStr.length); + } + + if (this.description && descPos >= 0) { + // Find HTML tag offsets + const re = /<[^>]+>/g; + let match; + while ((match = re.exec(this.description))) { + // If the search string occurs within an HTML tag, return without highlighting it. + if (descPos >= match.index && descPos <= (match.index + match[0].length)) + return; + } + + this.description = this.description.slice(0, descPos) + "" + + this.description.slice(descPos, descPos + searchStr.length) + "" + + this.description.slice(descPos + searchStr.length); + } + } + +} + + +/** + * Given a URL for a Wikipedia (or other wiki) page, this function returns a link to that page. + * + * @param {string} url + * @returns {string} + */ +function titleFromWikiLink(url) { + const splitURL = url.split("/"); + if (splitURL.indexOf("wikipedia.org") < 0 && splitURL.indexOf("forensicswiki.org") < 0) { + // Not a wiki link, return full URL + return `More Informationopen_in_new`; + } + + const wikiName = splitURL.indexOf("forensicswiki.org") < 0 ? "Wikipedia" : "Forensics Wiki"; + + const pageTitle = decodeURIComponent(splitURL[splitURL.length - 1]) + .replace(/_/g, " "); + return `${pageTitle}open_in_new on ${wikiName}`; +} + +export default HTMLOperation; diff --git a/src/web/HighlighterWaiter.js b/src/web/HighlighterWaiter.js deleted file mode 100755 index 7c9b7e42..00000000 --- a/src/web/HighlighterWaiter.js +++ /dev/null @@ -1,511 +0,0 @@ -import Utils from "../core/Utils.js"; - - -/** - * Waiter to handle events related to highlighting in CyberChef. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - */ -const HighlighterWaiter = function(app) { - this.app = app; - - this.mouseButtonDown = false; - this.mouseTarget = null; -}; - - -/** - * HighlighterWaiter data type enum for the input. - * @readonly - * @enum - */ -HighlighterWaiter.INPUT = 0; -/** - * HighlighterWaiter data type enum for the output. - * @readonly - * @enum - */ -HighlighterWaiter.OUTPUT = 1; - - -/** - * Determines if the current text selection is running backwards or forwards. - * StackOverflow answer id: 12652116 - * - * @private - * @returns {boolean} - */ -HighlighterWaiter.prototype._isSelectionBackwards = function() { - let backwards = false, - sel = window.getSelection(); - - if (!sel.isCollapsed) { - const range = document.createRange(); - range.setStart(sel.anchorNode, sel.anchorOffset); - range.setEnd(sel.focusNode, sel.focusOffset); - backwards = range.collapsed; - range.detach(); - } - return backwards; -}; - - -/** - * Calculates the text offset of a position in an HTML element, ignoring HTML tags. - * - * @private - * @param {element} node - The parent HTML node. - * @param {number} offset - The offset since the last HTML element. - * @returns {number} - */ -HighlighterWaiter.prototype._getOutputHtmlOffset = function(node, offset) { - const sel = window.getSelection(); - const range = document.createRange(); - - range.selectNodeContents(document.getElementById("output-html")); - range.setEnd(node, offset); - sel.removeAllRanges(); - sel.addRange(range); - - return sel.toString().length; -}; - - -/** - * Gets the current selection offsets in the output HTML, ignoring HTML tags. - * - * @private - * @returns {Object} pos - * @returns {number} pos.start - * @returns {number} pos.end - */ -HighlighterWaiter.prototype._getOutputHtmlSelectionOffsets = function() { - const sel = window.getSelection(); - let range, - start = 0, - end = 0, - backwards = false; - - if (sel.rangeCount) { - range = sel.getRangeAt(sel.rangeCount - 1); - backwards = this._isSelectionBackwards(); - start = this._getOutputHtmlOffset(range.startContainer, range.startOffset); - end = this._getOutputHtmlOffset(range.endContainer, range.endOffset); - sel.removeAllRanges(); - sel.addRange(range); - - if (backwards) { - // If selecting backwards, reverse the start and end offsets for the selection to - // prevent deselecting as the drag continues. - sel.collapseToEnd(); - sel.extend(sel.anchorNode, range.startOffset); - } - } - - return { - start: start, - end: end - }; -}; - - -/** - * Handler for input scroll events. - * Scrolls the highlighter pane to match the input textarea position. - * - * @param {event} e - */ -HighlighterWaiter.prototype.inputScroll = function(e) { - const el = e.target; - document.getElementById("input-highlighter").scrollTop = el.scrollTop; - document.getElementById("input-highlighter").scrollLeft = el.scrollLeft; -}; - - -/** - * Handler for output scroll events. - * Scrolls the highlighter pane to match the output textarea position. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputScroll = function(e) { - const el = e.target; - document.getElementById("output-highlighter").scrollTop = el.scrollTop; - document.getElementById("output-highlighter").scrollLeft = el.scrollLeft; -}; - - -/** - * Handler for input mousedown events. - * Calculates the current selection info, and highlights the corresponding data in the output. - * - * @param {event} e - */ -HighlighterWaiter.prototype.inputMousedown = function(e) { - this.mouseButtonDown = true; - this.mouseTarget = HighlighterWaiter.INPUT; - this.removeHighlights(); - - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; - - if (start !== 0 || end !== 0) { - document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); - this.highlightOutput([{start: start, end: end}]); - } -}; - - -/** - * Handler for output mousedown events. - * Calculates the current selection info, and highlights the corresponding data in the input. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputMousedown = function(e) { - this.mouseButtonDown = true; - this.mouseTarget = HighlighterWaiter.OUTPUT; - this.removeHighlights(); - - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; - - if (start !== 0 || end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); - this.highlightInput([{start: start, end: end}]); - } -}; - - -/** - * Handler for output HTML mousedown events. - * Calculates the current selection info. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputHtmlMousedown = function(e) { - this.mouseButtonDown = true; - this.mouseTarget = HighlighterWaiter.OUTPUT; - - const sel = this._getOutputHtmlSelectionOffsets(); - if (sel.start !== 0 || sel.end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); - } -}; - - -/** - * Handler for input mouseup events. - * - * @param {event} e - */ -HighlighterWaiter.prototype.inputMouseup = function(e) { - this.mouseButtonDown = false; -}; - - -/** - * Handler for output mouseup events. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputMouseup = function(e) { - this.mouseButtonDown = false; -}; - - -/** - * Handler for output HTML mouseup events. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputHtmlMouseup = function(e) { - this.mouseButtonDown = false; -}; - - -/** - * Handler for input mousemove events. - * Calculates the current selection info, and highlights the corresponding data in the output. - * - * @param {event} e - */ -HighlighterWaiter.prototype.inputMousemove = function(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== HighlighterWaiter.INPUT) - return; - - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; - - if (start !== 0 || end !== 0) { - document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); - this.highlightOutput([{start: start, end: end}]); - } -}; - - -/** - * Handler for output mousemove events. - * Calculates the current selection info, and highlights the corresponding data in the input. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputMousemove = function(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== HighlighterWaiter.OUTPUT) - return; - - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; - - if (start !== 0 || end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); - this.highlightInput([{start: start, end: end}]); - } -}; - - -/** - * Handler for output HTML mousemove events. - * Calculates the current selection info. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputHtmlMousemove = function(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== HighlighterWaiter.OUTPUT) - return; - - const sel = this._getOutputHtmlSelectionOffsets(); - if (sel.start !== 0 || sel.end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); - } -}; - - -/** - * Given start and end offsets, writes the HTML for the selection info element with the correct - * padding. - * - * @param {number} start - The start offset. - * @param {number} end - The end offset. - * @returns {string} - */ -HighlighterWaiter.prototype.selectionInfo = function(start, end) { - const len = end.toString().length; - const width = len < 2 ? 2 : len; - const startStr = Utils.pad(start.toString(), width, " ").replace(/ /g, " "); - const endStr = Utils.pad(end.toString(), width, " ").replace(/ /g, " "); - const lenStr = Utils.pad((end-start).toString(), width, " ").replace(/ /g, " "); - - return "start: " + startStr + "
    end: " + endStr + "
    length: " + lenStr; -}; - - -/** - * Removes highlighting and selection information. - */ -HighlighterWaiter.prototype.removeHighlights = function() { - document.getElementById("input-highlighter").innerHTML = ""; - document.getElementById("output-highlighter").innerHTML = ""; - document.getElementById("input-selection-info").innerHTML = ""; - document.getElementById("output-selection-info").innerHTML = ""; -}; - - -/** - * Generates a list of all the highlight functions assigned to operations in the recipe, if the - * entire recipe supports highlighting. - * - * @returns {Object[]} highlights - * @returns {function} highlights[].f - * @returns {function} highlights[].b - * @returns {Object[]} highlights[].args - */ -HighlighterWaiter.prototype.generateHighlightList = function() { - const recipeConfig = this.app.getRecipeConfig(); - const highlights = []; - - for (let i = 0; i < recipeConfig.length; i++) { - if (recipeConfig[i].disabled) continue; - - // If any breakpoints are set, do not attempt to highlight - if (recipeConfig[i].breakpoint) return false; - - const op = this.app.operations[recipeConfig[i].op]; - - // If any of the operations do not support highlighting, fail immediately. - if (op.highlight === false || op.highlight === undefined) return false; - - highlights.push({ - f: op.highlight, - b: op.highlightReverse, - args: recipeConfig[i].args - }); - } - - return highlights; -}; - - -/** - * Highlights the given offsets in the output. - * We will only highlight if: - * - input hasn't changed since last bake - * - last bake was a full bake - * - all operations in the recipe support highlighting - * - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. - */ -HighlighterWaiter.prototype.highlightOutput = function(pos) { - const highlights = this.generateHighlightList(); - - if (!highlights || !this.app.autoBake_) { - return false; - } - - for (let i = 0; i < highlights.length; i++) { - // Remove multiple highlights before processing again - pos = [pos[0]]; - - if (typeof highlights[i].f == "function") { - pos = highlights[i].f(pos, highlights[i].args); - } - } - - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); - this.highlight( - document.getElementById("output-text"), - document.getElementById("output-highlighter"), - pos); -}; - - -/** - * Highlights the given offsets in the input. - * We will only highlight if: - * - input hasn't changed since last bake - * - last bake was a full bake - * - all operations in the recipe support highlighting - * - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. - */ -HighlighterWaiter.prototype.highlightInput = function(pos) { - const highlights = this.generateHighlightList(); - - if (!highlights || !this.app.autoBake_) { - return false; - } - - for (let i = 0; i < highlights.length; i++) { - // Remove multiple highlights before processing again - pos = [pos[0]]; - - if (typeof highlights[i].b == "function") { - pos = highlights[i].b(pos, highlights[i].args); - } - } - - document.getElementById("input-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); - this.highlight( - document.getElementById("input-text"), - document.getElementById("input-highlighter"), - pos); -}; - - -/** - * Adds the relevant HTML to the specified highlight element such that highlighting appears - * underneath the correct offset. - * - * @param {element} textarea - The input or output textarea. - * @param {element} highlighter - The input or output highlighter element. - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. - */ -HighlighterWaiter.prototype.highlight = function(textarea, highlighter, pos) { - if (!this.app.options.showHighlighter) return false; - if (!this.app.options.attemptHighlight) return false; - - // Check if there is a carriage return in the output dish as this will not - // be displayed by the HTML textarea and will mess up highlighting offsets. - if (!this.app.dishStr || this.app.dishStr.indexOf("\r") >= 0) return false; - - const startPlaceholder = "[startHighlight]"; - const startPlaceholderRegex = /\[startHighlight\]/g; - const endPlaceholder = "[endHighlight]"; - const endPlaceholderRegex = /\[endHighlight\]/g; - let text = textarea.value; - - // Put placeholders in position - // If there's only one value, select that - // If there are multiple, ignore the first one and select all others - if (pos.length === 1) { - if (pos[0].end < pos[0].start) return; - text = text.slice(0, pos[0].start) + - startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder + - text.slice(pos[0].end, text.length); - } else { - // O(n^2) - Can anyone improve this without overwriting placeholders? - let result = "", - endPlaced = true; - - for (let i = 0; i < text.length; i++) { - for (let j = 1; j < pos.length; j++) { - if (pos[j].end < pos[j].start) continue; - if (pos[j].start === i) { - result += startPlaceholder; - endPlaced = false; - } - if (pos[j].end === i) { - result += endPlaceholder; - endPlaced = true; - } - } - result += text[i]; - } - if (!endPlaced) result += endPlaceholder; - text = result; - } - - const cssClass = "hl1"; - //if (colour) cssClass += "-"+colour; - - // Remove HTML tags - text = text.replace(/&/g, "&") - .replace(//g, ">") - .replace(/\n/g, " ") - // Convert placeholders to tags - .replace(startPlaceholderRegex, "") - .replace(endPlaceholderRegex, "") + " "; - - // Adjust width to allow for scrollbars - highlighter.style.width = textarea.clientWidth + "px"; - highlighter.innerHTML = text; - highlighter.scrollTop = textarea.scrollTop; - highlighter.scrollLeft = textarea.scrollLeft; -}; - -export default HighlighterWaiter; diff --git a/src/web/HighlighterWaiter.mjs b/src/web/HighlighterWaiter.mjs new file mode 100755 index 00000000..99ae10b1 --- /dev/null +++ b/src/web/HighlighterWaiter.mjs @@ -0,0 +1,468 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * HighlighterWaiter data type enum for the input. + * @enum + */ +const INPUT = 0; + +/** + * HighlighterWaiter data type enum for the output. + * @enum + */ +const OUTPUT = 1; + + +/** + * Waiter to handle events related to highlighting in CyberChef. + */ +class HighlighterWaiter { + + /** + * HighlighterWaiter 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; + + this.mouseButtonDown = false; + this.mouseTarget = null; + } + + + /** + * Determines if the current text selection is running backwards or forwards. + * StackOverflow answer id: 12652116 + * + * @private + * @returns {boolean} + */ + _isSelectionBackwards() { + let backwards = false; + const sel = window.getSelection(); + + if (!sel.isCollapsed) { + const range = document.createRange(); + range.setStart(sel.anchorNode, sel.anchorOffset); + range.setEnd(sel.focusNode, sel.focusOffset); + backwards = range.collapsed; + range.detach(); + } + return backwards; + } + + + /** + * Calculates the text offset of a position in an HTML element, ignoring HTML tags. + * + * @private + * @param {element} node - The parent HTML node. + * @param {number} offset - The offset since the last HTML element. + * @returns {number} + */ + _getOutputHtmlOffset(node, offset) { + const sel = window.getSelection(); + const range = document.createRange(); + + range.selectNodeContents(document.getElementById("output-html")); + range.setEnd(node, offset); + sel.removeAllRanges(); + sel.addRange(range); + + return sel.toString().length; + } + + + /** + * Gets the current selection offsets in the output HTML, ignoring HTML tags. + * + * @private + * @returns {Object} pos + * @returns {number} pos.start + * @returns {number} pos.end + */ + _getOutputHtmlSelectionOffsets() { + const sel = window.getSelection(); + let range, + start = 0, + end = 0, + backwards = false; + + if (sel.rangeCount) { + range = sel.getRangeAt(sel.rangeCount - 1); + backwards = this._isSelectionBackwards(); + start = this._getOutputHtmlOffset(range.startContainer, range.startOffset); + end = this._getOutputHtmlOffset(range.endContainer, range.endOffset); + sel.removeAllRanges(); + sel.addRange(range); + + if (backwards) { + // If selecting backwards, reverse the start and end offsets for the selection to + // prevent deselecting as the drag continues. + sel.collapseToEnd(); + sel.extend(sel.anchorNode, range.startOffset); + } + } + + return { + start: start, + end: end + }; + } + + + /** + * Handler for input scroll events. + * Scrolls the highlighter pane to match the input textarea position. + * + * @param {event} e + */ + inputScroll(e) { + const el = e.target; + document.getElementById("input-highlighter").scrollTop = el.scrollTop; + document.getElementById("input-highlighter").scrollLeft = el.scrollLeft; + } + + + /** + * Handler for output scroll events. + * Scrolls the highlighter pane to match the output textarea position. + * + * @param {event} e + */ + outputScroll(e) { + const el = e.target; + document.getElementById("output-highlighter").scrollTop = el.scrollTop; + document.getElementById("output-highlighter").scrollLeft = el.scrollLeft; + } + + + /** + * Handler for input mousedown events. + * Calculates the current selection info, and highlights the corresponding data in the output. + * + * @param {event} e + */ + inputMousedown(e) { + this.mouseButtonDown = true; + this.mouseTarget = INPUT; + this.removeHighlights(); + + const el = e.target; + const start = el.selectionStart; + const end = el.selectionEnd; + + if (start !== 0 || end !== 0) { + document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); + this.highlightOutput([{start: start, end: end}]); + } + } + + + /** + * Handler for output mousedown events. + * Calculates the current selection info, and highlights the corresponding data in the input. + * + * @param {event} e + */ + outputMousedown(e) { + this.mouseButtonDown = true; + this.mouseTarget = OUTPUT; + this.removeHighlights(); + + const el = e.target; + const start = el.selectionStart; + const end = el.selectionEnd; + + if (start !== 0 || end !== 0) { + document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); + this.highlightInput([{start: start, end: end}]); + } + } + + + /** + * Handler for output HTML mousedown events. + * Calculates the current selection info. + * + * @param {event} e + */ + outputHtmlMousedown(e) { + this.mouseButtonDown = true; + this.mouseTarget = OUTPUT; + + const sel = this._getOutputHtmlSelectionOffsets(); + if (sel.start !== 0 || sel.end !== 0) { + document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); + } + } + + + /** + * Handler for input mouseup events. + * + * @param {event} e + */ + inputMouseup(e) { + this.mouseButtonDown = false; + } + + + /** + * Handler for output mouseup events. + * + * @param {event} e + */ + outputMouseup(e) { + this.mouseButtonDown = false; + } + + + /** + * Handler for output HTML mouseup events. + * + * @param {event} e + */ + outputHtmlMouseup(e) { + this.mouseButtonDown = false; + } + + + /** + * Handler for input mousemove events. + * Calculates the current selection info, and highlights the corresponding data in the output. + * + * @param {event} e + */ + inputMousemove(e) { + // Check that the left mouse button is pressed + if (!this.mouseButtonDown || + e.which !== 1 || + this.mouseTarget !== INPUT) + return; + + const el = e.target; + const start = el.selectionStart; + const end = el.selectionEnd; + + if (start !== 0 || end !== 0) { + document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); + this.highlightOutput([{start: start, end: end}]); + } + } + + + /** + * Handler for output mousemove events. + * Calculates the current selection info, and highlights the corresponding data in the input. + * + * @param {event} e + */ + outputMousemove(e) { + // Check that the left mouse button is pressed + if (!this.mouseButtonDown || + e.which !== 1 || + this.mouseTarget !== OUTPUT) + return; + + const el = e.target; + const start = el.selectionStart; + const end = el.selectionEnd; + + if (start !== 0 || end !== 0) { + document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); + this.highlightInput([{start: start, end: end}]); + } + } + + + /** + * Handler for output HTML mousemove events. + * Calculates the current selection info. + * + * @param {event} e + */ + outputHtmlMousemove(e) { + // Check that the left mouse button is pressed + if (!this.mouseButtonDown || + e.which !== 1 || + this.mouseTarget !== OUTPUT) + return; + + const sel = this._getOutputHtmlSelectionOffsets(); + if (sel.start !== 0 || sel.end !== 0) { + document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); + } + } + + + /** + * Given start and end offsets, writes the HTML for the selection info element with the correct + * padding. + * + * @param {number} start - The start offset. + * @param {number} end - The end offset. + * @returns {string} + */ + selectionInfo(start, end) { + const len = end.toString().length; + const width = len < 2 ? 2 : len; + const startStr = start.toString().padStart(width, " ").replace(/ /g, " "); + const endStr = end.toString().padStart(width, " ").replace(/ /g, " "); + const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, " "); + + return "start: " + startStr + "
    end: " + endStr + "
    length: " + lenStr; + } + + + /** + * Removes highlighting and selection information. + */ + removeHighlights() { + document.getElementById("input-highlighter").innerHTML = ""; + document.getElementById("output-highlighter").innerHTML = ""; + document.getElementById("input-selection-info").innerHTML = ""; + document.getElementById("output-selection-info").innerHTML = ""; + } + + + /** + * Highlights the given offsets in the output. + * We will only highlight if: + * - input hasn't changed since last bake + * - last bake was a full bake + * - all operations in the recipe support highlighting + * + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + */ + highlightOutput(pos) { + if (!this.app.autoBake_ || this.app.baking) return false; + this.manager.worker.highlight(this.app.getRecipeConfig(), "forward", pos); + } + + + /** + * Highlights the given offsets in the input. + * We will only highlight if: + * - input hasn't changed since last bake + * - last bake was a full bake + * - all operations in the recipe support highlighting + * + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + */ + highlightInput(pos) { + if (!this.app.autoBake_ || this.app.baking) return false; + this.manager.worker.highlight(this.app.getRecipeConfig(), "reverse", pos); + } + + + /** + * Displays highlight offsets sent back from the Chef. + * + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + * @param {string} direction + */ + displayHighlights(pos, direction) { + if (!pos) return; + + const io = direction === "forward" ? "output" : "input"; + + document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); + this.highlight( + document.getElementById(io + "-text"), + document.getElementById(io + "-highlighter"), + pos); + } + + + /** + * Adds the relevant HTML to the specified highlight element such that highlighting appears + * underneath the correct offset. + * + * @param {element} textarea - The input or output textarea. + * @param {element} highlighter - The input or output highlighter element. + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + */ + async highlight(textarea, highlighter, pos) { + if (!this.app.options.showHighlighter) return false; + if (!this.app.options.attemptHighlight) return false; + + // Check if there is a carriage return in the output dish as this will not + // be displayed by the HTML textarea and will mess up highlighting offsets. + if (await this.manager.output.containsCR()) return false; + + const startPlaceholder = "[startHighlight]"; + const startPlaceholderRegex = /\[startHighlight\]/g; + const endPlaceholder = "[endHighlight]"; + const endPlaceholderRegex = /\[endHighlight\]/g; + let text = textarea.value; + + // Put placeholders in position + // If there's only one value, select that + // If there are multiple, ignore the first one and select all others + if (pos.length === 1) { + if (pos[0].end < pos[0].start) return; + text = text.slice(0, pos[0].start) + + startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder + + text.slice(pos[0].end, text.length); + } else { + // O(n^2) - Can anyone improve this without overwriting placeholders? + let result = "", + endPlaced = true; + + for (let i = 0; i < text.length; i++) { + for (let j = 1; j < pos.length; j++) { + if (pos[j].end < pos[j].start) continue; + if (pos[j].start === i) { + result += startPlaceholder; + endPlaced = false; + } + if (pos[j].end === i) { + result += endPlaceholder; + endPlaced = true; + } + } + result += text[i]; + } + if (!endPlaced) result += endPlaceholder; + text = result; + } + + const cssClass = "hl1"; + //if (colour) cssClass += "-"+colour; + + // Remove HTML tags + text = text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/\n/g, " ") + // Convert placeholders to tags + .replace(startPlaceholderRegex, "") + .replace(endPlaceholderRegex, "") + " "; + + // Adjust width to allow for scrollbars + highlighter.style.width = textarea.clientWidth + "px"; + highlighter.innerHTML = text; + highlighter.scrollTop = textarea.scrollTop; + highlighter.scrollLeft = textarea.scrollLeft; + } + +} + +export default HighlighterWaiter; diff --git a/src/web/InputWaiter.js b/src/web/InputWaiter.js deleted file mode 100755 index 040716bb..00000000 --- a/src/web/InputWaiter.js +++ /dev/null @@ -1,222 +0,0 @@ -import Utils from "../core/Utils.js"; - - -/** - * Waiter to handle events related to the input. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const InputWaiter = function(app, manager) { - this.app = app; - this.manager = manager; - - // Define keys that don't change the input so we don't have to autobake when they are pressed - this.badKeys = [ - 16, //Shift - 17, //Ctrl - 18, //Alt - 19, //Pause - 20, //Caps - 27, //Esc - 33, 34, 35, 36, //PgUp, PgDn, End, Home - 37, 38, 39, 40, //Directional - 44, //PrntScrn - 91, 92, //Win - 93, //Context - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, //F1-12 - 144, //Num - 145, //Scroll - ]; -}; - - -/** - * Gets the user's input from the input textarea. - * - * @returns {string} - */ -InputWaiter.prototype.get = function() { - return document.getElementById("input-text").value; -}; - - -/** - * Sets the input in the input textarea. - * - * @param {string} input - * - * @fires Manager#statechange - */ -InputWaiter.prototype.set = function(input) { - document.getElementById("input-text").value = input; - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Displays information about the input. - * - * @param {number} length - The length of the current input string - * @param {number} lines - The number of the lines in the current input string - */ -InputWaiter.prototype.setInputInfo = function(length, lines) { - let width = length.toString().length; - width = width < 2 ? 2 : width; - - const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " "); - const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " "); - - document.getElementById("input-info").innerHTML = "length: " + lengthStr + "
    lines: " + linesStr; -}; - - -/** - * Handler for input scroll events. - * Scrolls the highlighter pane to match the input textarea position and updates history state. - * - * @param {event} e - * - * @fires Manager#statechange - */ -InputWaiter.prototype.inputChange = function(e) { - // Remove highlighting from input and output panes as the offsets might be different now - this.manager.highlighter.removeHighlights(); - - // Reset recipe progress as any previous processing will be redundant now - this.app.progress = 0; - - // Update the input metadata info - const inputText = this.get(); - const lines = inputText.count("\n") + 1; - - this.setInputInfo(inputText.length, lines); - - - if (this.badKeys.indexOf(e.keyCode) < 0) { - // Fire the statechange event as the input has been modified - window.dispatchEvent(this.manager.statechange); - } -}; - - -/** - * Handler for input dragover events. - * Gives the user a visual cue to show that items can be dropped here. - * - * @param {event} e - */ -InputWaiter.prototype.inputDragover = function(e) { - // This will be set if we're dragging an operation - if (e.dataTransfer.effectAllowed === "move") - return false; - - e.stopPropagation(); - e.preventDefault(); - e.target.classList.add("dropping-file"); -}; - - -/** - * Handler for input dragleave events. - * Removes the visual cue. - * - * @param {event} e - */ -InputWaiter.prototype.inputDragleave = function(e) { - e.stopPropagation(); - e.preventDefault(); - e.target.classList.remove("dropping-file"); -}; - - -/** - * Handler for input drop events. - * Loads the dragged data into the input textarea. - * - * @param {event} e - */ -InputWaiter.prototype.inputDrop = function(e) { - // This will be set if we're dragging an operation - if (e.dataTransfer.effectAllowed === "move") - return false; - - e.stopPropagation(); - e.preventDefault(); - - const el = e.target; - const file = e.dataTransfer.files[0]; - const text = e.dataTransfer.getData("Text"); - const reader = new FileReader(); - let inputCharcode = ""; - let offset = 0; - const CHUNK_SIZE = 20480; // 20KB - - const setInput = function() { - if (inputCharcode.length > 100000 && this.app.autoBake_) { - this.manager.controls.setAutoBake(false); - this.app.alert("Turned off Auto Bake as the input is large", "warning", 5000); - } - - this.set(inputCharcode); - const recipeConfig = this.app.getRecipeConfig(); - if (!recipeConfig[0] || recipeConfig[0].op !== "From Hex") { - recipeConfig.unshift({op:"From Hex", args:["Space"]}); - this.app.setRecipeConfig(recipeConfig); - } - - el.classList.remove("loadingFile"); - }.bind(this); - - const seek = function() { - if (offset >= file.size) { - setInput(); - return; - } - el.value = "Processing... " + Math.round(offset / file.size * 100) + "%"; - const slice = file.slice(offset, offset + CHUNK_SIZE); - reader.readAsArrayBuffer(slice); - }; - - reader.onload = function(e) { - const data = new Uint8Array(reader.result); - inputCharcode += Utils.toHexFast(data); - offset += CHUNK_SIZE; - seek(); - }; - - - el.classList.remove("dropping-file"); - - if (file) { - el.classList.add("loadingFile"); - seek(); - } else if (text) { - this.set(text); - } -}; - - -/** - * Handler for clear IO events. - * Resets the input, output and info areas. - * - * @fires Manager#statechange - */ -InputWaiter.prototype.clearIoClick = function() { - this.manager.highlighter.removeHighlights(); - document.getElementById("input-text").value = ""; - document.getElementById("output-text").value = ""; - document.getElementById("input-info").innerHTML = ""; - document.getElementById("output-info").innerHTML = ""; - document.getElementById("input-selection-info").innerHTML = ""; - document.getElementById("output-selection-info").innerHTML = ""; - window.dispatchEvent(this.manager.statechange); -}; - -export default InputWaiter; diff --git a/src/web/InputWaiter.mjs b/src/web/InputWaiter.mjs new file mode 100755 index 00000000..17b48b6f --- /dev/null +++ b/src/web/InputWaiter.mjs @@ -0,0 +1,354 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker"; +import Utils from "../core/Utils"; + + +/** + * Waiter to handle events related to the input. + */ +class InputWaiter { + + /** + * InputWaiter 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; + + // Define keys that don't change the input so we don't have to autobake when they are pressed + this.badKeys = [ + 16, //Shift + 17, //Ctrl + 18, //Alt + 19, //Pause + 20, //Caps + 27, //Esc + 33, 34, 35, 36, //PgUp, PgDn, End, Home + 37, 38, 39, 40, //Directional + 44, //PrntScrn + 91, 92, //Win + 93, //Context + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, //F1-12 + 144, //Num + 145, //Scroll + ]; + + this.loaderWorker = null; + this.fileBuffer = null; + } + + + /** + * Gets the user's input from the input textarea. + * + * @returns {string} + */ + get() { + return this.fileBuffer || document.getElementById("input-text").value; + } + + + /** + * Sets the input in the input area. + * + * @param {string|File} input + * @param {boolean} [silent=false] - Suppress statechange event + * + * @fires Manager#statechange + */ + set(input, silent=false) { + const inputText = document.getElementById("input-text"); + if (input instanceof File) { + this.setFile(input); + inputText.value = ""; + this.setInputInfo(input.size, null); + } else { + inputText.value = input; + this.closeFile(); + if (!silent) window.dispatchEvent(this.manager.statechange); + const lines = input.length < (this.app.options.ioDisplayThreshold * 1024) ? + input.count("\n") + 1 : null; + this.setInputInfo(input.length, lines); + } + } + + + /** + * Shows file details. + * + * @param {File} file + */ + setFile(file) { + // Display file overlay in input area with details + const fileOverlay = document.getElementById("input-file"), + fileName = document.getElementById("input-file-name"), + fileSize = document.getElementById("input-file-size"), + fileType = document.getElementById("input-file-type"), + fileLoaded = document.getElementById("input-file-loaded"); + + this.fileBuffer = new ArrayBuffer(); + fileOverlay.style.display = "block"; + fileName.textContent = file.name; + fileSize.textContent = file.size.toLocaleString() + " bytes"; + fileType.textContent = file.type || "unknown"; + fileLoaded.textContent = "0%"; + } + + + /** + * Displays information about the input. + * + * @param {number} length - The length of the current input string + * @param {number} lines - The number of the lines in the current input string + */ + setInputInfo(length, lines) { + let width = length.toString().length; + width = width < 2 ? 2 : width; + + const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); + let msg = "length: " + lengthStr; + + if (typeof lines === "number") { + const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); + msg += "
    lines: " + linesStr; + } + + document.getElementById("input-info").innerHTML = msg; + } + + + /** + * Handler for input change events. + * + * @param {event} e + * + * @fires Manager#statechange + */ + inputChange(e) { + // Ignore this function if the input is a File + if (this.fileBuffer) return; + + // Remove highlighting from input and output panes as the offsets might be different now + this.manager.highlighter.removeHighlights(); + + // Reset recipe progress as any previous processing will be redundant now + this.app.progress = 0; + + // Update the input metadata info + const inputText = this.get(); + const lines = inputText.length < (this.app.options.ioDisplayThreshold * 1024) ? + inputText.count("\n") + 1 : null; + + this.setInputInfo(inputText.length, lines); + + if (e && this.badKeys.indexOf(e.keyCode) < 0) { + // Fire the statechange event as the input has been modified + window.dispatchEvent(this.manager.statechange); + } + } + + + /** + * Handler for input paste events. + * Checks that the size of the input is below the display limit, otherwise treats it as a file/blob. + * + * @param {event} e + */ + inputPaste(e) { + const pastedData = e.clipboardData.getData("Text"); + + if (pastedData.length < (this.app.options.ioDisplayThreshold * 1024)) { + this.inputChange(e); + } else { + e.preventDefault(); + e.stopPropagation(); + + const file = new File([pastedData], "PastedData", { + type: "text/plain", + lastModified: Date.now() + }); + + this.loaderWorker = new LoaderWorker(); + this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); + this.loaderWorker.postMessage({"file": file}); + this.set(file); + return false; + } + } + + + /** + * Handler for input dragover events. + * Gives the user a visual cue to show that items can be dropped here. + * + * @param {event} e + */ + inputDragover(e) { + // This will be set if we're dragging an operation + if (e.dataTransfer.effectAllowed === "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + e.target.closest("#input-text,#input-file").classList.add("dropping-file"); + } + + + /** + * Handler for input dragleave events. + * Removes the visual cue. + * + * @param {event} e + */ + inputDragleave(e) { + e.stopPropagation(); + e.preventDefault(); + document.getElementById("input-text").classList.remove("dropping-file"); + document.getElementById("input-file").classList.remove("dropping-file"); + } + + + /** + * Handler for input drop events. + * Loads the dragged data into the input textarea. + * + * @param {event} e + */ + inputDrop(e) { + // This will be set if we're dragging an operation + if (e.dataTransfer.effectAllowed === "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + + const file = e.dataTransfer.files[0]; + const text = e.dataTransfer.getData("Text"); + + document.getElementById("input-text").classList.remove("dropping-file"); + document.getElementById("input-file").classList.remove("dropping-file"); + + if (text) { + this.closeFile(); + this.set(text); + return; + } + + if (file) { + this.loadFile(file); + } + } + + /** + * Handler for open input button events + * Loads the opened data into the input textarea + * + * @param {event} e + */ + inputOpen(e) { + e.preventDefault(); + const file = e.srcElement.files[0]; + this.loadFile(file); + } + + + /** + * Handler for messages sent back by the LoaderWorker. + * + * @param {MessageEvent} e + */ + handleLoaderMessage(e) { + const r = e.data; + if (r.hasOwnProperty("progress")) { + const fileLoaded = document.getElementById("input-file-loaded"); + fileLoaded.textContent = r.progress + "%"; + } + + if (r.hasOwnProperty("error")) { + this.app.alert(r.error, 10000); + } + + if (r.hasOwnProperty("fileBuffer")) { + log.debug("Input file loaded"); + this.fileBuffer = r.fileBuffer; + this.displayFilePreview(); + window.dispatchEvent(this.manager.statechange); + } + } + + + /** + * Shows a chunk of the file in the input behind the file overlay. + */ + displayFilePreview() { + const inputText = document.getElementById("input-text"), + fileSlice = this.fileBuffer.slice(0, 4096); + + inputText.style.overflow = "hidden"; + inputText.classList.add("blur"); + inputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice)); + if (this.fileBuffer.byteLength > 4096) { + inputText.value += "[truncated]..."; + } + } + + + /** + * Handler for file close events. + */ + closeFile() { + if (this.loaderWorker) this.loaderWorker.terminate(); + this.fileBuffer = null; + document.getElementById("input-file").style.display = "none"; + const inputText = document.getElementById("input-text"); + inputText.style.overflow = "auto"; + inputText.classList.remove("blur"); + } + + + /** + * Loads a file into the input. + * + * @param {File} file + */ + loadFile(file) { + if (file) { + this.closeFile(); + this.loaderWorker = new LoaderWorker(); + this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); + this.loaderWorker.postMessage({"file": file}); + this.set(file); + } + } + + + /** + * Handler for clear IO events. + * Resets the input, output and info areas. + * + * @fires Manager#statechange + */ + clearIoClick() { + this.closeFile(); + this.manager.output.closeFile(); + this.manager.highlighter.removeHighlights(); + document.getElementById("input-text").value = ""; + document.getElementById("output-text").value = ""; + document.getElementById("input-info").innerHTML = ""; + document.getElementById("output-info").innerHTML = ""; + document.getElementById("input-selection-info").innerHTML = ""; + document.getElementById("output-selection-info").innerHTML = ""; + window.dispatchEvent(this.manager.statechange); + } + +} + +export default InputWaiter; diff --git a/src/web/LoaderWorker.js b/src/web/LoaderWorker.js new file mode 100755 index 00000000..076e0e7d --- /dev/null +++ b/src/web/LoaderWorker.js @@ -0,0 +1,54 @@ +/** + * Web Worker to load large amounts of data without locking up the UI. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + + +/** + * Respond to message from parent thread. + */ +self.addEventListener("message", function(e) { + const r = e.data; + if (r.hasOwnProperty("file")) { + self.loadFile(r.file); + } +}); + + +/** + * Loads a file object into an ArrayBuffer, then transfers it back to the parent thread. + * + * @param {File} file + */ +self.loadFile = function(file) { + const reader = new FileReader(); + const data = new Uint8Array(file.size); + let offset = 0; + const CHUNK_SIZE = 10485760; // 10MiB + + const seek = function() { + if (offset >= file.size) { + self.postMessage({"progress": 100}); + self.postMessage({"fileBuffer": data.buffer}, [data.buffer]); + return; + } + self.postMessage({"progress": Math.round(offset / file.size * 100)}); + const slice = file.slice(offset, offset + CHUNK_SIZE); + reader.readAsArrayBuffer(slice); + }; + + reader.onload = function(e) { + data.set(new Uint8Array(reader.result), offset); + offset += CHUNK_SIZE; + seek(); + }; + + reader.onerror = function(e) { + self.postMessage({"error": reader.error.message}); + }; + + seek(); +}; diff --git a/src/web/Manager.js b/src/web/Manager.js deleted file mode 100755 index ead3123d..00000000 --- a/src/web/Manager.js +++ /dev/null @@ -1,280 +0,0 @@ -import WindowWaiter from "./WindowWaiter.js"; -import ControlsWaiter from "./ControlsWaiter.js"; -import RecipeWaiter from "./RecipeWaiter.js"; -import OperationsWaiter from "./OperationsWaiter.js"; -import InputWaiter from "./InputWaiter.js"; -import OutputWaiter from "./OutputWaiter.js"; -import OptionsWaiter from "./OptionsWaiter.js"; -import HighlighterWaiter from "./HighlighterWaiter.js"; -import SeasonalWaiter from "./SeasonalWaiter.js"; - - -/** - * This object controls the Waiters responsible for handling events from all areas of the app. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - */ -const Manager = function(app) { - this.app = app; - - // Define custom events - /** - * @event Manager#appstart - */ - this.appstart = new CustomEvent("appstart", {bubbles: true}); - /** - * @event Manager#operationadd - */ - this.operationadd = new CustomEvent("operationadd", {bubbles: true}); - /** - * @event Manager#operationremove - */ - this.operationremove = new CustomEvent("operationremove", {bubbles: true}); - /** - * @event Manager#oplistcreate - */ - this.oplistcreate = new CustomEvent("oplistcreate", {bubbles: true}); - /** - * @event Manager#statechange - */ - this.statechange = new CustomEvent("statechange", {bubbles: true}); - - // Define Waiter objects to handle various areas - this.window = new WindowWaiter(this.app); - this.controls = new ControlsWaiter(this.app, this); - this.recipe = new RecipeWaiter(this.app, this); - this.ops = new OperationsWaiter(this.app, this); - this.input = new InputWaiter(this.app, this); - this.output = new OutputWaiter(this.app, this); - this.options = new OptionsWaiter(this.app); - this.highlighter = new HighlighterWaiter(this.app); - this.seasonal = new SeasonalWaiter(this.app, this); - - // Object to store dynamic handlers to fire on elements that may not exist yet - this.dynamicHandlers = {}; - - this.initialiseEventListeners(); -}; - - -/** - * Sets up the various components and listeners. - */ -Manager.prototype.setup = function() { - this.recipe.initialiseOperationDragNDrop(); - this.controls.autoBakeChange(); - this.seasonal.load(); -}; - - -/** - * Main function to handle the creation of the event listeners. - */ -Manager.prototype.initialiseEventListeners = function() { - // Global - window.addEventListener("resize", this.window.windowResize.bind(this.window)); - window.addEventListener("blur", this.window.windowBlur.bind(this.window)); - window.addEventListener("focus", this.window.windowFocus.bind(this.window)); - window.addEventListener("statechange", this.app.stateChange.bind(this.app)); - window.addEventListener("popstate", this.app.popState.bind(this.app)); - - // Controls - document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls)); - document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls)); - document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls)); - document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls)); - document.getElementById("clr-breaks").addEventListener("click", this.controls.clearBreaksClick.bind(this.controls)); - document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls)); - document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls)); - document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls)); - document.getElementById("save-link-input-checkbox").addEventListener("change", this.controls.sliCheckChange.bind(this.controls)); - document.getElementById("load").addEventListener("click", this.controls.loadClick.bind(this.controls)); - document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls)); - document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls)); - document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls)); - document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls)); - this.addMultiEventListener("#save-text", "keyup paste", this.controls.saveTextChange, this.controls); - - // Operations - this.addMultiEventListener("#search", "keyup paste search", this.ops.searchOperations, this.ops); - this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, this.ops); - document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops)); - document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops)); - document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops)); - this.addDynamicListener(".op-list .op-icon", "mouseover", this.ops.opIconMouseover, this.ops); - this.addDynamicListener(".op-list .op-icon", "mouseleave", this.ops.opIconMouseleave, this.ops); - this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops); - this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd.bind(this.recipe)); - - // Recipe - this.addDynamicListener(".arg", "keyup", this.recipe.ingChange, this.recipe); - this.addDynamicListener(".arg", "change", this.recipe.ingChange, this.recipe); - this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe); - this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe); - this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe); - this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe); - this.addDynamicListener("#rec-list .input-group .dropdown-menu a", "click", this.recipe.dropdownToggleClick, this.recipe); - this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe)); - - // Input - this.addMultiEventListener("#input-text", "keyup paste", this.input.inputChange, this.input); - document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); - document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input)); - document.getElementById("input-text").addEventListener("dragover", this.input.inputDragover.bind(this.input)); - document.getElementById("input-text").addEventListener("dragleave", this.input.inputDragleave.bind(this.input)); - document.getElementById("input-text").addEventListener("drop", this.input.inputDrop.bind(this.input)); - document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter)); - document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter)); - document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter)); - this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter); - - // Output - document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output)); - document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output)); - document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output)); - document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); - document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter)); - document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter)); - document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter)); - document.getElementById("output-html").addEventListener("mouseup", this.highlighter.outputHtmlMouseup.bind(this.highlighter)); - document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter)); - this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); - this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter); - - // Options - document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options)); - document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options)); - $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.switchChange.bind(this.options)); - $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.setWordWrap.bind(this.options)); - 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); - this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); - document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options)); - - // Misc - document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app)); -}; - - -/** - * Adds an event listener to each element in the specified group. - * - * @param {string} selector - A selector string for the element group to add the event to, see - * this.getAll() - * @param {string} eventType - The event to listen for - * @param {function} callback - The function to execute when the event is triggered - * @param {Object} [scope=this] - The object to bind to the callback function - * - * @example - * // Calls the clickable function whenever any element with the .clickable class is clicked - * this.addListeners(".clickable", "click", this.clickable, this); - */ -Manager.prototype.addListeners = function(selector, eventType, callback, scope) { - scope = scope || this; - [].forEach.call(document.querySelectorAll(selector), function(el) { - el.addEventListener(eventType, callback.bind(scope)); - }); -}; - - -/** - * Adds multiple event listeners to the specified element. - * - * @param {string} selector - A selector string for the element to add the events to - * @param {string} eventTypes - A space-separated string of all the event types to listen for - * @param {function} callback - The function to execute when the events are triggered - * @param {Object} [scope=this] - The object to bind to the callback function - * - * @example - * // Calls the search function whenever the the keyup, paste or search events are triggered on the - * // search element - * this.addMultiEventListener("search", "keyup paste search", this.search, this); - */ -Manager.prototype.addMultiEventListener = function(selector, eventTypes, callback, scope) { - const evs = eventTypes.split(" "); - for (let i = 0; i < evs.length; i++) { - document.querySelector(selector).addEventListener(evs[i], callback.bind(scope)); - } -}; - - -/** - * Adds multiple event listeners to each element in the specified group. - * - * @param {string} selector - A selector string for the element group to add the events to - * @param {string} eventTypes - A space-separated string of all the event types to listen for - * @param {function} callback - The function to execute when the events are triggered - * @param {Object} [scope=this] - The object to bind to the callback function - * - * @example - * // Calls the save function whenever the the keyup or paste events are triggered on any element - * // with the .saveable class - * this.addMultiEventListener(".saveable", "keyup paste", this.save, this); - */ -Manager.prototype.addMultiEventListeners = function(selector, eventTypes, callback, scope) { - const evs = eventTypes.split(" "); - for (let i = 0; i < evs.length; i++) { - this.addListeners(selector, evs[i], callback, scope); - } -}; - - -/** - * Adds an event listener to the global document object which will listen on dynamic elements which - * may not exist in the DOM yet. - * - * @param {string} selector - A selector string for the element(s) to add the event to - * @param {string} eventType - The event(s) to listen for - * @param {function} callback - The function to execute when the event(s) is/are triggered - * @param {Object} [scope=this] - The object to bind to the callback function - * - * @example - * // Pops up an alert whenever any button is clicked, even if it is added to the DOM after this - * // listener is created - * this.addDynamicListener("button", "click", alert, this); - */ -Manager.prototype.addDynamicListener = function(selector, eventType, callback, scope) { - const eventConfig = { - selector: selector, - callback: callback.bind(scope || this) - }; - - if (this.dynamicHandlers.hasOwnProperty(eventType)) { - // Listener already exists, add new handler to the appropriate list - this.dynamicHandlers[eventType].push(eventConfig); - } else { - this.dynamicHandlers[eventType] = [eventConfig]; - // Set up listener for this new type - document.addEventListener(eventType, this.dynamicListenerHandler.bind(this)); - } -}; - - -/** - * Handler for dynamic events. This function is called for any dynamic event and decides which - * callback(s) to execute based on the type and selector. - * - * @param {Event} e - The event to be handled - */ -Manager.prototype.dynamicListenerHandler = function(e) { - const { type, target } = e; - const handlers = this.dynamicHandlers[type]; - const matches = target.matches || - target.webkitMatchesSelector || - target.mozMatchesSelector || - target.msMatchesSelector || - target.oMatchesSelector; - - for (let i = 0; i < handlers.length; i++) { - if (matches && matches.call(target, handlers[i].selector)) { - handlers[i].callback(e); - } - } -}; - -export default Manager; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs new file mode 100755 index 00000000..30cb4943 --- /dev/null +++ b/src/web/Manager.mjs @@ -0,0 +1,312 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import WorkerWaiter from "./WorkerWaiter"; +import WindowWaiter from "./WindowWaiter"; +import ControlsWaiter from "./ControlsWaiter"; +import RecipeWaiter from "./RecipeWaiter"; +import OperationsWaiter from "./OperationsWaiter"; +import InputWaiter from "./InputWaiter"; +import OutputWaiter from "./OutputWaiter"; +import OptionsWaiter from "./OptionsWaiter"; +import HighlighterWaiter from "./HighlighterWaiter"; +import SeasonalWaiter from "./SeasonalWaiter"; +import BindingsWaiter from "./BindingsWaiter"; +import BackgroundWorkerWaiter from "./BackgroundWorkerWaiter"; + + +/** + * This object controls the Waiters responsible for handling events from all areas of the app. + */ +class Manager { + + /** + * Manager constructor. + * + * @param {App} app - The main view object for CyberChef. + */ + constructor(app) { + this.app = app; + + // Define custom events + /** + * @event Manager#appstart + */ + this.appstart = new CustomEvent("appstart", {bubbles: true}); + /** + * @event Manager#apploaded + */ + this.apploaded = new CustomEvent("apploaded", {bubbles: true}); + /** + * @event Manager#operationadd + */ + this.operationadd = new CustomEvent("operationadd", {bubbles: true}); + /** + * @event Manager#operationremove + */ + this.operationremove = new CustomEvent("operationremove", {bubbles: true}); + /** + * @event Manager#oplistcreate + */ + this.oplistcreate = new CustomEvent("oplistcreate", {bubbles: true}); + /** + * @event Manager#statechange + */ + this.statechange = new CustomEvent("statechange", {bubbles: true}); + + // Define Waiter objects to handle various areas + this.worker = new WorkerWaiter(this.app, this); + this.window = new WindowWaiter(this.app); + this.controls = new ControlsWaiter(this.app, this); + this.recipe = new RecipeWaiter(this.app, this); + this.ops = new OperationsWaiter(this.app, this); + this.input = new InputWaiter(this.app, this); + this.output = new OutputWaiter(this.app, this); + this.options = new OptionsWaiter(this.app, this); + this.highlighter = new HighlighterWaiter(this.app, this); + this.seasonal = new SeasonalWaiter(this.app, this); + this.bindings = new BindingsWaiter(this.app, this); + this.background = new BackgroundWorkerWaiter(this.app, this); + + // Object to store dynamic handlers to fire on elements that may not exist yet + this.dynamicHandlers = {}; + + this.initialiseEventListeners(); + } + + + /** + * Sets up the various components and listeners. + */ + setup() { + this.worker.registerChefWorker(); + this.recipe.initialiseOperationDragNDrop(); + this.controls.initComponents(); + this.controls.autoBakeChange(); + this.bindings.updateKeybList(); + this.background.registerChefWorker(); + this.seasonal.load(); + } + + + /** + * Main function to handle the creation of the event listeners. + */ + initialiseEventListeners() { + // Global + window.addEventListener("resize", this.window.windowResize.bind(this.window)); + window.addEventListener("blur", this.window.windowBlur.bind(this.window)); + window.addEventListener("focus", this.window.windowFocus.bind(this.window)); + window.addEventListener("statechange", this.app.stateChange.bind(this.app)); + window.addEventListener("popstate", this.app.popState.bind(this.app)); + + // Controls + document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls)); + document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls)); + document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls)); + document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls)); + document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls)); + document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls)); + document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls)); + document.getElementById("save-link-input-checkbox").addEventListener("change", this.controls.sliCheckChange.bind(this.controls)); + document.getElementById("load").addEventListener("click", this.controls.loadClick.bind(this.controls)); + document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls)); + document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls)); + document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls)); + document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls)); + this.addMultiEventListeners("#save-texts textarea", "keyup paste", this.controls.saveTextChange, this.controls); + + // Operations + this.addMultiEventListener("#search", "keyup paste search", this.ops.searchOperations, this.ops); + this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, this.ops); + document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops)); + document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops)); + document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops)); + this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops); + this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe); + + // Recipe + this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe); + this.addDynamicListener(".arg[type=checkbox], .arg[type=radio], select.arg", "change", this.recipe.ingChange, this.recipe); + this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe); + this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe); + this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe); + this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe); + this.addDynamicListener("#rec-list .dropdown-menu.toggle-dropdown a", "click", this.recipe.dropdownToggleClick, this.recipe); + this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe)); + this.addDynamicListener("textarea.arg", "dragover", this.recipe.textArgDragover, this.recipe); + this.addDynamicListener("textarea.arg", "dragleave", this.recipe.textArgDragLeave, this.recipe); + this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe); + + // Input + this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input); + this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input); + document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); + document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input)); + this.addListeners("#open-file", "change", this.input.inputOpen, this.input); + this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input); + this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input); + this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input); + document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter)); + document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter)); + document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter)); + this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter); + document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input)); + + // Output + document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output)); + document.getElementById("copy-output").addEventListener("click", this.output.copyClick.bind(this.output)); + document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output)); + document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output)); + document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); + document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output)); + document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter)); + document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter)); + document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter)); + document.getElementById("output-html").addEventListener("mouseup", this.highlighter.outputHtmlMouseup.bind(this.highlighter)); + document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter)); + this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); + this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter); + this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); + this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output); + document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output)); + + // Options + 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]#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); + this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); + document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options)); + document.getElementById("logLevel").addEventListener("change", this.options.logLevelChange.bind(this.options)); + + // Misc + window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); + } + + + /** + * Adds an event listener to each element in the specified group. + * + * @param {string} selector - A selector string for the element group to add the event to, see + * this.getAll() + * @param {string} eventType - The event to listen for + * @param {function} callback - The function to execute when the event is triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Calls the clickable function whenever any element with the .clickable class is clicked + * this.addListeners(".clickable", "click", this.clickable, this); + */ + addListeners(selector, eventType, callback, scope) { + scope = scope || this; + [].forEach.call(document.querySelectorAll(selector), function(el) { + el.addEventListener(eventType, callback.bind(scope)); + }); + } + + + /** + * Adds multiple event listeners to the specified element. + * + * @param {string} selector - A selector string for the element to add the events to + * @param {string} eventTypes - A space-separated string of all the event types to listen for + * @param {function} callback - The function to execute when the events are triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Calls the search function whenever the the keyup, paste or search events are triggered on the + * // search element + * this.addMultiEventListener("search", "keyup paste search", this.search, this); + */ + addMultiEventListener(selector, eventTypes, callback, scope) { + const evs = eventTypes.split(" "); + for (let i = 0; i < evs.length; i++) { + document.querySelector(selector).addEventListener(evs[i], callback.bind(scope)); + } + } + + + /** + * Adds multiple event listeners to each element in the specified group. + * + * @param {string} selector - A selector string for the element group to add the events to + * @param {string} eventTypes - A space-separated string of all the event types to listen for + * @param {function} callback - The function to execute when the events are triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Calls the save function whenever the the keyup or paste events are triggered on any element + * // with the .saveable class + * this.addMultiEventListener(".saveable", "keyup paste", this.save, this); + */ + addMultiEventListeners(selector, eventTypes, callback, scope) { + const evs = eventTypes.split(" "); + for (let i = 0; i < evs.length; i++) { + this.addListeners(selector, evs[i], callback, scope); + } + } + + + /** + * Adds an event listener to the global document object which will listen on dynamic elements which + * may not exist in the DOM yet. + * + * @param {string} selector - A selector string for the element(s) to add the event to + * @param {string} eventType - The event(s) to listen for + * @param {function} callback - The function to execute when the event(s) is/are triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Pops up an alert whenever any button is clicked, even if it is added to the DOM after this + * // listener is created + * this.addDynamicListener("button", "click", alert, this); + */ + addDynamicListener(selector, eventType, callback, scope) { + const eventConfig = { + selector: selector, + callback: callback.bind(scope || this) + }; + + if (this.dynamicHandlers.hasOwnProperty(eventType)) { + // Listener already exists, add new handler to the appropriate list + this.dynamicHandlers[eventType].push(eventConfig); + } else { + this.dynamicHandlers[eventType] = [eventConfig]; + // Set up listener for this new type + document.addEventListener(eventType, this.dynamicListenerHandler.bind(this)); + } + } + + + /** + * Handler for dynamic events. This function is called for any dynamic event and decides which + * callback(s) to execute based on the type and selector. + * + * @param {Event} e - The event to be handled + */ + dynamicListenerHandler(e) { + const { type, target } = e; + const handlers = this.dynamicHandlers[type]; + const matches = target.matches || + target.webkitMatchesSelector || + target.mozMatchesSelector || + target.msMatchesSelector || + target.oMatchesSelector; + + for (let i = 0; i < handlers.length; i++) { + if (matches && matches.call(target, handlers[i].selector)) { + handlers[i].callback(e); + } + } + } + +} + +export default Manager; diff --git a/src/web/OperationsWaiter.js b/src/web/OperationsWaiter.js deleted file mode 100755 index 992b737e..00000000 --- a/src/web/OperationsWaiter.js +++ /dev/null @@ -1,286 +0,0 @@ -import HTMLOperation from "./HTMLOperation.js"; -import Sortable from "sortablejs"; - - -/** - * Waiter to handle events related to the operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const OperationsWaiter = function(app, manager) { - this.app = app; - this.manager = manager; - - this.options = {}; - this.removeIntent = false; -}; - - -/** - * Handler for search events. - * Finds operations which match the given search term and displays them under the search box. - * - * @param {event} e - */ -OperationsWaiter.prototype.searchOperations = function(e) { - let ops, selected; - - if (e.type === "search") { // Search - e.preventDefault(); - ops = document.querySelectorAll("#search-results li"); - if (ops.length) { - selected = this.getSelectedOp(ops); - if (selected > -1) { - this.manager.recipe.addOperation(ops[selected].innerHTML); - this.app.autoBake(); - } - } - } - - if (e.keyCode === 13) { // Return - e.preventDefault(); - } else if (e.keyCode === 40) { // Down - e.preventDefault(); - ops = document.querySelectorAll("#search-results li"); - if (ops.length) { - selected = this.getSelectedOp(ops); - if (selected > -1) { - ops[selected].classList.remove("selected-op"); - } - if (selected === ops.length-1) selected = -1; - ops[selected+1].classList.add("selected-op"); - } - } else if (e.keyCode === 38) { // Up - e.preventDefault(); - ops = document.querySelectorAll("#search-results li"); - if (ops.length) { - selected = this.getSelectedOp(ops); - if (selected > -1) { - ops[selected].classList.remove("selected-op"); - } - if (selected === 0) selected = ops.length; - ops[selected-1].classList.add("selected-op"); - } - } else { - const searchResultsEl = document.getElementById("search-results"); - const el = e.target; - const str = el.value; - - while (searchResultsEl.firstChild) { - try { - $(searchResultsEl.firstChild).popover("destroy"); - } catch (err) {} - searchResultsEl.removeChild(searchResultsEl.firstChild); - } - - $("#categories .in").collapse("hide"); - if (str) { - const matchedOps = this.filterOperations(str, true); - const matchedOpsHtml = matchedOps - .map(v => v.toStubHtml()) - .join(""); - - searchResultsEl.innerHTML = matchedOpsHtml; - searchResultsEl.dispatchEvent(this.manager.oplistcreate); - } - } -}; - - -/** - * Filters operations based on the search string and returns the matching ones. - * - * @param {string} searchStr - * @param {boolean} highlight - Whether or not to highlight the matching string in the operation - * name and description - * @returns {string[]} - */ -OperationsWaiter.prototype.filterOperations = function(inStr, highlight) { - const matchedOps = []; - const matchedDescs = []; - - const searchStr = inStr.toLowerCase(); - - for (const opName in this.app.operations) { - const op = this.app.operations[opName]; - const namePos = opName.toLowerCase().indexOf(searchStr); - const descPos = op.description.toLowerCase().indexOf(searchStr); - - if (namePos >= 0 || descPos >= 0) { - const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); - if (highlight) { - operation.highlightSearchString(searchStr, namePos, descPos); - } - - if (namePos < 0) { - matchedOps.push(operation); - } else { - matchedDescs.push(operation); - } - } - } - - return matchedDescs.concat(matchedOps); -}; - - -/** - * Finds the operation which has been selected using keyboard shortcuts. This will have the class - * 'selected-op' set. Returns the index of the operation within the given list. - * - * @param {element[]} ops - * @returns {number} - */ -OperationsWaiter.prototype.getSelectedOp = function(ops) { - for (let i = 0; i < ops.length; i++) { - if (ops[i].classList.contains("selected-op")) { - return i; - } - } - return -1; -}; - - -/** - * Handler for oplistcreate events. - * - * @listens Manager#oplistcreate - * @param {event} e - */ -OperationsWaiter.prototype.opListCreate = function(e) { - this.manager.recipe.createSortableSeedList(e.target); - $("[data-toggle=popover]").popover(); -}; - - -/** - * Handler for operation doubleclick events. - * Adds the operation to the recipe and auto bakes. - * - * @param {event} e - */ -OperationsWaiter.prototype.operationDblclick = function(e) { - const li = e.target; - - this.manager.recipe.addOperation(li.textContent); - this.app.autoBake(); -}; - - -/** - * Handler for edit favourites click events. - * Sets up the 'Edit favourites' pane and displays it. - * - * @param {event} e - */ -OperationsWaiter.prototype.editFavouritesClick = function(e) { - e.preventDefault(); - e.stopPropagation(); - - // Add favourites to modal - const favCat = this.app.categories.filter(function(c) { - return c.name === "Favourites"; - })[0]; - - let html = ""; - for (let i = 0; i < favCat.ops.length; i++) { - const opName = favCat.ops[i]; - const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); - html += operation.toStubHtml(true); - } - - const editFavouritesList = document.getElementById("edit-favourites-list"); - editFavouritesList.innerHTML = html; - this.removeIntent = false; - - const editableList = Sortable.create(editFavouritesList, { - filter: ".remove-icon", - onFilter: function (evt) { - const el = editableList.closest(evt.item); - if (el) { - $(el).popover("destroy"); - el.parentNode.removeChild(el); - } - }, - onEnd: function(evt) { - if (this.removeIntent) { - $(evt.item).popover("destroy"); - evt.item.remove(); - } - }.bind(this), - }); - - Sortable.utils.on(editFavouritesList, "dragleave", function() { - this.removeIntent = true; - }.bind(this)); - - Sortable.utils.on(editFavouritesList, "dragover", function() { - this.removeIntent = false; - }.bind(this)); - - $("#edit-favourites-list [data-toggle=popover]").popover(); - $("#favourites-modal").modal(); -}; - - -/** - * Handler for save favourites click events. - * Saves the selected favourites and reloads them. - */ -OperationsWaiter.prototype.saveFavouritesClick = function() { - const favs = document.querySelectorAll("#edit-favourites-list li"); - const favouritesList = Array.from(favs, e => e.textContent); - - this.app.saveFavourites(favouritesList); - this.app.loadFavourites(); - this.app.populateOperationsList(); - this.manager.recipe.initialiseOperationDragNDrop(); -}; - - -/** - * Handler for reset favourites click events. - * Resets favourites to their defaults. - */ -OperationsWaiter.prototype.resetFavouritesClick = function() { - this.app.resetFavourites(); -}; - - -/** - * Handler for opIcon mouseover events. - * Hides any popovers already showing on the operation so that there aren't two at once. - * - * @param {event} e - */ -OperationsWaiter.prototype.opIconMouseover = function(e) { - const opEl = e.target.parentNode; - if (e.target.getAttribute("data-toggle") === "popover") { - $(opEl).popover("hide"); - } -}; - - -/** - * Handler for opIcon mouseleave events. - * If this icon created a popover and we're moving back to the operation element, display the - * operation popover again. - * - * @param {event} e - */ -OperationsWaiter.prototype.opIconMouseleave = function(e) { - const opEl = e.target.parentNode; - const toEl = e.toElement || e.relatedElement; - - if (e.target.getAttribute("data-toggle") === "popover" && toEl === opEl) { - $(opEl).popover("show"); - } -}; - -export default OperationsWaiter; diff --git a/src/web/OperationsWaiter.mjs b/src/web/OperationsWaiter.mjs new file mode 100755 index 00000000..decc49d6 --- /dev/null +++ b/src/web/OperationsWaiter.mjs @@ -0,0 +1,288 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import HTMLOperation from "./HTMLOperation"; +import Sortable from "sortablejs"; + + +/** + * Waiter to handle events related to the operations. + */ +class OperationsWaiter { + + /** + * OperationsWaiter 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; + + this.options = {}; + this.removeIntent = false; + } + + + /** + * Handler for search events. + * Finds operations which match the given search term and displays them under the search box. + * + * @param {event} e + */ + searchOperations(e) { + let ops, selected; + + if (e.type === "search" || e.keyCode === 13) { // Search or Return + e.preventDefault(); + ops = document.querySelectorAll("#search-results li"); + if (ops.length) { + selected = this.getSelectedOp(ops); + if (selected > -1) { + this.manager.recipe.addOperation(ops[selected].innerHTML); + } + } + } + + if (e.keyCode === 40) { // Down + e.preventDefault(); + ops = document.querySelectorAll("#search-results li"); + if (ops.length) { + selected = this.getSelectedOp(ops); + if (selected > -1) { + ops[selected].classList.remove("selected-op"); + } + if (selected === ops.length-1) selected = -1; + ops[selected+1].classList.add("selected-op"); + } + } else if (e.keyCode === 38) { // Up + e.preventDefault(); + ops = document.querySelectorAll("#search-results li"); + if (ops.length) { + selected = this.getSelectedOp(ops); + if (selected > -1) { + ops[selected].classList.remove("selected-op"); + } + if (selected === 0) selected = ops.length; + ops[selected-1].classList.add("selected-op"); + } + } else { + const searchResultsEl = document.getElementById("search-results"); + const el = e.target; + const str = el.value; + + while (searchResultsEl.firstChild) { + try { + $(searchResultsEl.firstChild).popover("dispose"); + } catch (err) {} + searchResultsEl.removeChild(searchResultsEl.firstChild); + } + + $("#categories .show").collapse("hide"); + if (str) { + const matchedOps = this.filterOperations(str, true); + const matchedOpsHtml = matchedOps + .map(v => v.toStubHtml()) + .join(""); + + searchResultsEl.innerHTML = matchedOpsHtml; + searchResultsEl.dispatchEvent(this.manager.oplistcreate); + } + } + } + + + /** + * Filters operations based on the search string and returns the matching ones. + * + * @param {string} searchStr + * @param {boolean} highlight - Whether or not to highlight the matching string in the operation + * name and description + * @returns {string[]} + */ + filterOperations(inStr, highlight) { + const matchedOps = []; + const matchedDescs = []; + + const searchStr = inStr.toLowerCase(); + + for (const opName in this.app.operations) { + const op = this.app.operations[opName]; + const namePos = opName.toLowerCase().indexOf(searchStr); + const descPos = op.description.toLowerCase().indexOf(searchStr); + + if (namePos >= 0 || descPos >= 0) { + const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); + if (highlight) { + operation.highlightSearchString(searchStr, namePos, descPos); + } + + if (namePos < 0) { + matchedOps.push(operation); + } else { + matchedDescs.push(operation); + } + } + } + + return matchedDescs.concat(matchedOps); + } + + + /** + * Finds the operation which has been selected using keyboard shortcuts. This will have the class + * 'selected-op' set. Returns the index of the operation within the given list. + * + * @param {element[]} ops + * @returns {number} + */ + getSelectedOp(ops) { + for (let i = 0; i < ops.length; i++) { + if (ops[i].classList.contains("selected-op")) { + return i; + } + } + return -1; + } + + + /** + * Handler for oplistcreate events. + * + * @listens Manager#oplistcreate + * @param {event} e + */ + opListCreate(e) { + this.manager.recipe.createSortableSeedList(e.target); + this.enableOpsListPopovers(e.target); + } + + + /** + * Sets up popovers, allowing the popover itself to gain focus which enables scrolling + * and other interactions. + * + * @param {Element} el - The element to start selecting from + */ + enableOpsListPopovers(el) { + $(el).find("[data-toggle=popover]").addBack("[data-toggle=popover]") + .popover({trigger: "manual"}) + .on("mouseenter", function(e) { + if (e.buttons > 0) return; // Mouse button held down - likely dragging an opertion + const _this = this; + $(this).popover("show"); + $(".popover").on("mouseleave", function () { + $(_this).popover("hide"); + }); + }).on("mouseleave", function () { + const _this = this; + setTimeout(function() { + // Determine if the popover associated with this element is being hovered over + if ($(_this).data("bs.popover") && + ($(_this).data("bs.popover").tip && !$($(_this).data("bs.popover").tip).is(":hover"))) { + $(_this).popover("hide"); + } + }, 50); + }); + } + + + /** + * Handler for operation doubleclick events. + * Adds the operation to the recipe and auto bakes. + * + * @param {event} e + */ + operationDblclick(e) { + const li = e.target; + + this.manager.recipe.addOperation(li.textContent); + } + + + /** + * Handler for edit favourites click events. + * Sets up the 'Edit favourites' pane and displays it. + * + * @param {event} e + */ + editFavouritesClick(e) { + e.preventDefault(); + e.stopPropagation(); + + // Add favourites to modal + const favCat = this.app.categories.filter(function(c) { + return c.name === "Favourites"; + })[0]; + + let html = ""; + for (let i = 0; i < favCat.ops.length; i++) { + const opName = favCat.ops[i]; + const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); + html += operation.toStubHtml(true); + } + + const editFavouritesList = document.getElementById("edit-favourites-list"); + editFavouritesList.innerHTML = html; + this.removeIntent = false; + + const editableList = Sortable.create(editFavouritesList, { + filter: ".remove-icon", + onFilter: function (evt) { + const el = editableList.closest(evt.item); + if (el && el.parentNode) { + $(el).popover("dispose"); + el.parentNode.removeChild(el); + } + }, + onEnd: function(evt) { + if (this.removeIntent) { + $(evt.item).popover("dispose"); + evt.item.remove(); + } + }.bind(this), + }); + + Sortable.utils.on(editFavouritesList, "dragleave", function() { + this.removeIntent = true; + }.bind(this)); + + Sortable.utils.on(editFavouritesList, "dragover", function() { + this.removeIntent = false; + }.bind(this)); + + $("#edit-favourites-list [data-toggle=popover]").popover(); + $("#favourites-modal").modal(); + } + + + /** + * Handler for save favourites click events. + * Saves the selected favourites and reloads them. + */ + saveFavouritesClick() { + const favs = document.querySelectorAll("#edit-favourites-list li"); + const favouritesList = Array.from(favs, e => e.childNodes[0].textContent); + + this.app.saveFavourites(favouritesList); + this.app.loadFavourites(); + this.app.populateOperationsList(); + this.manager.recipe.initialiseOperationDragNDrop(); + } + + + /** + * Handler for reset favourites click events. + * Resets favourites to their defaults. + */ + resetFavouritesClick() { + this.app.resetFavourites(); + } + +} + +export default OperationsWaiter; diff --git a/src/web/OptionsWaiter.js b/src/web/OptionsWaiter.mjs similarity index 74% rename from src/web/OptionsWaiter.js rename to src/web/OptionsWaiter.mjs index 02e44c0c..3f08b91b 100755 --- a/src/web/OptionsWaiter.js +++ b/src/web/OptionsWaiter.mjs @@ -8,8 +8,9 @@ * @constructor * @param {App} app - The main view object for CyberChef. */ -const OptionsWaiter = function(app) { +const OptionsWaiter = function(app, manager) { this.app = app; + this.manager = manager; }; @@ -19,11 +20,6 @@ const OptionsWaiter = function(app) { * @param {Object} options */ OptionsWaiter.prototype.load = function(options) { - $(".option-item input:checkbox").bootstrapSwitch({ - size: "small", - animate: false, - }); - for (const option in options) { this.app.options[option] = options[option]; } @@ -32,7 +28,7 @@ OptionsWaiter.prototype.load = function(options) { const cboxes = document.querySelectorAll("#options-body input[type=checkbox]"); let i; for (i = 0; i < cboxes.length; i++) { - $(cboxes[i]).bootstrapSwitch("state", this.app.options[cboxes[i].getAttribute("option")]); + cboxes[i].checked = this.app.options[cboxes[i].getAttribute("option")]; } const nboxes = document.querySelectorAll("#options-body input[type=number]"); @@ -47,6 +43,8 @@ OptionsWaiter.prototype.load = function(options) { if (val) { selects[i].value = val; selects[i].dispatchEvent(new CustomEvent("change", {bubbles: true})); + } else { + selects[i].selectedIndex = 0; } } }; @@ -55,8 +53,11 @@ OptionsWaiter.prototype.load = function(options) { /** * Handler for options click events. * Dispays the options pane. + * + * @param {event} e */ -OptionsWaiter.prototype.optionsClick = function() { +OptionsWaiter.prototype.optionsClick = function(e) { + e.preventDefault(); $("#options-modal").modal(); }; @@ -75,14 +76,17 @@ OptionsWaiter.prototype.resetOptionsClick = function() { * Modifies the option state and saves it to local storage. * * @param {event} e - * @param {boolean} state */ -OptionsWaiter.prototype.switchChange = function(e, state) { +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; - localStorage.setItem("options", JSON.stringify(this.app.options)); + + if (this.app.isLocalStorageAvailable()) + localStorage.setItem("options", JSON.stringify(this.app.options)); }; @@ -95,9 +99,13 @@ OptionsWaiter.prototype.switchChange = function(e, state) { OptionsWaiter.prototype.numberChange = function(e) { const el = e.target; const option = el.getAttribute("option"); + const val = parseInt(el.value, 10); - this.app.options[option] = parseInt(el.value, 10); - localStorage.setItem("options", JSON.stringify(this.app.options)); + log.debug(`Setting ${option} to ${val}`); + this.app.options[option] = val; + + if (this.app.isLocalStorageAvailable()) + localStorage.setItem("options", JSON.stringify(this.app.options)); }; @@ -111,8 +119,11 @@ 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; - localStorage.setItem("options", JSON.stringify(this.app.options)); + + if (this.app.isLocalStorageAvailable()) + localStorage.setItem("options", JSON.stringify(this.app.options)); }; @@ -138,6 +149,8 @@ OptionsWaiter.prototype.setWordWrap = function() { /** * Changes the theme by setting the class of the element. + * + * @param {Event} e */ OptionsWaiter.prototype.themeChange = function (e) { const themeClass = e.target.value; @@ -145,4 +158,16 @@ OptionsWaiter.prototype.themeChange = function (e) { document.querySelector(":root").className = themeClass; }; + +/** + * 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(); +}; + export default OptionsWaiter; diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js deleted file mode 100755 index 98292784..00000000 --- a/src/web/OutputWaiter.js +++ /dev/null @@ -1,193 +0,0 @@ -import Utils from "../core/Utils.js"; - - -/** - * Waiter to handle events related to the output. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const OutputWaiter = function(app, manager) { - this.app = app; - this.manager = manager; -}; - - -/** - * Gets the output string from the output textarea. - * - * @returns {string} - */ -OutputWaiter.prototype.get = function() { - return document.getElementById("output-text").value; -}; - - -/** - * Sets the output in the output textarea. - * - * @param {string} dataStr - The output string/HTML - * @param {string} type - The data type of the output - * @param {number} duration - The length of time (ms) it took to generate the output - */ -OutputWaiter.prototype.set = function(dataStr, type, duration) { - const outputText = document.getElementById("output-text"); - const outputHtml = document.getElementById("output-html"); - const outputHighlighter = document.getElementById("output-highlighter"); - const inputHighlighter = document.getElementById("input-highlighter"); - - if (type === "html") { - outputText.style.display = "none"; - outputHtml.style.display = "block"; - outputHighlighter.display = "none"; - inputHighlighter.display = "none"; - - outputText.value = ""; - outputHtml.innerHTML = dataStr; - - // Execute script sections - const scriptElements = outputHtml.querySelectorAll("script"); - for (let i = 0; i < scriptElements.length; i++) { - try { - eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval - } catch (err) { - console.error(err); - } - } - } else { - outputText.style.display = "block"; - outputHtml.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; - - outputText.value = Utils.printable(dataStr, true); - outputHtml.innerHTML = ""; - } - - this.manager.highlighter.removeHighlights(); - const lines = dataStr.count("\n") + 1; - this.setOutputInfo(dataStr.length, lines, duration); -}; - - -/** - * Displays information about the output. - * - * @param {number} length - The length of the current output string - * @param {number} lines - The number of the lines in the current output string - * @param {number} duration - The length of time (ms) it took to generate the output - */ -OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) { - let width = length.toString().length; - width = width < 4 ? 4 : width; - - const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " "); - const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " "); - const timeStr = Utils.pad(duration.toString() + "ms", width, " ").replace(/ /g, " "); - - document.getElementById("output-info").innerHTML = "time: " + timeStr + - "
    length: " + lengthStr + - "
    lines: " + linesStr; - document.getElementById("input-selection-info").innerHTML = ""; - document.getElementById("output-selection-info").innerHTML = ""; -}; - - -/** - * Adjusts the display properties of the output buttons so that they fit within the current width - * without wrapping or overflowing. - */ -OutputWaiter.prototype.adjustWidth = function() { - const output = document.getElementById("output"); - const saveToFile = document.getElementById("save-to-file"); - const switchIO = document.getElementById("switch"); - const undoSwitch = document.getElementById("undo-switch"); - const maximiseOutput = document.getElementById("maximise-output"); - - if (output.clientWidth < 680) { - saveToFile.childNodes[1].nodeValue = ""; - switchIO.childNodes[1].nodeValue = ""; - undoSwitch.childNodes[1].nodeValue = ""; - maximiseOutput.childNodes[1].nodeValue = ""; - } else { - saveToFile.childNodes[1].nodeValue = " Save to file"; - switchIO.childNodes[1].nodeValue = " Move output to input"; - undoSwitch.childNodes[1].nodeValue = " Undo"; - maximiseOutput.childNodes[1].nodeValue = - maximiseOutput.getAttribute("title") === "Maximise" ? " Max" : " Restore"; - } -}; - - -/** - * Handler for save click events. - * Saves the current output to a file, downloaded as a URL octet stream. - */ -OutputWaiter.prototype.saveClick = function() { - const data = Utils.toBase64(this.app.dishStr); - const filename = window.prompt("Please enter a filename:", "download.dat"); - - if (filename) { - const el = document.createElement("a"); - el.setAttribute("href", "data:application/octet-stream;base64;charset=utf-8," + data); - el.setAttribute("download", filename); - - // Firefox requires that the element be added to the DOM before it can be clicked - el.style.display = "none"; - document.body.appendChild(el); - - el.click(); - el.remove(); - } -}; - - -/** - * Handler for switch click events. - * Moves the current output into the input textarea. - */ -OutputWaiter.prototype.switchClick = function() { - this.switchOrigData = this.manager.input.get(); - document.getElementById("undo-switch").disabled = false; - this.app.setInput(this.app.dishStr); -}; - - -/** - * Handler for undo switch click events. - * Removes the output from the input and replaces the input that was removed. - */ -OutputWaiter.prototype.undoSwitchClick = function() { - this.app.setInput(this.switchOrigData); - document.getElementById("undo-switch").disabled = true; -}; - - -/** - * Handler for maximise output click events. - * Resizes the output frame to be as large as possible, or restores it to its original size. - */ -OutputWaiter.prototype.maximiseOutputClick = function(e) { - const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode; - - if (el.getAttribute("title") === "Maximise") { - this.app.columnSplitter.collapse(0); - this.app.columnSplitter.collapse(1); - this.app.ioSplitter.collapse(0); - - el.setAttribute("title", "Restore"); - el.innerHTML = " Restore"; - this.adjustWidth(); - } else { - el.setAttribute("title", "Maximise"); - el.innerHTML = " Max"; - this.app.resetLayout(); - } -}; - -export default OutputWaiter; diff --git a/src/web/OutputWaiter.mjs b/src/web/OutputWaiter.mjs new file mode 100755 index 00000000..2d93507c --- /dev/null +++ b/src/web/OutputWaiter.mjs @@ -0,0 +1,499 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "../core/Utils"; +import FileSaver from "file-saver"; + + +/** + * Waiter to handle events related to the output. + */ +class OutputWaiter { + + /** + * OutputWaiter 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; + + this.dishBuffer = null; + this.dishStr = null; + } + + + /** + * Gets the output string from the output textarea. + * + * @returns {string} + */ + get() { + return document.getElementById("output-text").value; + } + + + /** + * Sets the output in the output textarea. + * + * @param {string|ArrayBuffer} data - The output string/HTML/ArrayBuffer + * @param {string} type - The data type of the output + * @param {number} duration - The length of time (ms) it took to generate the output + * @param {boolean} [preserveBuffer=false] - Whether to preserve the dishBuffer + */ + async set(data, type, duration, preserveBuffer) { + log.debug("Output type: " + type); + const outputText = document.getElementById("output-text"); + const outputHtml = document.getElementById("output-html"); + const outputFile = document.getElementById("output-file"); + const outputHighlighter = document.getElementById("output-highlighter"); + const inputHighlighter = document.getElementById("input-highlighter"); + let scriptElements, lines, length; + + if (!preserveBuffer) { + this.closeFile(); + this.dishStr = null; + document.getElementById("show-file-overlay").style.display = "none"; + } + + switch (type) { + case "html": + outputText.style.display = "none"; + outputHtml.style.display = "block"; + outputFile.style.display = "none"; + outputHighlighter.display = "none"; + inputHighlighter.display = "none"; + + outputText.value = ""; + outputHtml.innerHTML = data; + + // Execute script sections + scriptElements = outputHtml.querySelectorAll("script"); + for (let i = 0; i < scriptElements.length; i++) { + try { + eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval + } catch (err) { + log.error(err); + } + } + + await this.getDishStr(); + length = this.dishStr.length; + lines = this.dishStr.count("\n") + 1; + break; + case "ArrayBuffer": + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputHighlighter.display = "none"; + inputHighlighter.display = "none"; + + outputText.value = ""; + outputHtml.innerHTML = ""; + length = data.byteLength; + + this.setFile(data); + break; + case "string": + default: + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputFile.style.display = "none"; + outputHighlighter.display = "block"; + inputHighlighter.display = "block"; + + outputText.value = Utils.printable(data, true); + outputHtml.innerHTML = ""; + + lines = data.count("\n") + 1; + length = data.length; + this.dishStr = data; + break; + } + + this.manager.highlighter.removeHighlights(); + this.setOutputInfo(length, lines, duration); + this.backgroundMagic(); + } + + + /** + * Shows file details. + * + * @param {ArrayBuffer} buf + */ + setFile(buf) { + this.dishBuffer = buf; + const file = new File([buf], "output.dat"); + + // Display file overlay in output area with details + const fileOverlay = document.getElementById("output-file"), + fileSize = document.getElementById("output-file-size"); + + fileOverlay.style.display = "block"; + fileSize.textContent = file.size.toLocaleString() + " bytes"; + + // Display preview slice in the background + const outputText = document.getElementById("output-text"), + fileSlice = this.dishBuffer.slice(0, 4096); + + outputText.classList.add("blur"); + outputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice)); + } + + + /** + * Removes the output file and nulls its memory. + */ + closeFile() { + this.dishBuffer = null; + document.getElementById("output-file").style.display = "none"; + document.getElementById("output-text").classList.remove("blur"); + } + + + /** + * Handler for file download events. + */ + async downloadFile() { + this.filename = window.prompt("Please enter a filename:", this.filename || "download.dat"); + await this.getDishBuffer(); + const file = new File([this.dishBuffer], this.filename); + if (this.filename) FileSaver.saveAs(file, this.filename, false); + } + + + /** + * Handler for file slice display events. + */ + displayFileSlice() { + const startTime = new Date().getTime(), + showFileOverlay = document.getElementById("show-file-overlay"), + sliceFromEl = document.getElementById("output-file-slice-from"), + sliceToEl = document.getElementById("output-file-slice-to"), + sliceFrom = parseInt(sliceFromEl.value, 10), + sliceTo = parseInt(sliceToEl.value, 10), + str = Utils.arrayBufferToStr(this.dishBuffer.slice(sliceFrom, sliceTo)); + + document.getElementById("output-text").classList.remove("blur"); + showFileOverlay.style.display = "block"; + this.set(str, "string", new Date().getTime() - startTime, true); + } + + + /** + * Handler for show file overlay events. + * + * @param {Event} e + */ + showFileOverlayClick(e) { + const outputFile = document.getElementById("output-file"), + showFileOverlay = e.target; + + document.getElementById("output-text").classList.add("blur"); + outputFile.style.display = "block"; + showFileOverlay.style.display = "none"; + this.setOutputInfo(this.dishBuffer.byteLength, null, 0); + } + + + /** + * Displays information about the output. + * + * @param {number} length - The length of the current output string + * @param {number} lines - The number of the lines in the current output string + * @param {number} duration - The length of time (ms) it took to generate the output + */ + setOutputInfo(length, lines, duration) { + let width = length.toString().length; + width = width < 4 ? 4 : width; + + const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); + const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, " "); + + let msg = "time: " + timeStr + "
    length: " + lengthStr; + + if (typeof lines === "number") { + const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); + msg += "
    lines: " + linesStr; + } + + document.getElementById("output-info").innerHTML = msg; + document.getElementById("input-selection-info").innerHTML = ""; + document.getElementById("output-selection-info").innerHTML = ""; + } + + + /** + * Handler for save click events. + * Saves the current output to a file. + */ + saveClick() { + this.downloadFile(); + } + + + /** + * Handler for copy click events. + * Copies the output to the clipboard. + */ + async copyClick() { + await this.getDishStr(); + + // 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"); + textarea.style.position = "fixed"; + textarea.style.top = 0; + textarea.style.left = 0; + textarea.style.width = 0; + textarea.style.height = 0; + textarea.style.border = "none"; + + textarea.value = this.dishStr; + document.body.appendChild(textarea); + + // Select and copy the contents of this textarea + let success = false; + try { + textarea.select(); + success = this.dishStr && document.execCommand("copy"); + } catch (err) { + success = false; + } + + if (success) { + this.app.alert("Copied raw output successfully.", 2000); + } else { + this.app.alert("Sorry, the output could not be copied.", 3000); + } + + // Clean up + document.body.removeChild(textarea); + } + + + /** + * Handler for switch click events. + * Moves the current output into the input textarea. + */ + async switchClick() { + this.switchOrigData = this.manager.input.get(); + document.getElementById("undo-switch").disabled = false; + if (this.dishBuffer) { + this.manager.input.setFile(new File([this.dishBuffer], "output.dat")); + this.manager.input.handleLoaderMessage({ + data: { + progress: 100, + fileBuffer: this.dishBuffer + } + }); + } else { + await this.getDishStr(); + this.app.setInput(this.dishStr); + } + } + + + /** + * Handler for undo switch click events. + * Removes the output from the input and replaces the input that was removed. + */ + undoSwitchClick() { + this.app.setInput(this.switchOrigData); + const undoSwitch = document.getElementById("undo-switch"); + undoSwitch.disabled = true; + $(undoSwitch).tooltip("hide"); + } + + + /** + * Handler for maximise output click events. + * Resizes the output frame to be as large as possible, or restores it to its original size. + */ + maximiseOutputClick(e) { + const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode; + + if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) { + this.app.initialiseSplitter(true); + this.app.columnSplitter.collapse(0); + this.app.columnSplitter.collapse(1); + this.app.ioSplitter.collapse(0); + + $(el).attr("data-original-title", "Restore output pane"); + el.querySelector("i").innerHTML = "fullscreen_exit"; + } else { + $(el).attr("data-original-title", "Maximise output pane"); + el.querySelector("i").innerHTML = "fullscreen"; + this.app.initialiseSplitter(false); + this.app.resetLayout(); + } + } + + + /** + * Shows or hides the loading icon. + * + * @param {boolean} value + */ + toggleLoader(value) { + const outputLoader = document.getElementById("output-loader"), + outputElement = document.getElementById("output-text"); + + if (value) { + this.manager.controls.hideStaleIndicator(); + this.bakingStatusTimeout = setTimeout(function() { + outputElement.disabled = true; + outputLoader.style.visibility = "visible"; + outputLoader.style.opacity = 1; + this.manager.controls.toggleBakeButtonFunction(true); + }.bind(this), 200); + } else { + clearTimeout(this.bakingStatusTimeout); + outputElement.disabled = false; + outputLoader.style.opacity = 0; + outputLoader.style.visibility = "hidden"; + this.manager.controls.toggleBakeButtonFunction(false); + this.setStatusMsg(""); + } + } + + + /** + * Sets the baking status message value. + * + * @param {string} msg + */ + setStatusMsg(msg) { + const el = document.querySelector("#output-loader .loading-msg"); + + el.textContent = msg; + } + + + /** + * Returns true if the output contains carriage returns + * + * @returns {boolean} + */ + async containsCR() { + await this.getDishStr(); + return this.dishStr.indexOf("\r") >= 0; + } + + + /** + * Retrieves the current dish as a string, returning the cached version if possible. + * + * @returns {string} + */ + async getDishStr() { + if (this.dishStr) return this.dishStr; + + this.dishStr = await new Promise(resolve => { + this.manager.worker.getDishAs(this.app.dish, "string", r => { + resolve(r.value); + }); + }); + return this.dishStr; + } + + + /** + * Retrieves the current dish as an ArrayBuffer, returning the cached version if possible. + * + * @returns {ArrayBuffer} + */ + async getDishBuffer() { + if (this.dishBuffer) return this.dishBuffer; + + this.dishBuffer = await new Promise(resolve => { + this.manager.worker.getDishAs(this.app.dish, "ArrayBuffer", r => { + resolve(r.value); + }); + }); + return this.dishBuffer; + } + + + /** + * Triggers the BackgroundWorker to attempt Magic on the current output. + */ + backgroundMagic() { + this.hideMagicButton(); + if (!this.app.options.autoMagic) return; + + const sample = this.dishStr ? this.dishStr.slice(0, 1000) : + this.dishBuffer ? this.dishBuffer.slice(0, 1000) : ""; + + if (sample.length) { + this.manager.background.magic(sample); + } + } + + + /** + * Handles the results of a background Magic call. + * + * @param {Object[]} options + */ + backgroundMagicResult(options) { + if (!options.length || + !options[0].recipe.length) + return; + + const currentRecipeConfig = this.app.getRecipeConfig(); + const newRecipeConfig = currentRecipeConfig.concat(options[0].recipe); + const opSequence = options[0].recipe.map(o => o.op).join(", "); + + this.showMagicButton(opSequence, options[0].data, newRecipeConfig); + } + + + /** + * Handler for Magic click events. + * + * Loads the Magic recipe. + * + * @fires Manager#statechange + */ + magicClick() { + const magicButton = document.getElementById("magic"); + this.app.setRecipeConfig(JSON.parse(magicButton.getAttribute("data-recipe"))); + window.dispatchEvent(this.manager.statechange); + this.hideMagicButton(); + } + + + /** + * Displays the Magic button with a title and adds a link to a complete recipe. + * + * @param {string} opSequence + * @param {string} result + * @param {Object[]} recipeConfig + */ + showMagicButton(opSequence, result, recipeConfig) { + const magicButton = document.getElementById("magic"); + magicButton.setAttribute("data-original-title", `${opSequence} will produce "${Utils.escapeHtml(Utils.truncate(result), 30)}"`); + magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, ""); + magicButton.classList.remove("hidden"); + } + + + /** + * Hides the Magic button and resets its values. + */ + hideMagicButton() { + const magicButton = document.getElementById("magic"); + magicButton.classList.add("hidden"); + magicButton.setAttribute("data-recipe", ""); + magicButton.setAttribute("data-original-title", "Magic!"); + } + +} + +export default OutputWaiter; diff --git a/src/web/RecipeWaiter.js b/src/web/RecipeWaiter.js deleted file mode 100755 index b9cf23c5..00000000 --- a/src/web/RecipeWaiter.js +++ /dev/null @@ -1,437 +0,0 @@ -import HTMLOperation from "./HTMLOperation.js"; -import Sortable from "sortablejs"; - - -/** - * Waiter to handle events related to the recipe. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const RecipeWaiter = function(app, manager) { - this.app = app; - this.manager = manager; - this.removeIntent = false; -}; - - -/** - * Sets up the drag and drop capability for operations in the operations and recipe areas. - */ -RecipeWaiter.prototype.initialiseOperationDragNDrop = function() { - const recList = document.getElementById("rec-list"); - - // Recipe list - Sortable.create(recList, { - group: "recipe", - sort: true, - animation: 0, - delay: 0, - filter: ".arg-input,.arg", - preventOnFilter: false, - setData: function(dataTransfer, dragEl) { - dataTransfer.setData("Text", dragEl.querySelector(".arg-title").textContent); - }, - onEnd: function(evt) { - if (this.removeIntent) { - evt.item.remove(); - evt.target.dispatchEvent(this.manager.operationremove); - } - }.bind(this), - onSort: function(evt) { - if (evt.from.id === "rec-list") { - document.dispatchEvent(this.manager.statechange); - } - }.bind(this) - }); - - Sortable.utils.on(recList, "dragover", function() { - this.removeIntent = false; - }.bind(this)); - - Sortable.utils.on(recList, "dragleave", function() { - this.removeIntent = true; - this.app.progress = 0; - }.bind(this)); - - Sortable.utils.on(recList, "touchend", function(e) { - const loc = e.changedTouches[0]; - const target = document.elementFromPoint(loc.clientX, loc.clientY); - - this.removeIntent = !recList.contains(target); - }.bind(this)); - - // Favourites category - document.querySelector("#categories a").addEventListener("dragover", this.favDragover.bind(this)); - document.querySelector("#categories a").addEventListener("dragleave", this.favDragleave.bind(this)); - document.querySelector("#categories a").addEventListener("drop", this.favDrop.bind(this)); -}; - - -/** - * Creates a drag-n-droppable seed list of operations. - * - * @param {element} listEl - The list to initialise - */ -RecipeWaiter.prototype.createSortableSeedList = function(listEl) { - Sortable.create(listEl, { - group: { - name: "recipe", - pull: "clone", - put: false, - }, - sort: false, - setData: function(dataTransfer, dragEl) { - dataTransfer.setData("Text", dragEl.textContent); - }, - onStart: function(evt) { - // Removes popover element and event bindings from the dragged operation but not the - // event bindings from the one left in the operations list. Without manually removing - // these bindings, we cannot re-initialise the popover on the stub operation. - $(evt.item).popover("destroy"); - $(evt.clone).off(".popover").removeData("bs.popover"); - evt.item.setAttribute("data-toggle", "popover-disabled"); - }, - onEnd: this.opSortEnd.bind(this) - }); -}; - - -/** - * Handler for operation sort end events. - * Removes the operation from the list if it has been dropped outside. If not, adds it to the list - * at the appropriate place and initialises it. - * - * @fires Manager#operationadd - * @param {event} evt - */ -RecipeWaiter.prototype.opSortEnd = function(evt) { - if (this.removeIntent) { - if (evt.item.parentNode.id === "rec-list") { - evt.item.remove(); - } - return; - } - - // Reinitialise the popover on the original element in the ops list because for some reason it - // gets destroyed and recreated. - $(evt.clone).popover(); - $(evt.clone).children("[data-toggle=popover]").popover(); - - if (evt.item.parentNode.id !== "rec-list") { - return; - } - - this.buildRecipeOperation(evt.item); - evt.item.dispatchEvent(this.manager.operationadd); -}; - - -/** - * Handler for favourite dragover events. - * If the element being dragged is an operation, displays a visual cue so that the user knows it can - * be dropped here. - * - * @param {event} e - */ -RecipeWaiter.prototype.favDragover = function(e) { - if (e.dataTransfer.effectAllowed !== "move") - return false; - - e.stopPropagation(); - e.preventDefault(); - if (e.target.className && e.target.className.indexOf("category-title") > -1) { - // Hovering over the a - e.target.classList.add("favourites-hover"); - } else if (e.target.parentNode.className && e.target.parentNode.className.indexOf("category-title") > -1) { - // Hovering over the Edit button - e.target.parentNode.classList.add("favourites-hover"); - } else if (e.target.parentNode.parentNode.className && e.target.parentNode.parentNode.className.indexOf("category-title") > -1) { - // Hovering over the image on the Edit button - e.target.parentNode.parentNode.classList.add("favourites-hover"); - } -}; - - -/** - * Handler for favourite dragleave events. - * Removes the visual cue. - * - * @param {event} e - */ -RecipeWaiter.prototype.favDragleave = function(e) { - e.stopPropagation(); - e.preventDefault(); - document.querySelector("#categories a").classList.remove("favourites-hover"); -}; - - -/** - * Handler for favourite drop events. - * Adds the dragged operation to the favourites list. - * - * @param {event} e - */ -RecipeWaiter.prototype.favDrop = function(e) { - e.stopPropagation(); - e.preventDefault(); - e.target.classList.remove("favourites-hover"); - - const opName = e.dataTransfer.getData("Text"); - this.app.addFavourite(opName); -}; - - -/** - * Handler for ingredient change events. - * - * @fires Manager#statechange - */ -RecipeWaiter.prototype.ingChange = function() { - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Handler for disable click events. - * Updates the icon status. - * - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.disableClick = function(e) { - const icon = e.target; - - if (icon.getAttribute("disabled") === "false") { - icon.setAttribute("disabled", "true"); - icon.classList.add("disable-icon-selected"); - icon.parentNode.parentNode.classList.add("disabled"); - } else { - icon.setAttribute("disabled", "false"); - icon.classList.remove("disable-icon-selected"); - icon.parentNode.parentNode.classList.remove("disabled"); - } - - this.app.progress = 0; - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Handler for breakpoint click events. - * Updates the icon status. - * - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.breakpointClick = function(e) { - const bp = e.target; - - if (bp.getAttribute("break") === "false") { - bp.setAttribute("break", "true"); - bp.classList.add("breakpoint-selected"); - } else { - bp.setAttribute("break", "false"); - bp.classList.remove("breakpoint-selected"); - } - - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Handler for operation doubleclick events. - * Removes the operation from the recipe and auto bakes. - * - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.operationDblclick = function(e) { - e.target.remove(); - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Handler for operation child doubleclick events. - * Removes the operation from the recipe. - * - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.operationChildDblclick = function(e) { - e.target.parentNode.remove(); - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Generates a configuration object to represent the current recipe. - * - * @returns {recipeConfig} - */ -RecipeWaiter.prototype.getConfig = function() { - const config = []; - let ingredients, ingList, disabled, bp, item; - const operations = document.querySelectorAll("#rec-list li.operation"); - - for (let i = 0; i < operations.length; i++) { - ingredients = []; - disabled = operations[i].querySelector(".disable-icon"); - bp = operations[i].querySelector(".breakpoint"); - ingList = operations[i].querySelectorAll(".arg"); - - for (let j = 0; j < ingList.length; j++) { - if (ingList[j].getAttribute("type") === "checkbox") { - // checkbox - ingredients[j] = ingList[j].checked; - } else if (ingList[j].classList.contains("toggle-string")) { - // toggleString - ingredients[j] = { - option: ingList[j].previousSibling.children[0].textContent.slice(0, -1), - string: ingList[j].value - }; - } else { - // all others - ingredients[j] = ingList[j].value; - } - } - - item = { - op: operations[i].querySelector(".arg-title").textContent, - args: ingredients - }; - - if (disabled && disabled.getAttribute("disabled") === "true") { - item.disabled = true; - } - - if (bp && bp.getAttribute("break") === "true") { - item.breakpoint = true; - } - - config.push(item); - } - - return config; -}; - - -/** - * Moves or removes the breakpoint indicator in the recipe based on the position. - * - * @param {number} position - */ -RecipeWaiter.prototype.updateBreakpointIndicator = function(position) { - const operations = document.querySelectorAll("#rec-list li.operation"); - for (let i = 0; i < operations.length; i++) { - if (i === position) { - operations[i].classList.add("break"); - } else { - operations[i].classList.remove("break"); - } - } -}; - - -/** - * Given an operation stub element, this function converts it into a full recipe element with - * arguments. - * - * @param {element} el - The operation stub element from the operations pane - */ -RecipeWaiter.prototype.buildRecipeOperation = function(el) { - const opName = el.textContent; - const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); - el.innerHTML = op.toFullHtml(); - - if (this.app.operations[opName].flowControl) { - el.classList.add("flow-control-op"); - } - - // Disable auto-bake if this is a manual op - this should be moved to the 'operationadd' - // handler after event restructuring - if (op.manualBake && this.app.autoBake_) { - this.manager.controls.setAutoBake(false); - this.app.alert("Auto-Bake is disabled by default when using this operation.", "info", 5000); - } -}; - -/** - * Adds the specified operation to the recipe. - * - * @fires Manager#operationadd - * @param {string} name - The name of the operation to add - * @returns {element} - */ -RecipeWaiter.prototype.addOperation = function(name) { - const item = document.createElement("li"); - - item.classList.add("operation"); - item.innerHTML = name; - this.buildRecipeOperation(item); - document.getElementById("rec-list").appendChild(item); - - item.dispatchEvent(this.manager.operationadd); - return item; -}; - - -/** - * Removes all operations from the recipe. - * - * @fires Manager#operationremove - */ -RecipeWaiter.prototype.clearRecipe = function() { - const recList = document.getElementById("rec-list"); - while (recList.firstChild) { - recList.removeChild(recList.firstChild); - } - recList.dispatchEvent(this.manager.operationremove); -}; - - -/** - * Handler for operation dropdown events from toggleString arguments. - * Sets the selected option as the name of the button. - * - * @param {event} e - */ -RecipeWaiter.prototype.dropdownToggleClick = function(e) { - const el = e.target; - const button = el.parentNode.parentNode.previousSibling; - - button.innerHTML = el.textContent + " "; - this.ingChange(); -}; - - -/** - * Handler for operationadd events. - * - * @listens Manager#operationadd - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.opAdd = function(e) { - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Handler for operationremove events. - * - * @listens Manager#operationremove - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.opRemove = function(e) { - window.dispatchEvent(this.manager.statechange); -}; - -export default RecipeWaiter; diff --git a/src/web/RecipeWaiter.mjs b/src/web/RecipeWaiter.mjs new file mode 100755 index 00000000..98457bb3 --- /dev/null +++ b/src/web/RecipeWaiter.mjs @@ -0,0 +1,598 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import HTMLOperation from "./HTMLOperation"; +import Sortable from "sortablejs"; +import Utils from "../core/Utils"; + + +/** + * Waiter to handle events related to the recipe. + */ +class RecipeWaiter { + + /** + * RecipeWaiter 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; + this.removeIntent = false; + } + + + /** + * Sets up the drag and drop capability for operations in the operations and recipe areas. + */ + initialiseOperationDragNDrop() { + const recList = document.getElementById("rec-list"); + + // Recipe list + Sortable.create(recList, { + group: "recipe", + sort: true, + animation: 0, + delay: 0, + filter: ".arg", + preventOnFilter: false, + setData: function(dataTransfer, dragEl) { + dataTransfer.setData("Text", dragEl.querySelector(".op-title").textContent); + }, + onEnd: function(evt) { + if (this.removeIntent) { + evt.item.remove(); + evt.target.dispatchEvent(this.manager.operationremove); + } + }.bind(this), + onSort: function(evt) { + if (evt.from.id === "rec-list") { + document.dispatchEvent(this.manager.statechange); + } + }.bind(this) + }); + + Sortable.utils.on(recList, "dragover", function() { + this.removeIntent = false; + }.bind(this)); + + Sortable.utils.on(recList, "dragleave", function() { + this.removeIntent = true; + this.app.progress = 0; + }.bind(this)); + + Sortable.utils.on(recList, "touchend", function(e) { + const loc = e.changedTouches[0]; + const target = document.elementFromPoint(loc.clientX, loc.clientY); + + this.removeIntent = !recList.contains(target); + }.bind(this)); + + // Favourites category + document.querySelector("#categories a").addEventListener("dragover", this.favDragover.bind(this)); + document.querySelector("#categories a").addEventListener("dragleave", this.favDragleave.bind(this)); + document.querySelector("#categories a").addEventListener("drop", this.favDrop.bind(this)); + } + + + /** + * Creates a drag-n-droppable seed list of operations. + * + * @param {element} listEl - The list to initialise + */ + createSortableSeedList(listEl) { + Sortable.create(listEl, { + group: { + name: "recipe", + pull: "clone", + put: false, + }, + sort: false, + setData: function(dataTransfer, dragEl) { + dataTransfer.setData("Text", dragEl.textContent); + }, + onStart: function(evt) { + // Removes popover element and event bindings from the dragged operation but not the + // event bindings from the one left in the operations list. Without manually removing + // these bindings, we cannot re-initialise the popover on the stub operation. + $(evt.item) + .popover("dispose") + .removeData("bs.popover") + .off("mouseenter") + .off("mouseleave") + .attr("data-toggle", "popover-disabled"); + $(evt.clone) + .off(".popover") + .removeData("bs.popover"); + }, + onEnd: this.opSortEnd.bind(this) + }); + } + + + /** + * Handler for operation sort end events. + * Removes the operation from the list if it has been dropped outside. If not, adds it to the list + * at the appropriate place and initialises it. + * + * @fires Manager#operationadd + * @param {event} evt + */ + opSortEnd(evt) { + if (this.removeIntent) { + if (evt.item.parentNode.id === "rec-list") { + evt.item.remove(); + } + return; + } + + // Reinitialise the popover on the original element in the ops list because for some reason it + // gets destroyed and recreated. + this.manager.ops.enableOpsListPopovers(evt.clone); + + if (evt.item.parentNode.id !== "rec-list") { + return; + } + + this.buildRecipeOperation(evt.item); + evt.item.dispatchEvent(this.manager.operationadd); + } + + + /** + * Handler for favourite dragover events. + * If the element being dragged is an operation, displays a visual cue so that the user knows it can + * be dropped here. + * + * @param {event} e + */ + favDragover(e) { + if (e.dataTransfer.effectAllowed !== "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + if (e.target.className && e.target.className.indexOf("category-title") > -1) { + // Hovering over the a + e.target.classList.add("favourites-hover"); + } else if (e.target.parentNode.className && e.target.parentNode.className.indexOf("category-title") > -1) { + // Hovering over the Edit button + e.target.parentNode.classList.add("favourites-hover"); + } else if (e.target.parentNode.parentNode.className && e.target.parentNode.parentNode.className.indexOf("category-title") > -1) { + // Hovering over the image on the Edit button + e.target.parentNode.parentNode.classList.add("favourites-hover"); + } + } + + + /** + * Handler for favourite dragleave events. + * Removes the visual cue. + * + * @param {event} e + */ + favDragleave(e) { + e.stopPropagation(); + e.preventDefault(); + document.querySelector("#categories a").classList.remove("favourites-hover"); + } + + + /** + * Handler for favourite drop events. + * Adds the dragged operation to the favourites list. + * + * @param {event} e + */ + favDrop(e) { + e.stopPropagation(); + e.preventDefault(); + e.target.classList.remove("favourites-hover"); + + const opName = e.dataTransfer.getData("Text"); + this.app.addFavourite(opName); + } + + + /** + * Handler for ingredient change events. + * + * @fires Manager#statechange + */ + ingChange(e) { + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for disable click events. + * Updates the icon status. + * + * @fires Manager#statechange + * @param {event} e + */ + disableClick(e) { + const icon = e.target; + + if (icon.getAttribute("disabled") === "false") { + icon.setAttribute("disabled", "true"); + icon.classList.add("disable-icon-selected"); + icon.parentNode.parentNode.classList.add("disabled"); + } else { + icon.setAttribute("disabled", "false"); + icon.classList.remove("disable-icon-selected"); + icon.parentNode.parentNode.classList.remove("disabled"); + } + + this.app.progress = 0; + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for breakpoint click events. + * Updates the icon status. + * + * @fires Manager#statechange + * @param {event} e + */ + breakpointClick(e) { + const bp = e.target; + + if (bp.getAttribute("break") === "false") { + bp.setAttribute("break", "true"); + bp.classList.add("breakpoint-selected"); + } else { + bp.setAttribute("break", "false"); + bp.classList.remove("breakpoint-selected"); + } + + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for operation doubleclick events. + * Removes the operation from the recipe and auto bakes. + * + * @fires Manager#statechange + * @param {event} e + */ + operationDblclick(e) { + e.target.remove(); + this.opRemove(e); + } + + + /** + * Handler for operation child doubleclick events. + * Removes the operation from the recipe. + * + * @fires Manager#statechange + * @param {event} e + */ + operationChildDblclick(e) { + e.target.parentNode.remove(); + this.opRemove(e); + } + + + /** + * Generates a configuration object to represent the current recipe. + * + * @returns {recipeConfig} + */ + getConfig() { + const config = []; + let ingredients, ingList, disabled, bp, item; + const operations = document.querySelectorAll("#rec-list li.operation"); + + for (let i = 0; i < operations.length; i++) { + ingredients = []; + disabled = operations[i].querySelector(".disable-icon"); + bp = operations[i].querySelector(".breakpoint"); + ingList = operations[i].querySelectorAll(".arg"); + + for (let j = 0; j < ingList.length; j++) { + if (ingList[j].getAttribute("type") === "checkbox") { + // checkbox + ingredients[j] = ingList[j].checked; + } else if (ingList[j].classList.contains("toggle-string")) { + // toggleString + ingredients[j] = { + option: ingList[j].parentNode.parentNode.querySelector("button").textContent, + string: ingList[j].value + }; + } else if (ingList[j].getAttribute("type") === "number") { + // number + ingredients[j] = parseFloat(ingList[j].value, 10); + } else { + // all others + ingredients[j] = ingList[j].value; + } + } + + item = { + op: operations[i].querySelector(".op-title").textContent, + args: ingredients + }; + + if (disabled && disabled.getAttribute("disabled") === "true") { + item.disabled = true; + } + + if (bp && bp.getAttribute("break") === "true") { + item.breakpoint = true; + } + + config.push(item); + } + + return config; + } + + + /** + * Moves or removes the breakpoint indicator in the recipe based on the position. + * + * @param {number} position + */ + updateBreakpointIndicator(position) { + const operations = document.querySelectorAll("#rec-list li.operation"); + for (let i = 0; i < operations.length; i++) { + if (i === position) { + operations[i].classList.add("break"); + } else { + operations[i].classList.remove("break"); + } + } + } + + + /** + * Given an operation stub element, this function converts it into a full recipe element with + * arguments. + * + * @param {element} el - The operation stub element from the operations pane + */ + buildRecipeOperation(el) { + const opName = el.textContent; + const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); + el.innerHTML = op.toFullHtml(); + + if (this.app.operations[opName].flowControl) { + el.classList.add("flow-control-op"); + } + + // Disable auto-bake if this is a manual op + if (op.manualBake && this.app.autoBake_) { + this.manager.controls.setAutoBake(false); + this.app.alert("Auto-Bake is disabled by default when using this operation.", 5000); + } + } + + + /** + * Adds the specified operation to the recipe. + * + * @fires Manager#operationadd + * @param {string} name - The name of the operation to add + * @returns {element} + */ + addOperation(name) { + const item = document.createElement("li"); + + item.classList.add("operation"); + item.innerHTML = name; + this.buildRecipeOperation(item); + document.getElementById("rec-list").appendChild(item); + + item.dispatchEvent(this.manager.operationadd); + return item; + } + + + /** + * Removes all operations from the recipe. + * + * @fires Manager#operationremove + */ + clearRecipe() { + const recList = document.getElementById("rec-list"); + while (recList.firstChild) { + recList.removeChild(recList.firstChild); + } + recList.dispatchEvent(this.manager.operationremove); + } + + + /** + * Handler for operation dropdown events from toggleString arguments. + * Sets the selected option as the name of the button. + * + * @param {event} e + */ + dropdownToggleClick(e) { + e.stopPropagation(); + e.preventDefault(); + + const el = e.target; + const button = el.parentNode.parentNode.querySelector("button"); + + button.innerHTML = el.textContent; + this.ingChange(); + } + + + /** + * Handler for operationadd events. + * + * @listens Manager#operationadd + * @fires Manager#statechange + * @param {event} e + */ + opAdd(e) { + log.debug(`'${e.target.querySelector(".op-title").textContent}' added to recipe`); + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for operationremove events. + * + * @listens Manager#operationremove + * @fires Manager#statechange + * @param {event} e + */ + opRemove(e) { + log.debug("Operation removed from recipe"); + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for text argument dragover events. + * Gives the user a visual cue to show that items can be dropped here. + * + * @param {event} e + */ + textArgDragover (e) { + // This will be set if we're dragging an operation + if (e.dataTransfer.effectAllowed === "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + e.target.closest("textarea.arg").classList.add("dropping-file"); + } + + + /** + * Handler for text argument dragleave events. + * Removes the visual cue. + * + * @param {event} e + */ + textArgDragLeave (e) { + e.stopPropagation(); + e.preventDefault(); + e.target.classList.remove("dropping-file"); + } + + + /** + * Handler for text argument drop events. + * Loads the dragged data into the argument textarea. + * + * @param {event} e + */ + textArgDrop(e) { + // This will be set if we're dragging an operation + if (e.dataTransfer.effectAllowed === "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + const targ = e.target; + const file = e.dataTransfer.files[0]; + const text = e.dataTransfer.getData("Text"); + + targ.classList.remove("dropping-file"); + + if (text) { + targ.value = text; + return; + } + + if (file) { + const reader = new FileReader(); + const self = this; + reader.onload = function (e) { + targ.value = e.target.result; + // Trigger floating label move + const changeEvent = new Event("change"); + targ.dispatchEvent(changeEvent); + window.dispatchEvent(self.manager.statechange); + }; + reader.readAsText(file); + } + } + + + /** + * Sets register values. + * + * @param {number} opIndex + * @param {number} numPrevRegisters + * @param {string[]} registers + */ + setRegisters(opIndex, numPrevRegisters, registers) { + const op = document.querySelector(`#rec-list .operation:nth-child(${opIndex + 1})`), + prevRegList = op.querySelector(".register-list"); + + // Remove previous div + if (prevRegList) prevRegList.remove(); + + const registerList = []; + for (let i = 0; i < registers.length; i++) { + registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`); + } + const registerListEl = `
    + ${registerList.join("
    ")} +
    `; + + op.insertAdjacentHTML("beforeend", registerListEl); + } + + + /** + * Adjusts the number of ingredient columns as the width of the recipe changes. + */ + adjustWidth() { + const recList = document.getElementById("rec-list"); + + if (!this.ingredientRuleID) { + this.ingredientRuleID = null; + this.ingredientChildRuleID = null; + + // Find relevant rules in the stylesheet + // try/catch for chrome 64+ CORS error on cssRules. + try { + for (const i in document.styleSheets[0].cssRules) { + if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") { + this.ingredientRuleID = i; + } + if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") { + this.ingredientChildRuleID = i; + } + } + } catch (e) { + // Do nothing. + } + } + + if (!this.ingredientRuleID || !this.ingredientChildRuleID) return; + + const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID]; + const ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID]; + + if (recList.clientWidth < 450) { + ingredientRule.style.gridTemplateColumns = "auto auto"; + ingredientChildRule.style.gridColumn = "1 / span 2"; + } else if (recList.clientWidth < 620) { + ingredientRule.style.gridTemplateColumns = "auto auto auto"; + ingredientChildRule.style.gridColumn = "1 / span 3"; + } else { + ingredientRule.style.gridTemplateColumns = "auto auto auto auto"; + ingredientChildRule.style.gridColumn = "1 / span 4"; + } + } + +} + +export default RecipeWaiter; diff --git a/src/web/SeasonalWaiter.js b/src/web/SeasonalWaiter.js deleted file mode 100755 index fb671024..00000000 --- a/src/web/SeasonalWaiter.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Waiter to handle seasonal events and easter eggs. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const SeasonalWaiter = function(app, manager) { - this.app = app; - this.manager = manager; -}; - - -/** - * Loads all relevant items depending on the current date. - */ -SeasonalWaiter.prototype.load = function() { - // Konami code - this.kkeys = []; - window.addEventListener("keydown", this.konamiCodeListener.bind(this)); -}; - - -/** - * Listen for the Konami code sequence of keys. Turn the page upside down if they are all heard in - * sequence. - * #konamicode - */ -SeasonalWaiter.prototype.konamiCodeListener = function(e) { - this.kkeys.push(e.keyCode); - const konami = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; - for (let i = 0; i < this.kkeys.length; i++) { - if (this.kkeys[i] !== konami[i]) { - this.kkeys = []; - break; - } - if (i === konami.length - 1) { - $("body").children().toggleClass("konami"); - this.kkeys = []; - } - } -}; - -export default SeasonalWaiter; diff --git a/src/web/SeasonalWaiter.mjs b/src/web/SeasonalWaiter.mjs new file mode 100755 index 00000000..e6611a89 --- /dev/null +++ b/src/web/SeasonalWaiter.mjs @@ -0,0 +1,56 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Waiter to handle seasonal events and easter eggs. + */ +class SeasonalWaiter { + + /** + * SeasonalWaiter contructor. + * + * @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; + } + + + /** + * Loads all relevant items depending on the current date. + */ + load() { + // Konami code + this.kkeys = []; + window.addEventListener("keydown", this.konamiCodeListener.bind(this)); + } + + + /** + * Listen for the Konami code sequence of keys. Turn the page upside down if they are all heard in + * sequence. + * #konamicode + */ + konamiCodeListener(e) { + this.kkeys.push(e.keyCode); + const konami = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; + for (let i = 0; i < this.kkeys.length; i++) { + if (this.kkeys[i] !== konami[i]) { + this.kkeys = []; + break; + } + if (i === konami.length - 1) { + $("body").children().toggleClass("konami"); + this.kkeys = []; + } + } + } + +} + +export default SeasonalWaiter; diff --git a/src/web/WindowWaiter.js b/src/web/WindowWaiter.js deleted file mode 100755 index e0d3944c..00000000 --- a/src/web/WindowWaiter.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Waiter to handle events related to the window object. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - */ -const WindowWaiter = function(app) { - this.app = app; -}; - - -/** - * Handler for window resize events. - * Resets the layout of CyberChef's panes after 200ms (so that continuous resizing doesn't cause - * continuous resetting). - */ -WindowWaiter.prototype.windowResize = function() { - clearTimeout(this.resetLayoutTimeout); - this.resetLayoutTimeout = setTimeout(this.app.resetLayout.bind(this.app), 200); -}; - - -/** - * Handler for window blur events. - * Saves the current time so that we can calculate how long the window was unfocussed for when - * focus is returned. - */ -WindowWaiter.prototype.windowBlur = function() { - this.windowBlurTime = new Date().getTime(); -}; - - -/** - * Handler for window focus events. - * - * When a browser tab is unfocused and the browser has to run lots of dynamic content in other - * tabs, it swaps out the memory for that tab. - * If the CyberChef tab has been unfocused for more than a minute, we run a silent bake which will - * force the browser to load and cache all the relevant JavaScript code needed to do a real bake. - * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for - * a long time and the browser has swapped out all its memory. - */ -WindowWaiter.prototype.windowFocus = function() { - const unfocusedTime = new Date().getTime() - this.windowBlurTime; - if (unfocusedTime > 60000) { - this.app.silentBake(); - } -}; - -export default WindowWaiter; diff --git a/src/web/WindowWaiter.mjs b/src/web/WindowWaiter.mjs new file mode 100755 index 00000000..a8e124f5 --- /dev/null +++ b/src/web/WindowWaiter.mjs @@ -0,0 +1,62 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Waiter to handle events related to the window object. + */ +class WindowWaiter { + + /** + * WindowWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + */ + constructor(app) { + this.app = app; + } + + + /** + * Handler for window resize events. + * Resets the layout of CyberChef's panes after 200ms (so that continuous resizing doesn't cause + * continuous resetting). + */ + windowResize() { + clearTimeout(this.resetLayoutTimeout); + this.resetLayoutTimeout = setTimeout(this.app.resetLayout.bind(this.app), 200); + } + + + /** + * Handler for window blur events. + * Saves the current time so that we can calculate how long the window was unfocussed for when + * focus is returned. + */ + windowBlur() { + this.windowBlurTime = new Date().getTime(); + } + + + /** + * Handler for window focus events. + * + * When a browser tab is unfocused and the browser has to run lots of dynamic content in other + * tabs, it swaps out the memory for that tab. + * If the CyberChef tab has been unfocused for more than a minute, we run a silent bake which will + * force the browser to load and cache all the relevant JavaScript code needed to do a real bake. + * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for + * a long time and the browser has swapped out all its memory. + */ + windowFocus() { + const unfocusedTime = new Date().getTime() - this.windowBlurTime; + if (unfocusedTime > 60000) { + this.app.silentBake(); + } + } + +} + +export default WindowWaiter; diff --git a/src/web/WorkerWaiter.mjs b/src/web/WorkerWaiter.mjs new file mode 100755 index 00000000..7ef72263 --- /dev/null +++ b/src/web/WorkerWaiter.mjs @@ -0,0 +1,239 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker"; + +/** + * Waiter to handle conversations with the ChefWorker. + */ +class WorkerWaiter { + + /** + * WorkerWaiter 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; + + this.callbacks = {}; + this.callbackID = 0; + } + + + /** + * Sets up the ChefWorker and associated listeners. + */ + registerChefWorker() { + log.debug("Registering new ChefWorker"); + this.chefWorker = new ChefWorker(); + this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this)); + this.setLogLevel(); + + let docURL = document.location.href.split(/[#?]/)[0]; + const index = docURL.lastIndexOf("/"); + if (index > 0) { + docURL = docURL.substring(0, index); + } + this.chefWorker.postMessage({"action": "docURL", "data": docURL}); + } + + + /** + * Handler for messages sent back by the ChefWorker. + * + * @param {MessageEvent} e + */ + handleChefMessage(e) { + const r = e.data; + log.debug("Receiving '" + r.action + "' from ChefWorker"); + + switch (r.action) { + case "bakeComplete": + this.bakingComplete(r.data); + break; + case "bakeError": + this.app.handleError(r.data); + this.setBakingStatus(false); + break; + case "dishReturned": + this.callbacks[r.data.id](r.data); + break; + case "silentBakeComplete": + break; + case "workerLoaded": + this.app.workerLoaded = true; + log.debug("ChefWorker loaded"); + this.app.loaded(); + break; + case "statusMessage": + this.manager.output.setStatusMsg(r.data); + break; + case "optionUpdate": + log.debug(`Setting ${r.data.option} to ${r.data.value}`); + this.app.options[r.data.option] = r.data.value; + break; + case "setRegisters": + this.manager.recipe.setRegisters(r.data.opIndex, r.data.numPrevRegisters, r.data.registers); + break; + case "highlightsCalculated": + this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction); + break; + default: + log.error("Unrecognised message from ChefWorker", e); + break; + } + } + + + /** + * Updates the UI to show if baking is in process or not. + * + * @param {bakingStatus} + */ + setBakingStatus(bakingStatus) { + this.app.baking = bakingStatus; + + this.manager.output.toggleLoader(bakingStatus); + } + + + /** + * Cancels the current bake by terminating the ChefWorker and creating a new one. + */ + cancelBake() { + this.chefWorker.terminate(); + this.registerChefWorker(); + this.setBakingStatus(false); + this.manager.controls.showStaleIndicator(); + } + + + /** + * Handler for completed bakes. + * + * @param {Object} response + */ + bakingComplete(response) { + this.setBakingStatus(false); + + if (!response) return; + + if (response.error) { + this.app.handleError(response.error); + } + + this.app.progress = response.progress; + this.app.dish = response.dish; + this.manager.recipe.updateBreakpointIndicator(response.progress); + this.manager.output.set(response.result, response.type, response.duration); + log.debug("--- Bake complete ---"); + } + + + /** + * Asks the ChefWorker to bake the current input using the current recipe. + * + * @param {string} input + * @param {Object[]} recipeConfig + * @param {Object} options + * @param {number} progress + * @param {boolean} step + */ + bake(input, recipeConfig, options, progress, step) { + this.setBakingStatus(true); + + this.chefWorker.postMessage({ + action: "bake", + data: { + input: input, + recipeConfig: recipeConfig, + options: options, + progress: progress, + step: step + } + }); + } + + + /** + * Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant + * JavaScript code needed to do a real bake. + * + * @param {Object[]} [recipeConfig] + */ + silentBake(recipeConfig) { + this.chefWorker.postMessage({ + action: "silentBake", + data: { + recipeConfig: recipeConfig + } + }); + } + + + /** + * Asks the ChefWorker to calculate highlight offsets if possible. + * + * @param {Object[]} recipeConfig + * @param {string} direction + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + */ + highlight(recipeConfig, direction, pos) { + this.chefWorker.postMessage({ + action: "highlight", + data: { + recipeConfig: recipeConfig, + direction: direction, + pos: pos + } + }); + } + + + /** + * Asks the ChefWorker to return the dish as the specified type + * + * @param {Dish} dish + * @param {string} type + * @param {Function} callback + */ + getDishAs(dish, type, callback) { + const id = this.callbackID++; + this.callbacks[id] = callback; + this.chefWorker.postMessage({ + action: "getDishAs", + data: { + dish: dish, + type: type, + id: id + } + }); + } + + + /** + * Sets the console log level in the worker. + * + * @param {string} level + */ + setLogLevel(level) { + if (!this.chefWorker) return; + + this.chefWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + } + +} + + +export default WorkerWaiter; diff --git a/src/web/html/index.html b/src/web/html/index.html index 4c596be9..74eb0ed8 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -1,17 +1,17 @@
    -
    -
    +
    +
    +
    - Edit -
    - - -
    +
    -