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/.cspell.json b/.cspell.json new file mode 100644 index 00000000..66fca29b --- /dev/null +++ b/.cspell.json @@ -0,0 +1,17 @@ +{ + "version": "0.2", + "language": "en,en-gb", + "words": [], + "dictionaries": [ + "npm", + "softwareTerms", + "node", + "html", + "css", + "bash", + "en-gb", + "misc" + ], + "ignorePaths": ["package.json", "package-lock.json", "node_modules"] + } + \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..92ebd43c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,41 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node +{ + "name": "CyberChef", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bookworm", + + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + "ghcr.io/devcontainers/features/github-cli": "latest" + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [8080], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": { + "npm": "bash -c \"sudo chown node node_modules && npm install\"" + }, + + "containerEnv": { + "DISPLAY": ":99" + }, + + "mounts": [ + "source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume" + ], + + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "GitHub.vscode-github-actions" + ] + } + } + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..dd87e2d7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +node_modules +build 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/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6064121c..abb37d42 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,7 +6,7 @@ There are lots of opportunities to contribute to CyberChef. If you want ideas, t Before your contributions can be accepted, you must: - - Sign the [GCHQ Contributor Licence Agreement](https://github.com/gchq/Gaffer/wiki/GCHQ-OSS-Contributor-License-Agreement-V1.0) + - Sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef) - Push your changes to your fork. - Submit a pull request. @@ -22,15 +22,15 @@ Before your contributions can be accepted, you must: * Line endings: UNIX style (\n) -## Design Principals +## Design Principles 1. If at all possible, all operations and features should be client-side and not rely on connections to an external server. This increases the utility of CyberChef on closed networks and in virtual machines that are not connected to the Internet. Calls to external APIs may be accepted if there is no other option, but not for critical components. -2. Latency should be kept to a minimum to enhance the user experience. This means that all operation code should sit on the client, rather than being loaded dynamically from a server. -3. Use Vanilla JS if at all possible to reduce the number of libraries required and relied upon. Frameworks like jQuery, although included, should not be used unless absolutely necessary. -4. Minimise the use of large libraries, especially for niche operations that won't be used very often - these will be downloaded by everyone using the app, whether they use that operation or not (due to principal 2). +2. Latency should be kept to a minimum to enhance the user experience. This means that operation code should sit on the client and be executed there. However, as a trade-off between latency and bandwidth, operation code with large dependencies can be loaded in discrete modules in order to reduce the size of the initial download. The downloading of additional modules must remain entirely transparent so that the user is not inconvenienced. +3. Large libraries should be kept in separate modules so that they are not downloaded by everyone who uses the app, just those who specifically require the relevant operations. +4. Use Vanilla JS if at all possible to reduce the number of libraries required and relied upon. Frameworks like jQuery, although included, should not be used unless absolutely necessary. -With these principals in mind, any changes or additions to CyberChef should keep it: +With these principles in mind, any changes or additions to CyberChef should keep it: - Standalone - Efficient diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9e029351..e90ab51f 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,25 +1 @@ - - - - - - -### Summary - - - -### Example - - - - -### Possible solutions - - - -### Environment - - -* CyberChef compile time: -* User-Agent: -* [Link to reproduce]() + diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 00000000..32b7fbe0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: 'Bug report: ' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behaviour or a link to the recipe / input used to cause the bug: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behaviour** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (if relevant, please complete the following information):** + - OS: [e.g. Windows] + - Browser: [e.g. chrome 72, firefox 60] + - CyberChef version: [e.g. 9.7.14] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 00000000..a8e637eb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for the project +title: 'Feature request: ' +labels: feature +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. E.g. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/operation-request.md b/.github/ISSUE_TEMPLATE/operation-request.md new file mode 100644 index 00000000..d88e6703 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/operation-request.md @@ -0,0 +1,14 @@ +--- +name: Operation request +about: Suggest a new operation +title: 'Operation request: ' +labels: operation +assignees: '' + +--- + +## Summary + +### Example Input + +### Example Output diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..1350e976 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,40 @@ +name: "CodeQL Analysis" + +on: + workflow_dispatch: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + types: [synchronize, opened, reopened] + schedule: + - cron: '22 17 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml new file mode 100644 index 00000000..8a3aff54 --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,58 @@ +name: "Master Build, Test & Deploy" + +on: + workflow_dispatch: + push: + branches: + - master + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set node version + uses: actions/setup-node@v3 + with: + node-version: '18.x' + + - name: Install + run: | + export DETECT_CHROMEDRIVER_VERSION=true + npm install + npm run setheapsize + + - name: Lint + run: npx grunt lint + + - name: Unit Tests + run: | + npm test + npm run testnodeconsumer + + - name: Production Build + if: success() + run: npx grunt prod --msg="Version 10 is here! Read about the new features here" + + - name: Generate sitemap + run: npx grunt exec:sitemap + + - name: UI Tests + if: success() + run: | + sudo apt-get install xvfb + xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui + + - name: Prepare for GitHub Pages + if: success() + run: npx grunt copy:ghPages + + - name: Deploy to GitHub Pages + if: success() && github.ref == 'refs/heads/master' + uses: crazy-max/ghaction-github-pages@v3 + with: + target_branch: gh-pages + build_dir: ./build/prod + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml new file mode 100644 index 00000000..296e60b9 --- /dev/null +++ b/.github/workflows/pull_requests.yml @@ -0,0 +1,55 @@ +name: "Pull Requests" + +on: + workflow_dispatch: + pull_request: + types: [synchronize, opened, reopened] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set node version + uses: actions/setup-node@v3 + with: + node-version: '18.x' + + - name: Install + run: | + export DETECT_CHROMEDRIVER_VERSION=true + npm install + npm run setheapsize + + - name: Lint + run: npx grunt lint + + - name: Unit Tests + run: | + npm test + npm run testnodeconsumer + + - name: Production Build + if: success() + run: npx grunt prod + + - name: Production Image Build + if: success() + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + # Not being uploaded to any registry, use a simple name to allow Buildah to build correctly. + image: cyberchef + containerfiles: ./Dockerfile + platforms: linux/amd64 + oci: true + # Webpack seems to use a lot of open files, increase the max open file limit to accomodate. + extra-args: | + --ulimit nofile=10000 + + - name: UI Tests + if: success() + run: | + sudo apt-get install xvfb + xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml new file mode 100644 index 00000000..20968772 --- /dev/null +++ b/.github/workflows/releases.yml @@ -0,0 +1,97 @@ +name: "Releases" + +on: + workflow_dispatch: + push: + tags: + - 'v*' + +env: + REGISTRY: ghcr.io + REGISTRY_USER: ${{ github.actor }} + REGISTRY_PASSWORD: ${{ github.token }} + IMAGE_NAME: ${{ github.repository }} + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set node version + uses: actions/setup-node@v3 + with: + node-version: '18.x' + + - name: Install + run: | + export DETECT_CHROMEDRIVER_VERSION=true + npm ci + npm run setheapsize + + - name: Lint + run: npx grunt lint + + - name: Unit Tests + run: | + npm test + npm run testnodeconsumer + + - name: Production Build + run: npx grunt prod + + - name: UI Tests + run: | + sudo apt-get install xvfb + xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui + + - name: Image Metadata + id: image-metadata + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern={{major}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}} + + - name: Production Image Build + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + tags: ${{ steps.image-metadata.outputs.tags }} + labels: ${{ steps.image-metadata.outputs.labels }} + containerfiles: ./Dockerfile + platforms: linux/amd64,linux/arm64 + oci: true + # enable build layer caching between platforms + layers: true + # Webpack seems to use a lot of open files, increase the max open file limit to accomodate. + extra-args: | + --ulimit nofile=10000 + + - name: Publish to GHCR + uses: redhat-actions/push-to-registry@v2 + with: + image: ${{ steps.build-image.outputs.image }} + tags: ${{ steps.build-image.outputs.tags }} + registry: ${{ env.REGISTRY }} + username: ${{ env.REGISTRY_USER }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Upload Release Assets + id: upload-release-assets + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: build/prod/*.zip + tag: ${{ github.ref }} + overwrite: true + file_glob: true + body: "See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details." + + - name: Publish to NPM + uses: JS-DevTools/npm-publish@v1 + if: false + with: + token: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 570ea499..42923f5d 100755 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,15 @@ node_modules npm-debug.log +travis.log build -docs/* -!docs/*.conf.json -!docs/*.ico .vscode +.idea +.*.swp +src/core/config/modules/* +src/core/config/OperationConfig.json +src/core/operations/index.mjs +src/node/config/OperationConfig.json +src/node/index.mjs +**/*.DS_Store +tests/browser/output/* +.node-version diff --git a/.npmignore b/.npmignore new file mode 100755 index 00000000..05ab5f52 --- /dev/null +++ b/.npmignore @@ -0,0 +1,7 @@ +node_modules +npm-debug.log +travis.log +build/* +!build/node +.vscode +.github diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..3c032078 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3e11eeaa..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: node_js -node_js: - - node -install: npm install -before_script: - - npm install -g grunt -script: - - grunt lint - - grunt test - - grunt docs - - grunt node - - grunt prod -deploy: - - provider: pages - skip_cleanup: true - github_token: $GITHUB_TOKEN - local_dir: build/prod/ - target_branch: gh-pages - on: - branch: master - - provider: releases - skip_cleaup: true - api_key: - secure: Lbya9e1a6bUeJLKsjuqRaNF/BNr7iqSjV0/9rhWbTCc5d1cd+A6DGnDqvw47S2x/+8b08XR5Umas5dS6KU4lLAZQ5DFJgKEuOqUDqz7fOfIZyaRKe8HBTrUn3c1zaqpbrIDQHAOwwEMSx3W+SXmFuniIHRkBFALfK/luyHb4u6e1vq9AsX59A6ICOl5fpGD8kHDtdFZtgCrrZ5tkX5wweiRUbro+LBNlfBCaXeuUwnigU0chAGx8wJWeSvHrndGoPPFEXc0MT4QPlI+q6R3AiO/mwS2/nLhEIGt4jrWUtyFQD9gFLZXg7Z6GC5pKqyf2EQXzgC8Kggybmbg5/MiWj1ns0d1XMK26r50bHD/9K7Kzlh5vq8Sum4DYWtuT4AsWgL/PfUEvB1FbF4InRJoIlbT3pWj9zNFKTyBNcvUchPk1uBE1Ldn5yPPZoSgdKO0ZiGjn/Qx0ByaDMGJbM5KMg3ALewJmaCiCbnvAaVvAm2UY6GrVK15JiGVn08GhliSzsx6E48ZcFtNJBwHK8MAimbTRyHR6aStJbEYIgoNiAjVv5m0twee/XknDvhULG2tor9chwhnqKb4kvaYPqbVuHNPyDT8VkLsHcpgeD2MKxGTq81NBgPz3rP3T1yWMxsuvhxoH91tIT8mU1jo8/e3BJYogKyRZXxwIHaF7ScDQZ4c= - file: - - build/prod/cyberchef.htm - - build/node/CyberChef.js - on: - repo: gchq/CyberChef - tags: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..d3a8feb7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,781 @@ +# Changelog + +## Versioning + +CyberChef uses the [semver](https://semver.org/) system to manage versioning: `..`. + +- MAJOR version changes represent a significant change to the fundamental architecture of CyberChef and may (but don't always) make breaking changes that are not backwards compatible. +- MINOR version changes usually mean the addition of new operations or reasonably significant new features. +- PATCH versions are used for bug fixes and any other small tweaks that modify or improve existing capabilities. + +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). + + +## Details + +### [10.19.0] - 2024-06-21 +- Add support for ECDSA and DSA in 'Parse CSR' [@robinsandhu] | [#1828] +- Fix typos in SIGABA.mjs [@eltociear] | [#1834] + +### [10.18.0] - 2024-04-24 +- Added 'XXTEA Encrypt' and 'XXTEA Decrypt' operations [@n1474335] | [0a353ee] + +### [10.17.0] - 2024-04-13 +- Fix unit test 'expectOutput' implementation [@zb3] | [#1783] +- Add accessibility labels for icons [@e218736] | [#1743] +- Add focus styling for keyboard navigation [@e218736] | [#1739] +- Add support for operation option hiding [@TheZ3ro] | [#541] +- Improve efficiency of RAKE implementation [@sw5678] | [#1751] +- Require (a, 26) to be coprime in 'Affine Encode' [@EvieHarv] | [#1788] +- Added 'JWK to PEM' operation [@cplussharp] | [#1277] +- Added 'PEM to JWK' operation [@cplussharp] | [#1277] +- Added 'Public Key from Certificate' operation [@cplussharp] | [#1642] +- Added 'Public Key from Private Key' operation [@cplussharp] | [#1642] + +### [10.16.0] - 2024-04-12 +- Added 'JA4Server Fingerprint' operation [@n1474335] | [#1789] + +### [10.15.0] - 2024-04-02 +- Fix Ciphersaber2 key concatenation [@zb3] | [#1765] +- Fix DeriveEVPKey's array parsing [@zb3] | [#1767] +- Fix JWT operations [@a3957273] | [#1769] +- Added 'Parse Certificate Signing Request' operation [@jkataja] | [#1504] +- Added 'Extract Hash Values' operation [@MShwed] | [#512] +- Added 'DateTime Delta' operation [@tomgond] | [#1732] + +### [10.14.0] - 2024-03-31 +- Added 'To Float' and 'From Float' operations [@tcode2k16] | [#1762] +- Fix ChaCha raw export option [@joostrijneveld] | [#1606] +- Update x86 disassembler vendor library [@evanreichard] | [#1197] +- Allow variable Blowfish key sizes [@cbeuw] | [#933] +- Added 'XXTEA' operation [@devcydo] | [#1361] + +### [10.13.0] - 2024-03-30 +- Added 'FangURL' operation [@breakersall] [@arnydo] | [#1591] [#654] + +### [10.12.0] - 2024-03-29 +- Added 'Salsa20' and 'XSalsa20' operation [@joostrijneveld] | [#1750] + +### [10.11.0] - 2024-03-29 +- Add HEIC/HEIF file signatures [@simonw] | [#1757] +- Update xmldom to fix medium security vulnerability [@chriswhite199] | [#1752] +- Update JSONWebToken to fix medium security vulnerability [@chriswhite199] | [#1753] + +### [10.10.0] - 2024-03-27 +- Added 'JA4 Fingerprint' operation [@n1474335] | [#1759] + +### [10.9.0] - 2024-03-26 +- Line ending sequences and UTF-8 character encoding are now detected automatically [@n1474335] | [65ffd8d] + +### [10.8.0] - 2024-02-13 +- Add official Docker images [@AshCorr] | [#1699] + +### [10.7.0] - 2024-02-09 +- Added 'File Tree' operation [@sw5678] | [#1667] +- Added 'RISON' operation [@sg5506844] | [#1555] +- Added 'MurmurHash3' operation [@AliceGrey] | [#1694] + +### [10.6.0] - 2024-02-03 +- Updated 'Forensics Wiki' URLs to new domain [@a3957273] | [#1703] +- Added 'LZNT1 Decompress' operation [@0xThiebaut] | [#1675] +- Updated 'Regex Expression' UUID matcher [@cnotin] | [#1678] +- Removed duplicate 'hover' message within baking info [@KevinSJ] | [#1541] + +### [10.5.0] - 2023-07-14 +- Added GOST Encrypt, Decrypt, Sign, Verify, Key Wrap, and Key Unwrap operations [@n1474335] | [#592] + +### [10.4.0] - 2023-03-24 +- Added 'Generate De Bruijn Sequence' operation [@gchq77703] | [#493] + +### [10.3.0] - 2023-03-24 +- Added 'Argon2' and 'Argon2 compare' operations [@Xenonym] | [#661] + +### [10.2.0] - 2023-03-23 +- Added 'Derive HKDF key' operation [@mikecat] | [#1528] + +### [10.1.0] - 2023-03-23 +- Added 'Levenshtein Distance' operation [@mikecat] | [#1498] +- Added 'Swap case' operation [@mikecat] | [#1499] + +## [10.0.0] - 2023-03-22 +- [Full details explained here](https://github.com/gchq/CyberChef/wiki/Character-encoding,-EOL-separators,-and-editor-features) +- Status bars added to the Input and Output [@n1474335] | [#1405] +- Character encoding selection added to the Input and Output [@n1474335] | [#1405] +- End of line separator selection added to the Input and Output [@n1474335] | [#1405] +- Non-printable characters are rendered as control character pictures [@n1474335] | [#1405] +- Loaded files can now be edited in the Input [@n1474335] | [#1405] +- Various editor features added such as multiple selections and bracket matching [@n1474335] | [#1405] +- Contextual help added, activated by pressing F1 while hovering over features [@n1474335] | [#1405] +- Many, many UI tests added for I/O features and operations [@n1474335] | [#1405] + +
+ Click to expand v9 minor versions + +### [9.55.0] - 2022-12-09 +- Added 'AMF Encode' and 'AMF Decode' operations [@n1474335] | [760eff4] + +### [9.54.0] - 2022-11-25 +- Added 'Rabbit' operation [@mikecat] | [#1450] + +### [9.53.0] - 2022-11-25 +- Added 'AES Key Wrap' and 'AES Key Unwrap' operations [@mikecat] | [#1456] + +### [9.52.0] - 2022-11-25 +- Added 'ChaCha' operation [@joostrijneveld] | [#1466] + +### [9.51.0] - 2022-11-25 +- Added 'CMAC' operation [@mikecat] | [#1457] + +### [9.50.0] - 2022-11-25 +- Added 'Shuffle' operation [@mikecat] | [#1472] + +### [9.49.0] - 2022-11-11 +- Added 'LZ4 Compress' and 'LZ4 Decompress' operations [@n1474335] | [31a7f83] + +### [9.48.0] - 2022-10-14 +- Added 'LM Hash' and 'NT Hash' operations [@n1474335] [@brun0ne] | [#1427] + +### [9.47.0] - 2022-10-14 +- Added 'LZMA Decompress' and 'LZMA Compress' operations [@mattnotmitt] | [#1421] + +### [9.46.0] - 2022-07-08 +- Added 'Cetacean Cipher Encode' and 'Cetacean Cipher Decode' operations [@valdelaseras] | [#1308] + +### [9.45.0] - 2022-07-08 +- Added 'ROT8000' operation [@thomasleplus] | [#1250] + +### [9.44.0] - 2022-07-08 +- Added 'LZString Compress' and 'LZString Decompress' operations [@crespyl] | [#1266] + +### [9.43.0] - 2022-07-08 +- Added 'ROT13 Brute Force' and 'ROT47 Brute Force' operations [@mikecat] | [#1264] + +### [9.42.0] - 2022-07-08 +- Added 'LS47 Encrypt' and 'LS47 Decrypt' operations [@n1073645] | [#951] + +### [9.41.0] - 2022-07-08 +- Added 'Caesar Box Cipher' operation [@n1073645] | [#1066] + +### [9.40.0] - 2022-07-08 +- Added 'P-list Viewer' operation [@n1073645] | [#906] + +### [9.39.0] - 2022-06-09 +- Added 'ELF Info' operation [@n1073645] | [#1364] + +### [9.38.0] - 2022-05-30 +- Added 'Parse TCP' operation [@n1474335] | [a895d1d] + +### [9.37.0] - 2022-03-29 +- 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189] +- NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189] + +### [9.36.0] - 2022-03-29 +- 'SIGABA' operation added [@hettysymes] | [#934] + +### [9.35.0] - 2022-03-28 +- 'To Base45' and 'From Base45' operations added [@t-8ch] | [#1242] + +### [9.34.0] - 2022-03-28 +- 'Get All Casings' operation added [@n1073645] | [#1065] + +### [9.33.0] - 2022-03-25 +- Updated to support Node 17 [@n1474335] [@john19696] [@t-8ch] | [[#1326] [#1313] [#1244] +- Improved CJS and ESM module support [@d98762625] | [#1037] + +### [9.32.0] - 2021-08-18 +- 'Protobuf Encode' operation added and decode operation modified to allow decoding with full and partial schemas [@n1474335] | [dd18e52] + +### [9.31.0] - 2021-08-10 +- 'HASSH Client Fingerprint' and 'HASSH Server Fingerprint' operations added [@n1474335] | [e9ca4dc] + +### [9.30.0] - 2021-08-10 +- 'JA3S Fingerprint' operation added [@n1474335] | [289a417] + +### [9.29.0] - 2021-07-28 +- 'JA3 Fingerprint' operation added [@n1474335] | [9a33498] + +### [9.28.0] - 2021-03-26 +- 'CBOR Encode' and 'CBOR Decode' operations added [@Danh4] | [#999] + +### [9.27.0] - 2021-02-12 +- 'Fuzzy Match' operation added [@n1474335] | [8ad18b] + +### [9.26.0] - 2021-02-11 +- 'Get Time' operation added [@n1073645] [@n1474335] | [#1045] + +### [9.25.0] - 2021-02-11 +- 'Extract ID3' operation added [@n1073645] [@n1474335] | [#1006] + +### [9.24.0] - 2021-02-02 +- 'SM3' hashing function added along with more configuration options for other hashing operations [@n1073645] [@n1474335] | [#1022] + +### [9.23.0] - 2021-02-01 +- Various RSA operations added to encrypt, decrypt, sign, verify and generate keys [@mattnotmitt] [@GCHQ77703] | [#652] + +### [9.22.0] - 2021-02-01 +- 'Unicode Text Format' operation added [@mattnotmitt] | [#1083] + +### [9.21.0] - 2020-06-12 +- Node API now exports `magic` operation [@d98762625] | [#1049] + +### [9.20.0] - 2020-03-27 +- 'Parse ObjectID Timestamp' operation added [@dmfj] | [#987] + +### [9.19.0] - 2020-03-24 +- Improvements to the 'Magic' operation, allowing it to recognise more data formats and provide more accurate results [@n1073645] [@n1474335] | [#966] [b765534b](https://github.com/gchq/CyberChef/commit/b765534b8b2a0454a5132a0a52d1d8844bcbdaaa) + +### [9.18.0] - 2020-03-13 +- 'Convert to NATO alphabet' operation added [@MarvinJWendt] | [#674] + +### [9.17.0] - 2020-03-13 +- 'Generate Image' operation added [@pointhi] | [#683] + +### [9.16.0] - 2020-03-06 +- 'Colossus' operation added [@VirtualColossus] | [#917] + +### [9.15.0] - 2020-03-05 +- 'CipherSaber2 Encrypt' and 'CipherSaber2 Decrypt' operations added [@n1073645] | [#952] + +### [9.14.0] - 2020-03-05 +- 'Luhn Checksum' operation added [@n1073645] | [#965] + +### [9.13.0] - 2020-02-13 +- 'Rail Fence Cipher Encode' and 'Rail Fence Cipher Decode' operations added [@Flavsditz] | [#948] + +### [9.12.0] - 2019-12-20 +- 'Normalise Unicode' operation added [@matthieuxyz] | [#912] + +### [9.11.0] - 2019-11-06 +- Implemented CFB, OFB, and CTR modes for Blowfish operations [@cbeuw] | [#653] + +### [9.10.0] - 2019-11-06 +- 'Lorenz' operation added [@VirtualColossus] | [#528] + +### [9.9.0] - 2019-11-01 +- Added support for 109 more character encodings [@n1474335] + +### [9.8.0] - 2019-10-31 +- 'Avro to JSON' operation added [@jarrodconnolly] | [#865] + +### [9.7.0] - 2019-09-13 +- 'Optical Character Recognition' operation added [@MShwed] [@n1474335] | [#632] + +### [9.6.0] - 2019-09-04 +- 'Bacon Cipher Encode' and 'Bacon Cipher Decode' operations added [@kassi] | [#500] + +### [9.5.0] - 2019-09-04 +- Various Steganography operations added: 'Extract LSB', 'Extract RGBA', 'Randomize Colour Palette', and 'View Bit Plane' [@Ge0rg3] | [#625] + +### [9.4.0] - 2019-08-30 +- 'Render Markdown' operation added [@j433866] | [#627] + +### [9.3.0] - 2019-08-30 +- 'Show on map' operation added [@j433866] | [#477] + +### [9.2.0] - 2019-08-23 +- 'Parse UDP' operation added [@h345983745] | [#614] + +### [9.1.0] - 2019-08-22 +- 'Parse SSH Host Key' operation added [@j433866] | [#595] +- 'Defang IP Addresses' operation added [@h345983745] | [#556] + +
+ +## [9.0.0] - 2019-07-09 +- [Multiple inputs](https://github.com/gchq/CyberChef/wiki/Multiple-Inputs) are now supported in the main web UI, allowing you to upload and process multiple files at once [@j433866] | [#566] +- A [Node.js API](https://github.com/gchq/CyberChef/wiki/Node-API) has been implemented, meaning that CyberChef can now be used as a library, either to provide specific operations, or an entire baking environment [@d98762625] | [#291] +- A [read-eval-print loop (REPL)](https://github.com/gchq/CyberChef/wiki/Node-API#repl) is also included to enable prototyping and experimentation with the API [@d98762625] | [#291] +- Light and dark Solarized themes added [@j433866] | [#566] + +
+ Click to expand v8 minor versions + +### [8.38.0] - 2019-07-03 +- 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530] + +### [8.37.0] - 2019-07-03 +- 'CRC-8 Checksum' operation added [@MShwed] | [#591] + +### [8.36.0] - 2019-07-03 +- 'PGP Verify' operation added [@artemisbot] | [#585] + +### [8.35.0] - 2019-07-03 +- 'Sharpen Image', 'Convert Image Format' and 'Add Text To Image' operations added [@j433866] | [#515] + +### [8.34.0] - 2019-06-28 +- Various new visualisations added to the 'Entropy' operation [@MShwed] | [#535] +- Efficiency improvements made to the 'Entropy' operation for large file support [@n1474335] + +### [8.33.0] - 2019-06-27 +- 'Bzip2 Compress' operation added and 'Bzip2 Decompress' operation greatly improved [@artemisbot] | [#531] + +### [8.32.0] - 2019-06-27 +- 'Index of Coincidence' operation added [@Ge0rg3] | [#571] + +### [8.31.0] - 2019-04-12 +- The downloadable version of CyberChef is now a .zip file containing separate modules rather than a single .htm file. It is still completely standalone and will not make any external network requests. This change reduces the complexity of the build process significantly. [@n1474335] + +### [8.30.0] - 2019-04-12 +- 'Decode Protobuf' operation added [@n1474335] | [#533] + +### [8.29.0] - 2019-03-31 +- 'BLAKE2s' and 'BLAKE2b' hashing operations added [@h345983745] | [#525] + +### [8.28.0] - 2019-03-31 +- 'Heatmap Chart', 'Hex Density Chart', 'Scatter Chart' and 'Series Chart' operation added [@artemisbot] [@tlwr] | [#496] [#143] + +### [8.27.0] - 2019-03-14 +- 'Enigma', 'Typex', 'Bombe' and 'Multiple Bombe' operations added [@s2224834] | [#516] +- See [this wiki article](https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex) for a full explanation of these operations. +- New Bombe-style loading animation added for long-running operations [@n1474335] +- New operation argument types added: `populateMultiOption` and `argSelector` [@n1474335] + +### [8.26.0] - 2019-03-09 +- Various image manipulation operations added [@j433866] | [#506] + +### [8.25.0] - 2019-03-09 +- 'Extract Files' operation added and more file formats supported [@n1474335] | [#440] + +### [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) + +[10.19.0]: https://github.com/gchq/CyberChef/releases/tag/v10.19.0 +[10.18.0]: https://github.com/gchq/CyberChef/releases/tag/v10.18.0 +[10.17.0]: https://github.com/gchq/CyberChef/releases/tag/v10.17.0 +[10.16.0]: https://github.com/gchq/CyberChef/releases/tag/v10.16.0 +[10.15.0]: https://github.com/gchq/CyberChef/releases/tag/v10.15.0 +[10.14.0]: https://github.com/gchq/CyberChef/releases/tag/v10.14.0 +[10.13.0]: https://github.com/gchq/CyberChef/releases/tag/v10.13.0 +[10.12.0]: https://github.com/gchq/CyberChef/releases/tag/v10.12.0 +[10.11.0]: https://github.com/gchq/CyberChef/releases/tag/v10.11.0 +[10.10.0]: https://github.com/gchq/CyberChef/releases/tag/v10.10.0 +[10.9.0]: https://github.com/gchq/CyberChef/releases/tag/v10.9.0 +[10.8.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0 +[10.7.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0 +[10.6.0]: https://github.com/gchq/CyberChef/releases/tag/v10.6.0 +[10.5.0]: https://github.com/gchq/CyberChef/releases/tag/v10.5.0 +[10.4.0]: https://github.com/gchq/CyberChef/releases/tag/v10.4.0 +[10.3.0]: https://github.com/gchq/CyberChef/releases/tag/v10.3.0 +[10.2.0]: https://github.com/gchq/CyberChef/releases/tag/v10.2.0 +[10.1.0]: https://github.com/gchq/CyberChef/releases/tag/v10.1.0 +[10.0.0]: https://github.com/gchq/CyberChef/releases/tag/v10.0.0 +[9.55.0]: https://github.com/gchq/CyberChef/releases/tag/v9.55.0 +[9.54.0]: https://github.com/gchq/CyberChef/releases/tag/v9.54.0 +[9.53.0]: https://github.com/gchq/CyberChef/releases/tag/v9.53.0 +[9.52.0]: https://github.com/gchq/CyberChef/releases/tag/v9.52.0 +[9.51.0]: https://github.com/gchq/CyberChef/releases/tag/v9.51.0 +[9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0 +[9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0 +[9.48.0]: https://github.com/gchq/CyberChef/releases/tag/v9.48.0 +[9.47.0]: https://github.com/gchq/CyberChef/releases/tag/v9.47.0 +[9.46.0]: https://github.com/gchq/CyberChef/releases/tag/v9.46.0 +[9.45.0]: https://github.com/gchq/CyberChef/releases/tag/v9.45.0 +[9.44.0]: https://github.com/gchq/CyberChef/releases/tag/v9.44.0 +[9.43.0]: https://github.com/gchq/CyberChef/releases/tag/v9.43.0 +[9.42.0]: https://github.com/gchq/CyberChef/releases/tag/v9.42.0 +[9.41.0]: https://github.com/gchq/CyberChef/releases/tag/v9.41.0 +[9.40.0]: https://github.com/gchq/CyberChef/releases/tag/v9.40.0 +[9.39.0]: https://github.com/gchq/CyberChef/releases/tag/v9.39.0 +[9.38.0]: https://github.com/gchq/CyberChef/releases/tag/v9.38.0 +[9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0 +[9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0 +[9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0 +[9.34.0]: https://github.com/gchq/CyberChef/releases/tag/v9.34.0 +[9.33.0]: https://github.com/gchq/CyberChef/releases/tag/v9.33.0 +[9.32.0]: https://github.com/gchq/CyberChef/releases/tag/v9.32.0 +[9.31.0]: https://github.com/gchq/CyberChef/releases/tag/v9.31.0 +[9.30.0]: https://github.com/gchq/CyberChef/releases/tag/v9.30.0 +[9.29.0]: https://github.com/gchq/CyberChef/releases/tag/v9.29.0 +[9.28.0]: https://github.com/gchq/CyberChef/releases/tag/v9.28.0 +[9.27.0]: https://github.com/gchq/CyberChef/releases/tag/v9.27.0 +[9.26.0]: https://github.com/gchq/CyberChef/releases/tag/v9.26.0 +[9.25.0]: https://github.com/gchq/CyberChef/releases/tag/v9.25.0 +[9.24.0]: https://github.com/gchq/CyberChef/releases/tag/v9.24.0 +[9.23.0]: https://github.com/gchq/CyberChef/releases/tag/v9.23.0 +[9.22.0]: https://github.com/gchq/CyberChef/releases/tag/v9.22.0 +[9.21.0]: https://github.com/gchq/CyberChef/releases/tag/v9.21.0 +[9.20.0]: https://github.com/gchq/CyberChef/releases/tag/v9.20.0 +[9.19.0]: https://github.com/gchq/CyberChef/releases/tag/v9.19.0 +[9.18.0]: https://github.com/gchq/CyberChef/releases/tag/v9.18.0 +[9.17.0]: https://github.com/gchq/CyberChef/releases/tag/v9.17.0 +[9.16.0]: https://github.com/gchq/CyberChef/releases/tag/v9.16.0 +[9.15.0]: https://github.com/gchq/CyberChef/releases/tag/v9.15.0 +[9.14.0]: https://github.com/gchq/CyberChef/releases/tag/v9.14.0 +[9.13.0]: https://github.com/gchq/CyberChef/releases/tag/v9.13.0 +[9.12.0]: https://github.com/gchq/CyberChef/releases/tag/v9.12.0 +[9.11.0]: https://github.com/gchq/CyberChef/releases/tag/v9.11.0 +[9.10.0]: https://github.com/gchq/CyberChef/releases/tag/v9.10.0 +[9.9.0]: https://github.com/gchq/CyberChef/releases/tag/v9.9.0 +[9.8.0]: https://github.com/gchq/CyberChef/releases/tag/v9.8.0 +[9.7.0]: https://github.com/gchq/CyberChef/releases/tag/v9.7.0 +[9.6.0]: https://github.com/gchq/CyberChef/releases/tag/v9.6.0 +[9.5.0]: https://github.com/gchq/CyberChef/releases/tag/v9.5.0 +[9.4.0]: https://github.com/gchq/CyberChef/releases/tag/v9.4.0 +[9.3.0]: https://github.com/gchq/CyberChef/releases/tag/v9.3.0 +[9.2.0]: https://github.com/gchq/CyberChef/releases/tag/v9.2.0 +[9.1.0]: https://github.com/gchq/CyberChef/releases/tag/v9.1.0 +[9.0.0]: https://github.com/gchq/CyberChef/releases/tag/v9.0.0 +[8.38.0]: https://github.com/gchq/CyberChef/releases/tag/v8.38.0 +[8.37.0]: https://github.com/gchq/CyberChef/releases/tag/v8.37.0 +[8.36.0]: https://github.com/gchq/CyberChef/releases/tag/v8.36.0 +[8.35.0]: https://github.com/gchq/CyberChef/releases/tag/v8.35.0 +[8.34.0]: https://github.com/gchq/CyberChef/releases/tag/v8.34.0 +[8.33.0]: https://github.com/gchq/CyberChef/releases/tag/v8.33.0 +[8.32.0]: https://github.com/gchq/CyberChef/releases/tag/v8.32.0 +[8.31.0]: https://github.com/gchq/CyberChef/releases/tag/v8.31.0 +[8.30.0]: https://github.com/gchq/CyberChef/releases/tag/v8.30.0 +[8.29.0]: https://github.com/gchq/CyberChef/releases/tag/v8.29.0 +[8.28.0]: https://github.com/gchq/CyberChef/releases/tag/v8.28.0 +[8.27.0]: https://github.com/gchq/CyberChef/releases/tag/v8.27.0 +[8.26.0]: https://github.com/gchq/CyberChef/releases/tag/v8.26.0 +[8.25.0]: https://github.com/gchq/CyberChef/releases/tag/v8.25.0 +[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 +[@n1073645]: https://github.com/n1073645 +[@GCHQ77703]: https://github.com/GCHQ77703 +[@h345983745]: https://github.com/h345983745 +[@s2224834]: https://github.com/s2224834 +[@artemisbot]: https://github.com/artemisbot +[@tlwr]: https://github.com/tlwr +[@picapi]: https://github.com/picapi +[@Dachande663]: https://github.com/Dachande663 +[@JustAnotherMark]: https://github.com/JustAnotherMark +[@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 +[@Ge0rg3]: https://github.com/Ge0rg3 +[@MShwed]: https://github.com/MShwed +[@kassi]: https://github.com/kassi +[@jarrodconnolly]: https://github.com/jarrodconnolly +[@VirtualColossus]: https://github.com/VirtualColossus +[@cbeuw]: https://github.com/cbeuw +[@matthieuxyz]: https://github.com/matthieuxyz +[@Flavsditz]: https://github.com/Flavsditz +[@pointhi]: https://github.com/pointhi +[@MarvinJWendt]: https://github.com/MarvinJWendt +[@dmfj]: https://github.com/dmfj +[@mattnotmitt]: https://github.com/mattnotmitt +[@Danh4]: https://github.com/Danh4 +[@john19696]: https://github.com/john19696 +[@t-8ch]: https://github.com/t-8ch +[@hettysymes]: https://github.com/hettysymes +[@swesven]: https://github.com/swesven +[@mikecat]: https://github.com/mikecat +[@crespyl]: https://github.com/crespyl +[@thomasleplus]: https://github.com/thomasleplus +[@valdelaseras]: https://github.com/valdelaseras +[@brun0ne]: https://github.com/brun0ne +[@joostrijneveld]: https://github.com/joostrijneveld +[@Xenonym]: https://github.com/Xenonym +[@gchq77703]: https://github.com/gchq77703 +[@a3957273]: https://github.com/a3957273 +[@0xThiebaut]: https://github.com/0xThiebaut +[@cnotin]: https://github.com/cnotin +[@KevinSJ]: https://github.com/KevinSJ +[@sw5678]: https://github.com/sw5678 +[@sg5506844]: https://github.com/sg5506844 +[@AliceGrey]: https://github.com/AliceGrey +[@AshCorr]: https://github.com/AshCorr +[@simonw]: https://github.com/simonw +[@chriswhite199]: https://github.com/chriswhite199 +[@breakersall]: https://github.com/breakersall +[@evanreichard]: https://github.com/evanreichard +[@devcydo]: https://github.com/devcydo +[@zb3]: https://github.com/zb3 +[@jkataja]: https://github.com/jkataja +[@tomgond]: https://github.com/tomgond +[@e218736]: https://github.com/e218736 +[@TheZ3ro]: https://github.com/TheZ3ro +[@EvieHarv]: https://github.com/EvieHarv +[@cplussharp]: https://github.com/cplussharp +[@robinsandhu]: https://github.com/robinsandhu +[@eltociear]: https://github.com/eltociear + + +[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7 +[9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513 +[289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe +[e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8 +[dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da +[a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737 +[31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff +[760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1 +[65ffd8d]: https://github.com/gchq/CyberChef/commit/65ffd8d65d88eb369f6f61a5d1d0f807179bffb7 +[0a353ee]: https://github.com/gchq/CyberChef/commit/0a353eeb378b9ca5d49e23c7dfc175ae07107b08 + +[#95]: https://github.com/gchq/CyberChef/pull/299 +[#173]: https://github.com/gchq/CyberChef/pull/173 +[#143]: https://github.com/gchq/CyberChef/pull/143 +[#224]: https://github.com/gchq/CyberChef/pull/224 +[#239]: https://github.com/gchq/CyberChef/pull/239 +[#248]: https://github.com/gchq/CyberChef/pull/248 +[#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 +[#291]: https://github.com/gchq/CyberChef/pull/291 +[#294]: https://github.com/gchq/CyberChef/pull/294 +[#296]: https://github.com/gchq/CyberChef/pull/296 +[#298]: https://github.com/gchq/CyberChef/pull/298 +[#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 +[#440]: https://github.com/gchq/CyberChef/pull/440 +[#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 +[#477]: https://github.com/gchq/CyberChef/pull/477 +[#489]: https://github.com/gchq/CyberChef/pull/489 +[#496]: https://github.com/gchq/CyberChef/pull/496 +[#500]: https://github.com/gchq/CyberChef/pull/500 +[#506]: https://github.com/gchq/CyberChef/pull/506 +[#515]: https://github.com/gchq/CyberChef/pull/515 +[#516]: https://github.com/gchq/CyberChef/pull/516 +[#525]: https://github.com/gchq/CyberChef/pull/525 +[#528]: https://github.com/gchq/CyberChef/pull/528 +[#530]: https://github.com/gchq/CyberChef/pull/530 +[#531]: https://github.com/gchq/CyberChef/pull/531 +[#533]: https://github.com/gchq/CyberChef/pull/533 +[#535]: https://github.com/gchq/CyberChef/pull/535 +[#556]: https://github.com/gchq/CyberChef/pull/556 +[#566]: https://github.com/gchq/CyberChef/pull/566 +[#571]: https://github.com/gchq/CyberChef/pull/571 +[#585]: https://github.com/gchq/CyberChef/pull/585 +[#591]: https://github.com/gchq/CyberChef/pull/591 +[#595]: https://github.com/gchq/CyberChef/pull/595 +[#614]: https://github.com/gchq/CyberChef/pull/614 +[#625]: https://github.com/gchq/CyberChef/pull/625 +[#627]: https://github.com/gchq/CyberChef/pull/627 +[#632]: https://github.com/gchq/CyberChef/pull/632 +[#652]: https://github.com/gchq/CyberChef/pull/652 +[#653]: https://github.com/gchq/CyberChef/pull/653 +[#674]: https://github.com/gchq/CyberChef/pull/674 +[#683]: https://github.com/gchq/CyberChef/pull/683 +[#865]: https://github.com/gchq/CyberChef/pull/865 +[#906]: https://github.com/gchq/CyberChef/pull/906 +[#912]: https://github.com/gchq/CyberChef/pull/912 +[#917]: https://github.com/gchq/CyberChef/pull/917 +[#934]: https://github.com/gchq/CyberChef/pull/934 +[#948]: https://github.com/gchq/CyberChef/pull/948 +[#951]: https://github.com/gchq/CyberChef/pull/951 +[#952]: https://github.com/gchq/CyberChef/pull/952 +[#965]: https://github.com/gchq/CyberChef/pull/965 +[#966]: https://github.com/gchq/CyberChef/pull/966 +[#987]: https://github.com/gchq/CyberChef/pull/987 +[#999]: https://github.com/gchq/CyberChef/pull/999 +[#1006]: https://github.com/gchq/CyberChef/pull/1006 +[#1022]: https://github.com/gchq/CyberChef/pull/1022 +[#1037]: https://github.com/gchq/CyberChef/pull/1037 +[#1045]: https://github.com/gchq/CyberChef/pull/1045 +[#1049]: https://github.com/gchq/CyberChef/pull/1049 +[#1065]: https://github.com/gchq/CyberChef/pull/1065 +[#1066]: https://github.com/gchq/CyberChef/pull/1066 +[#1083]: https://github.com/gchq/CyberChef/pull/1083 +[#1189]: https://github.com/gchq/CyberChef/pull/1189 +[#1242]: https://github.com/gchq/CyberChef/pull/1242 +[#1244]: https://github.com/gchq/CyberChef/pull/1244 +[#1313]: https://github.com/gchq/CyberChef/pull/1313 +[#1326]: https://github.com/gchq/CyberChef/pull/1326 +[#1364]: https://github.com/gchq/CyberChef/pull/1364 +[#1264]: https://github.com/gchq/CyberChef/pull/1264 +[#1266]: https://github.com/gchq/CyberChef/pull/1266 +[#1250]: https://github.com/gchq/CyberChef/pull/1250 +[#1308]: https://github.com/gchq/CyberChef/pull/1308 +[#1405]: https://github.com/gchq/CyberChef/pull/1405 +[#1421]: https://github.com/gchq/CyberChef/pull/1421 +[#1427]: https://github.com/gchq/CyberChef/pull/1427 +[#1472]: https://github.com/gchq/CyberChef/pull/1472 +[#1457]: https://github.com/gchq/CyberChef/pull/1457 +[#1466]: https://github.com/gchq/CyberChef/pull/1466 +[#1456]: https://github.com/gchq/CyberChef/pull/1456 +[#1450]: https://github.com/gchq/CyberChef/pull/1450 +[#1498]: https://github.com/gchq/CyberChef/pull/1498 +[#1499]: https://github.com/gchq/CyberChef/pull/1499 +[#1528]: https://github.com/gchq/CyberChef/pull/1528 +[#661]: https://github.com/gchq/CyberChef/pull/661 +[#493]: https://github.com/gchq/CyberChef/pull/493 +[#592]: https://github.com/gchq/CyberChef/issues/592 +[#1703]: https://github.com/gchq/CyberChef/issues/1703 +[#1675]: https://github.com/gchq/CyberChef/issues/1675 +[#1678]: https://github.com/gchq/CyberChef/issues/1678 +[#1541]: https://github.com/gchq/CyberChef/issues/1541 +[#1667]: https://github.com/gchq/CyberChef/issues/1667 +[#1555]: https://github.com/gchq/CyberChef/issues/1555 +[#1694]: https://github.com/gchq/CyberChef/issues/1694 +[#1699]: https://github.com/gchq/CyberChef/issues/1699 +[#1757]: https://github.com/gchq/CyberChef/issues/1757 +[#1752]: https://github.com/gchq/CyberChef/issues/1752 +[#1753]: https://github.com/gchq/CyberChef/issues/1753 +[#1750]: https://github.com/gchq/CyberChef/issues/1750 +[#1591]: https://github.com/gchq/CyberChef/issues/1591 +[#654]: https://github.com/gchq/CyberChef/issues/654 +[#1762]: https://github.com/gchq/CyberChef/issues/1762 +[#1606]: https://github.com/gchq/CyberChef/issues/1606 +[#1197]: https://github.com/gchq/CyberChef/issues/1197 +[#933]: https://github.com/gchq/CyberChef/issues/933 +[#1361]: https://github.com/gchq/CyberChef/issues/1361 +[#1765]: https://github.com/gchq/CyberChef/issues/1765 +[#1767]: https://github.com/gchq/CyberChef/issues/1767 +[#1769]: https://github.com/gchq/CyberChef/issues/1769 +[#1759]: https://github.com/gchq/CyberChef/issues/1759 +[#1504]: https://github.com/gchq/CyberChef/issues/1504 +[#512]: https://github.com/gchq/CyberChef/issues/512 +[#1732]: https://github.com/gchq/CyberChef/issues/1732 +[#1789]: https://github.com/gchq/CyberChef/issues/1789 + 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/Dockerfile b/Dockerfile new file mode 100644 index 00000000..d63a8ca3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +##################################### +# Build the app to a static website # +##################################### +# Modifier --platform=$BUILDPLATFORM limits the platform to "BUILDPLATFORM" during buildx multi-platform builds +# This is because npm "chromedriver" package is not compatiable with all platforms +# For more info see: https://docs.docker.com/build/building/multi-platform/#cross-compilation +FROM --platform=$BUILDPLATFORM node:18-alpine AS builder + +WORKDIR /app + +COPY package.json . +COPY package-lock.json . + +# Install dependencies +# --ignore-scripts prevents postinstall script (which runs grunt) as it depends on files other than package.json +RUN npm ci --ignore-scripts + +# Copy files needed for postinstall and build +COPY . . + +# npm postinstall runs grunt, which depends on files other than package.json +RUN npm run postinstall + +# Build the app +RUN npm run build + +######################################### +# Package static build files into nginx # +######################################### +# We are using Github Actions: redhat-actions/buildah-build@v2 which needs manual selection of arch in base image +# Remove TARGETARCH if docker buildx is supported in the CI release as --platform=$TARGETPLATFORM will be automatically set +ARG TARGETARCH +ARG TARGETPLATFORM +FROM ${TARGETARCH}/nginx:stable-alpine AS cyberchef + +COPY --from=builder /app/build/prod /usr/share/nginx/html/ diff --git a/Gruntfile.js b/Gruntfile.js index 3d77ab02..d3f6b635 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,7 +1,20 @@ -var webpack = require("webpack"), - ExtractTextPlugin = require("extract-text-webpack-plugin"), - HtmlWebpackPlugin = require("html-webpack-plugin"), - Inliner = require("web-resource-inliner"); +"use strict"; + +const webpack = require("webpack"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; +const glob = require("glob"); +const path = require("path"); + +const nodeFlags = "--experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-warnings --no-deprecation"; + +/** + * 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,282 +23,236 @@ 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"]); - - grunt.registerTask("node", - "Compiles CyberChef into a single NodeJS module.", - ["clean:node", "webpack:node", "chmod:build"]); - - grunt.registerTask("test", - "A task which runs all the tests in test/tests.", - ["clean:test", "webpack:tests", "execute:test"]); - - grunt.registerTask("docs", - "Compiles documentation in the /docs directory.", - ["clean:docs", "jsdoc", "chmod:docs"]); + ["clean:dev", "clean:config", "exec:generateConfig", "concurrent:dev"]); grunt.registerTask("prod", "Creates a production-ready build. Use the --msg flag to add a compile message.", - ["eslint", "clean:prod", "webpack:webProd", "inline", "chmod"]); + [ + "eslint", "clean:prod", "clean:config", "exec:generateConfig", "findModules", "webpack:web", + "copy:standalone", "zip:standalone", "clean:standalone", "exec:calcDownloadHash", "chmod" + ]); - grunt.registerTask("release", - "Prepares and deploys a production version of CyberChef to the gh-pages branch.", - ["copy:ghPages", "exec:deployGhPages"]); + grunt.registerTask("node", + "Compiles CyberChef into a single NodeJS module.", + [ + "clean:node", "clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex" + ]); + + grunt.registerTask("configTests", + "A task which configures config files in preparation for tests to be run. Use `npm test` to run tests.", + [ + "clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex" + ]); + + grunt.registerTask("testui", + "A task which runs all the UI tests in the tests directory. The prod task must already have been run.", + ["connect:prod", "exec:browserTests"]); + + grunt.registerTask("testnodeconsumer", + "A task which checks whether consuming CJS and ESM apps work with the CyberChef build", + ["exec:setupNodeConsumers", "exec:testCJSNodeConsumer", "exec:testESMNodeConsumer", "exec:teardownNodeConsumers"]); grunt.registerTask("default", "Lints the code base", ["eslint", "exec:repoSize"]); - grunt.registerTask("inline", - "Compiles a production build of CyberChef into a single, portable web page.", - runInliner); - - grunt.registerTask("doc", "docs"); - grunt.registerTask("tests", "test"); grunt.registerTask("lint", "eslint"); + grunt.registerTask("findModules", + "Finds all generated modules and updates the entry point list for Webpack", + function(arg1, arg2) { + const moduleEntryPoints = listEntryModules(); + + grunt.log.writeln(`Found ${Object.keys(moduleEntryPoints).length} modules.`); + + grunt.config.set("webpack.web.entry", + Object.assign({ + main: "./src/web/index.js" + }, moduleEntryPoints)); + }); + // Load tasks provided by each plugin grunt.loadNpmTasks("grunt-eslint"); grunt.loadNpmTasks("grunt-webpack"); - grunt.loadNpmTasks("grunt-jsdoc"); grunt.loadNpmTasks("grunt-contrib-clean"); grunt.loadNpmTasks("grunt-contrib-copy"); + grunt.loadNpmTasks("grunt-contrib-watch"); grunt.loadNpmTasks("grunt-chmod"); grunt.loadNpmTasks("grunt-exec"); - grunt.loadNpmTasks("grunt-execute"); + grunt.loadNpmTasks("grunt-concurrent"); + grunt.loadNpmTasks("grunt-contrib-connect"); + grunt.loadNpmTasks("grunt-zip"); // Project configuration - var compileTime = grunt.template.today("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"; + const compileYear = grunt.template.today("UTC:yyyy"), + compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC", + pkg = grunt.file.readJSON("package.json"), + webpackConfig = require("./webpack.config.js"), + BUILD_CONSTANTS = { + COMPILE_YEAR: JSON.stringify(compileYear), + COMPILE_TIME: JSON.stringify(compileTime), + COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""), + PKG_VERSION: JSON.stringify(pkg.version), + }, + moduleEntryPoints = listEntryModules(), + nodeConsumerTestPath = "~/tmp-cyberchef", + /** + * Configuration for Webpack production build. Defined as a function so that it + * can be recalculated when new modules are generated. + */ + webpackProdConf = () => { + return { + mode: "production", + target: "web", + entry: Object.assign({ + main: "./src/web/index.js" + }, moduleEntryPoints), + output: { + path: __dirname + "/build/prod", + filename: chunkData => { + return chunkData.chunk.name === "main" ? "assets/[name].js": "[name].js"; + }, + globalObject: "this" + }, + resolve: { + alias: { + "./config/modules/OpModules.mjs": "./config/modules/Default.mjs" + } + }, + plugins: [ + new webpack.DefinePlugin(BUILD_CONSTANTS), + new HtmlWebpackPlugin({ + filename: "index.html", + template: "./src/web/html/index.html", + chunks: ["main"], + compileYear: compileYear, + compileTime: compileTime, + version: pkg.version, + minify: { + removeComments: true, + collapseWhitespace: true, + minifyJS: true, + minifyCSS: true + } + }), + new BundleAnalyzerPlugin({ + analyzerMode: "static", + reportFilename: "BundleAnalyzerReport.html", + openAnalyzer: false + }), + ] + }; + }; + /** - * Compiles a production build of CyberChef into a single, portable web page. + * Generates an entry list for all the modules. */ - function runInliner() { - var inlinerError = false; - Inliner.html({ - relativeTo: "build/prod/", - fileContent: grunt.file.read("build/prod/cyberchef.htm"), - images: true, - svgs: true, - scripts: true, - links: true, - strict: true - }, function(error, result) { - if (error) { - console.log(error); - inlinerError = true; - return false; - } - grunt.file.write("build/prod/cyberchef.htm", result); + 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["modules/" + basename.split(".mjs")[0]] = path.resolve(file); }); - return !inlinerError; + return entryModules; + } + + /** + * Detects the correct delimiter to use to chain shell commands together + * based on the current OS. + * + * @param {string[]} cmds + * @returns {string} + */ + function chainCommands(cmds) { + const win = process.platform === "win32"; + if (!win) { + return cmds.join(";"); + } + return cmds + // && means that subsequent commands will not be executed if the + // previous one fails. & would coninue on a fail + .join("&&") + // Windows does not support \n properly + .replace(/\n/g, "\\n"); } 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"], + nodeConfig: ["src/node/index.mjs", "src/node/config/OperationConfig.json"], + standalone: ["build/prod/CyberChef*.html"] }, eslint: { - options: { - configFile: "src/.eslintrc.json" - }, - configs: ["Gruntfile.js"], - core: ["src/core/**/*.js", "!src/core/lib/**/*"], - web: ["src/web/**/*.js"], - node: ["src/node/**/*.js"], - tests: ["test/**/*.js"], - }, - jsdoc: { - options: { - destination: "docs", - template: "node_modules/ink-docstrap/template", - recurse: true, - readme: "./README.md", - configure: "docs/jsdoc.conf.json" - }, - all: { - src: [ - "src/**/*.js", - "!src/core/lib/**/*", - ], - } + configs: ["*.{js,mjs}"], + core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"], + web: ["src/web/**/*.{js,mjs}", "!src/web/static/**/*"], + node: ["src/node/**/*.{js,mjs}"], + tests: ["tests/**/*.{js,mjs}"], }, 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"), - ], + options: webpackConfig, + myConfig: webpackConfig, + web: webpackProdConf(), + }, + "webpack-dev-server": { + options: webpackConfig, + start: { + mode: "development", + target: "web", + entry: Object.assign({ + main: "./src/web/index.js" + }, moduleEntryPoints), resolve: { alias: { - jquery: "jquery/src/jquery" + "./config/modules/OpModules.mjs": "./config/modules/Default.mjs" } }, - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - loader: "babel-loader?compact=false" - }, - { - test: /\.css$/, - use: ExtractTextPlugin.extract({ - use: "css-loader?minimize" - }) - }, - { - test: /\.less$/, - use: ExtractTextPlugin.extract({ - use: [ - { loader: "css-loader?minimize" }, - { loader: "less-loader" } - ] - }) - }, - { - test: /\.(ico|eot|ttf|woff|woff2)$/, - loader: "url-loader", - options: { - limit: 10000 - } - }, - { // 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 - } - }, - ] - }, - stats: { - children: false - } - }, - webDev: { - target: "web", - entry: "./src/web/index.js", - output: { - filename: "scripts.js", - path: __dirname + "/build/dev" + devServer: { + port: grunt.option("port") || 8080, + client: { + logging: "error", + overlay: true + }, + hot: "only" }, plugins: [ + new webpack.DefinePlugin(BUILD_CONSTANTS), new HtmlWebpackPlugin({ filename: "index.html", template: "./src/web/html/index.html", - compileTime: compileTime + chunks: ["main"], + compileYear: compileYear, + compileTime: compileTime, + version: pkg.version, }) - ], - watch: true - }, - webProd: { - target: "web", - entry: "./src/web/index.js", - output: { - filename: "scripts.js", - 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, - minify: { - removeComments: true, - collapseWhitespace: true, - minifyJS: true, - minifyCSS: true - } - }), - new HtmlWebpackPlugin({ // Inline version - filename: "cyberchef.htm", - template: "./src/web/html/index.html", - compileTime: compileTime, - inline: true, - minify: { - removeComments: true, - collapseWhitespace: true, - minifyJS: true, - minifyCSS: true - } - }), ] - }, - tests: { - target: "node", - entry: "./test/index.js", - output: { - filename: "index.js", - path: __dirname + "/build/test" - } - }, - node: { - target: "node", - entry: "./src/node/index.js", - output: { - filename: "CyberChef.js", - path: __dirname + "/build/node", - library: "CyberChef", - libraryTarget: "commonjs2" + } + }, + zip: { + standalone: { + cwd: "build/prod/", + src: [ + "build/prod/**/*", + "!build/prod/index.html", + "!build/prod/BundleAnalyzerReport.html", + ], + dest: `build/prod/CyberChef_v${pkg.version}.zip` + } + }, + connect: { + prod: { + options: { + port: grunt.option("port") || 8000, + base: "build/prod/" } } }, @@ -293,14 +260,51 @@ module.exports = function (grunt) { ghPages: { options: { process: function (content, srcpath) { - // Add Google Analytics code to index.html - content = content.replace("", - grunt.file.read("src/static/ga.html") + ""); - return grunt.template.process(content); - } + if (srcpath.indexOf("index.html") >= 0) { + // Add Google Analytics code to index.html + content = content.replace("", + grunt.file.read("src/web/static/ga.html") + ""); + + // Add Structured Data for SEO + content = content.replace("", + ""); + 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" + } + ] + }, + standalone: { + options: { + process: function (content, srcpath) { + if (srcpath.indexOf("index.html") >= 0) { + // Replace download link with version number + content = content.replace(/]+>Download CyberChef.+?<\/a>/, + `Version ${pkg.version}`); + + return grunt.template.process(content, srcpath); + } else { + return content; + } + }, + noProcess: ["**", "!**/*.html"] + }, + files: [ + { + src: ["build/prod/index.html"], + dest: `build/prod/CyberChef_v${pkg.version}.html` + } + ] } }, chmod: { @@ -309,39 +313,137 @@ module.exports = function (grunt) { mode: "755", }, src: ["build/**/*", "build/"] - }, - docs: { - options: { - mode: "755", - }, - src: ["docs/**/*", "docs/"] + } + }, + watch: { + config: { + files: ["src/core/operations/**/*", "!src/core/operations/index.mjs"], + tasks: ["exec:generateNodeIndex", "exec:generateConfig"] + } + }, + concurrent: { + dev: ["watch:config", "webpack-dev-server:start"], + options: { + logConcurrentOutput: true } }, exec: { + calcDownloadHash: { + command: function () { + switch (process.platform) { + case "darwin": + return chainCommands([ + `shasum -a 256 build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`, + `sed -i '' -e "s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/" build/prod/index.html` + ]); + default: + return chainCommands([ + `sha256sum build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`, + `sed -i -e "s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/" build/prod/index.html` + ]); + } + }, + }, repoSize: { - command: [ + command: chainCommands([ "git ls-files | wc -l | xargs printf '\n%b\ttracked files\n'", "du -hs | egrep -o '^[^\t]*' | xargs printf '%b\trepository size\n'" - ].join(";"), + ]), stderr: false }, cleanGit: { command: "git gc --prune=now --aggressive" }, - deployGhPages: { - command: [ - "git add build/prod/index.html -v", - "COMMIT_HASH=$(git rev-parse HEAD)", - "git commit -m \"GitHub Pages release for ${COMMIT_HASH}\"", - "git push origin `git subtree split --prefix build/prod master`:gh-pages --force", - "git reset HEAD~", - "git checkout build/prod/index.html" - ].join(";") + sitemap: { + command: `node ${nodeFlags} src/web/static/sitemap.mjs > build/prod/sitemap.xml`, + sync: true + }, + generateConfig: { + command: chainCommands([ + "echo '\n--- Regenerating config files. ---'", + "echo [] > src/core/config/OperationConfig.json", + `node ${nodeFlags} src/core/config/scripts/generateOpsIndex.mjs`, + `node ${nodeFlags} src/core/config/scripts/generateConfig.mjs`, + "echo '--- Config scripts finished. ---\n'" + ]), + sync: true + }, + generateNodeIndex: { + command: chainCommands([ + "echo '\n--- Regenerating node index ---'", + `node ${nodeFlags} src/node/config/scripts/generateNodeIndex.mjs`, + "echo '--- Node index generated. ---\n'" + ]), + sync: true + }, + browserTests: { + command: "./node_modules/.bin/nightwatch --env prod" + }, + setupNodeConsumers: { + command: chainCommands([ + "echo '\n--- Testing node consumers ---'", + "npm link", + `mkdir ${nodeConsumerTestPath}`, + `cp tests/node/consumers/* ${nodeConsumerTestPath}`, + `cd ${nodeConsumerTestPath}`, + "npm link cyberchef" + ]), + sync: true + }, + teardownNodeConsumers: { + command: chainCommands([ + `rm -rf ${nodeConsumerTestPath}`, + "echo '\n--- Node consumer tests complete ---'" + ]), + }, + testCJSNodeConsumer: { + command: chainCommands([ + `cd ${nodeConsumerTestPath}`, + `node ${nodeFlags} cjs-consumer.js`, + ]), + stdout: false, + }, + testESMNodeConsumer: { + command: chainCommands([ + `cd ${nodeConsumerTestPath}`, + `node ${nodeFlags} esm-consumer.mjs`, + ]), + stdout: false, + }, + fixCryptoApiImports: { + command: function () { + switch (process.platform) { + case "darwin": + return `find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i '' -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`; + default: + return `find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`; + } + }, + stdout: false + }, + fixSnackbarMarkup: { + command: function () { + switch (process.platform) { + case "darwin": + return `sed -i '' 's/
/
/g' ./node_modules/snackbarjs/src/snackbar.js`; + default: + return `sed -i 's/
/
/g' ./node_modules/snackbarjs/src/snackbar.js`; + } + }, + stdout: false + }, + fixJimpModule: { + command: function () { + switch (process.platform) { + case "darwin": + // Space added before comma to prevent multiple modifications + return `sed -i '' 's/"es\\/index.js",/"es\\/index.js" ,\\n "type": "module",/' ./node_modules/jimp/package.json`; + default: + return `sed -i 's/"es\\/index.js",/"es\\/index.js" ,\\n "type": "module",/' ./node_modules/jimp/package.json`; + } + }, + stdout: false } }, - execute: { - test: "build/test/index.js" - }, }); - }; diff --git a/README.md b/README.md index 53b25c53..5549bda2 100755 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ # CyberChef +[![](https://github.com/gchq/CyberChef/workflows/Master%20Build,%20Test%20&%20Deploy/badge.svg)](https://github.com/gchq/CyberChef/actions?query=workflow%3A%22Master+Build%2C+Test+%26+Deploy%22) +[![npm](https://img.shields.io/npm/v/cyberchef.svg)](https://www.npmjs.com/package/cyberchef) +[![](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 and 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 @@ -14,15 +20,31 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur [A live demo can be found here][1] - have fun! +## Containers + +If you would like to try out CyberChef locally you can either build it yourself: + +```bash +docker build --tag cyberchef --ulimit nofile=10000 . +docker run -it -p 8080:80 cyberchef +``` + +Or you can use our image directly: + +```bash +docker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest +``` + +This image is built and published through our [GitHub Workflows](.github/workflows/releases.yml) ## How it works 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: @@ -30,60 +52,86 @@ 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 2GB can be dragged over the input box to load them directly into the browser. - Auto Bake - - Whenever you modify the input or the recipe, CyberChef will automatically “bake” for you and produce the output immediately. + - 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 that 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 2GB are supported (depending on your browser), however, some operations may take a very long time to run over this much data. - CyberChef is entirely client-side - - It should be noted that none of your 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 downloaded and run locally. You can use the link in the top left corner of the app to download a full copy of CyberChef and drop it into a virtual machine, share it with other people, or host it in a closed network. + + +## Deep linking + +By manipulating CyberChef's URL hash, you can change the initial settings with which the page opens. +The format is `https://gchq.github.io/CyberChef/#recipe=Operation()&input=...` + +Supported arguments are `recipe`, `input` (encoded in Base64), and `theme`. ## Browser support -CyberChef is built to support Google Chrome 40+, Mozilla Firefox 35+ and Microsoft Edge 14+. +CyberChef is built to support + + - Google Chrome 50+ + - Mozilla Firefox 38+ + + +## Node.js support + +CyberChef is built to fully support Node.js `v16`. For more information, see the ["Node API" wiki page](https://github.com/gchq/CyberChef/wiki/Node-API) ## 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! The quickstart script 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 ["Contributing" wiki page](https://github.com/gchq/CyberChef/wiki/Contributing). - - 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 -CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/licenses/LICENSE-2.0) and is covered by [Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/copyright-and-re-use/crown-copyright/). +CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/licenses/LICENSE-2.0) and is covered by [Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/). [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/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..c934c934 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,26 @@ +# Security Policy + +## Supported Versions + +CyberChef is supported on a best endeavours basis. Patches will be applied to +the latest version rather than retroactively to older versions. To ensure you +are using the most secure version of CyberChef, please make sure you have the +[latest release](https://github.com/gchq/CyberChef/releases/latest). The +official [live demo](https://gchq.github.io/CyberChef/) is always up to date. + +## Reporting a Vulnerability + +In most scenarios, the most appropriate way to report a vulnerability is to +[raise a new issue](https://github.com/gchq/CyberChef/issues/new/choose) +describing the problem in as much detail as possible, ideally with examples. +This will obviously be public. If you feel that the vulnerability is +significant enough to warrant a private disclosure, please email +[oss@gchq.gov.uk](mailto:oss@gchq.gov.uk) and +[n1474335@gmail.com](mailto:n1474335@gmail.com). + +Disclosures of vulnerabilities in CyberChef are always welcomed. Whilst we aim +to write clean and secure code free from bugs, we recognise that this is an open +source project written by analysts in their spare time, relying on dozens of +open source libraries that are modified and updated on a regular basis. We hope +that the community will continue to support us as we endeavour to maintain and +develop this tool together. diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 00000000..deab9108 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,27 @@ +module.exports = function(api) { + api.cache.forever(); + + return { + "presets": [ + ["@babel/preset-env", { + "modules": false, + "useBuiltIns": "entry", + "corejs": 3 + }] + ], + "plugins": [ + "dynamic-import-node", + "@babel/plugin-syntax-import-assertions", + [ + "babel-plugin-transform-builtin-extend", { + "globals": ["Error"] + } + ], + [ + "@babel/plugin-transform-runtime", { + "regenerator": true + } + ] + ] + }; +}; diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100755 index d67a4081..00000000 Binary files a/docs/favicon.ico and /dev/null differ diff --git a/docs/jsdoc.conf.json b/docs/jsdoc.conf.json deleted file mode 100755 index ad9ad72a..00000000 --- a/docs/jsdoc.conf.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "tags": { - "allowUnknownTags": true - }, - "plugins": ["plugins/markdown"], - "templates": { - "systemName": "CyberChef", - "footer": "", - "copyright": "© Crown Copyright 2016", - "navType": "inline", - "theme": "cerulean", - "linenums": true, - "collapseSymbols": false, - "inverseNav": true, - "outputSourceFiles": true, - "outputSourcePath": true, - "dateFormat": "ddd MMM Do YYYY", - "sort": false, - "logoFile": "../build/prod/images/cyberchef-32x32.png", - "cleverLinks": false, - "monospaceLinks": false, - "protocol": "html://", - "methodHeadingReturns": false - }, - "markdown": { - "parser": "gfm", - "hardwrap": true - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100755 index 00000000..c0a7222c --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,129 @@ +import babelParser from "@babel/eslint-parser"; +import jsdoc from "eslint-plugin-jsdoc"; +import js from "@eslint/js"; +import globals from "globals"; + +export default [ + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 2022, + parser: babelParser, + parserOptions: { + ecmaVersion: 2022, + ecmaFeatures: { + impliedStrict: true + }, + sourceType: "module", + allowImportExportEverywhere: true + }, + globals: { + ...globals.browser, + ...globals.node, + ...globals.es6, + "$": false, + "jQuery": false, + "log": false, + "app": false, + + "COMPILE_TIME": false, + "COMPILE_MSG": false, + "PKG_VERSION": false + }, + }, + ignores: ["src/core/vendor/**"], + plugins: { + jsdoc + }, + rules: { + // enable additional rules + "no-eval": "error", + "no-implied-eval": "error", + "dot-notation": "error", + "eqeqeq": ["error", "smart"], + "no-caller": "error", + "no-extra-bind": "error", + "no-unused-expressions": "error", + "no-useless-call": "error", + "no-useless-return": "error", + "radix": "warn", + + // modify rules from base configurations + "no-unused-vars": ["error", { + "args": "none", + "vars": "all", + "caughtErrors": "none" + }], + "no-empty": ["error", { + "allowEmptyCatch": true + }], + + // disable rules from base configurations + "no-control-regex": "off", + "require-atomic-updates": "off", + "no-async-promise-executor": "off", + + // stylistic conventions + "brace-style": ["error", "1tbs"], + "space-before-blocks": ["error", "always"], + "block-spacing": "error", + "array-bracket-spacing": "error", + "comma-spacing": "error", + "spaced-comment": ["error", "always", { "exceptions": ["/"] }], + "comma-style": "error", + "computed-property-spacing": "error", + "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 + }], + "linebreak-style": ["error", "unix"], + "quotes": ["error", "double", { + "avoidEscape": true, + "allowTemplateLiterals": true + }], + "camelcase": ["error", { + "properties": "always" + }], + "semi": ["error", "always"], + "unicode-bom": "error", + "jsdoc/require-jsdoc": ["error", { + "require": { + "FunctionDeclaration": true, + "MethodDefinition": true, + "ClassDeclaration": true, + "ArrowFunctionExpression": false + } + }], + "keyword-spacing": ["error", { + "before": true, + "after": true + }], + "no-multiple-empty-lines": ["warn", { + "max": 2, + "maxEOF": 1, + "maxBOF": 0 + }], + "no-whitespace-before-property": "error", + "operator-linebreak": ["error", "after"], + "space-in-parens": "error", + "no-var": "error", + "prefer-const": "error", + "no-console": "error" + }, + }, + // File-pattern specific overrides + { + files: ["tests/**/*"], + rules: { + "no-unused-expressions": "off", + "no-console": "off" + } + }, +]; diff --git a/nightwatch.json b/nightwatch.json new file mode 100644 index 00000000..95359f44 --- /dev/null +++ b/nightwatch.json @@ -0,0 +1,32 @@ +{ + "src_folders": ["tests/browser"], + "exclude": ["tests/browser/browserUtils.js"], + "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": "tests/browser/output" + }, + "desiredCapabilities": { + "browserName": "chrome" + }, + "enable_fail_fast": true + }, + + "dev": { + "launch_url": "http://localhost:8080" + }, + + "prod": { + "launch_url": "http://localhost:8000/index.html" + } + + } +} + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..ef2da3f0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,19371 @@ +{ + "name": "cyberchef", + "version": "10.19.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cyberchef", + "version": "10.19.4", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@astronautlabs/amf": "^0.0.6", + "@babel/polyfill": "^7.12.1", + "@blu3r4y/lzma": "^2.3.3", + "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", + "@xmldom/xmldom": "^0.8.10", + "argon2-browser": "^1.18.0", + "arrive": "^2.4.1", + "avsc": "^5.7.7", + "bcryptjs": "^2.4.3", + "bignumber.js": "^9.1.2", + "blakejs": "^1.2.1", + "bootstrap": "4.6.2", + "bootstrap-colorpicker": "^3.4.0", + "bootstrap-material-design": "^4.1.3", + "browserify-zlib": "^0.2.0", + "bson": "^4.7.2", + "buffer": "^6.0.3", + "cbor": "9.0.2", + "chi-squared": "^1.1.0", + "codepage": "^1.15.0", + "crypto-api": "^0.8.5", + "crypto-browserify": "^3.12.0", + "crypto-js": "^4.2.0", + "ctph.js": "0.0.5", + "d3": "7.9.0", + "d3-hexbin": "^0.2.2", + "diff": "^5.2.0", + "dompurify": "^3.2.5", + "es6-promisify": "^7.0.0", + "escodegen": "^2.1.0", + "esprima": "^4.0.1", + "exif-parser": "^0.1.12", + "fernet": "^0.4.0", + "file-saver": "^2.0.5", + "flat": "^6.0.1", + "geodesy": "1.1.3", + "highlight.js": "^11.9.0", + "ieee754": "^1.2.1", + "jimp": "^0.22.12", + "jq-web": "^0.5.1", + "jquery": "3.7.1", + "js-sha3": "^0.9.3", + "jsesc": "^3.0.2", + "json5": "^2.2.3", + "jsonpath-plus": "^9.0.0", + "jsonwebtoken": "8.5.1", + "jsqr": "^1.4.0", + "jsrsasign": "^11.1.0", + "kbpgp": "2.1.15", + "libbzip2-wasm": "0.0.4", + "libyara-wasm": "^1.2.1", + "lodash": "^4.17.21", + "loglevel": "^1.9.1", + "loglevel-message-prefix": "^3.0.0", + "lz-string": "^1.5.0", + "lz4js": "^0.2.0", + "markdown-it": "^14.1.0", + "moment": "^2.30.1", + "moment-timezone": "^0.5.45", + "ngeohash": "^0.6.3", + "node-forge": "^1.3.1", + "node-md6": "^0.1.0", + "nodom": "^2.4.0", + "notepack.io": "^3.0.1", + "ntlm": "^0.1.3", + "nwmatcher": "^1.4.4", + "otpauth": "9.3.6", + "path": "^0.12.7", + "popper.js": "^1.16.1", + "process": "^0.11.10", + "protobufjs": "^7.3.1", + "qr-image": "^3.2.0", + "reflect-metadata": "^0.2.2", + "rison": "^0.1.1", + "scryptsy": "^2.1.0", + "snackbarjs": "^1.1.0", + "sortablejs": "^1.15.2", + "split.js": "^1.6.5", + "ssdeep.js": "0.0.3", + "stream-browserify": "^3.0.0", + "tesseract.js": "5.1.0", + "ua-parser-js": "^1.0.38", + "unorm": "^1.6.0", + "utf8": "^3.0.0", + "vkbeautify": "^0.99.3", + "xpath": "0.0.34", + "xregexp": "^5.1.1", + "zlibjs": "^0.3.1" + }, + "devDependencies": { + "@babel/core": "^7.24.7", + "@babel/eslint-parser": "^7.24.7", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/runtime": "^7.24.7", + "@codemirror/commands": "^6.6.0", + "@codemirror/language": "^6.10.2", + "@codemirror/search": "^6.5.6", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.28.0", + "autoprefixer": "^10.4.19", + "babel-loader": "^9.1.3", + "babel-plugin-dynamic-import-node": "^2.3.3", + "babel-plugin-transform-builtin-extend": "1.1.2", + "base64-loader": "^1.0.0", + "chromedriver": "^130.0.0", + "cli-progress": "^3.12.0", + "colors": "^1.4.0", + "compression-webpack-plugin": "^11.1.0", + "copy-webpack-plugin": "^12.0.2", + "core-js": "^3.37.1", + "cspell": "^8.17.3", + "css-loader": "7.1.2", + "eslint": "^9.4.0", + "eslint-plugin-jsdoc": "^48.2.9", + "globals": "^15.4.0", + "grunt": "^1.6.1", + "grunt-chmod": "~1.1.1", + "grunt-concurrent": "^3.0.0", + "grunt-contrib-clean": "~2.0.1", + "grunt-contrib-connect": "^4.0.0", + "grunt-contrib-copy": "~1.0.0", + "grunt-contrib-watch": "^1.1.0", + "grunt-eslint": "^25.0.0", + "grunt-exec": "~3.0.0", + "grunt-webpack": "^6.0.0", + "grunt-zip": "^1.0.0", + "html-webpack-plugin": "^5.6.0", + "imports-loader": "^5.0.0", + "mini-css-extract-plugin": "2.9.0", + "modify-source-webpack-plugin": "^4.1.0", + "nightwatch": "^3.6.3", + "postcss": "^8.4.38", + "postcss-css-variables": "^0.19.0", + "postcss-import": "^16.1.0", + "postcss-loader": "^8.1.1", + "prompt": "^1.3.0", + "sitemap": "^8.0.0", + "terser": "^5.31.1", + "webpack": "^5.91.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "5.0.4", + "webpack-node-externals": "^3.0.0", + "worker-loader": "^3.0.8" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.3.tgz", + "integrity": "sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@astronautlabs/amf": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@astronautlabs/amf/-/amf-0.0.6.tgz", + "integrity": "sha512-cJgbXW45TIDLQf2hiHqDoRfmeRy5u9Z4npr7sZfBThvbp5cbqDieTWaJTu91cUAj35/u87OHZijLTbMO18ZIow==", + "license": "MIT", + "dependencies": { + "@astronautlabs/bitstream": "^4.0.0" + }, + "engines": { + "node": "^14" + } + }, + "node_modules/@astronautlabs/amf/node_modules/@astronautlabs/bitstream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@astronautlabs/bitstream/-/bitstream-4.2.2.tgz", + "integrity": "sha512-/D18Aua0Er95TkulkVBXK5oweh55tXlgpug7g2cKdUvVBT91k2YD9n1RYnbrfaaHIN+KVDaBIrf3j9dS3DhzXw==", + "license": "MIT", + "peerDependencies": { + "reflect-metadata": "^0.1.13" + } + }, + "node_modules/@astronautlabs/amf/node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", + "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.26.5.tgz", + "integrity": "sha512-Kkm8C8uxI842AwQADxl0GbcG1rupELYLShazYEZO/2DYjhyWXJIOUVOE3tBYm6JXzUCNJOZEzqc4rCW/jsEQYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz", + "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", + "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", + "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/polyfill": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz", + "integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==", + "deprecated": "🚨 This package has been deprecated in favor of separate inclusion of a polyfill and regenerator-runtime (when needed). See the @babel/polyfill docs (https://babeljs.io/docs/en/babel-polyfill) for more information.", + "license": "MIT", + "dependencies": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + } + }, + "node_modules/@babel/polyfill/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/@babel/preset-env": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.38.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz", + "integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==", + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.5.tgz", + "integrity": "sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.5", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.5", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", + "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bazel/runfiles": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.3.1.tgz", + "integrity": "sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@blu3r4y/lzma": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@blu3r4y/lzma/-/lzma-2.3.3.tgz", + "integrity": "sha512-2ckRSsYewLAgq/s8tUW3o5gurtCNYga1f9l0egV4QlT8hgVEilQHRt18s+behmPL2M/BPBxUINaOz67u++r0wA==", + "license": "MIT", + "bin": { + "lzma.js": "bin/lzma.js" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.0.tgz", + "integrity": "sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.10.8", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.8.tgz", + "integrity": "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.8", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz", + "integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.1.tgz", + "integrity": "sha512-3rA9lcwciEB47ZevqvD8qgbzhM9qMb8vCcQCNmDfVRPQG4JT9mSb0Jg8H7YjKGGQcFnLN323fj9jdnG59Kx6bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.36.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.2.tgz", + "integrity": "sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspell/cspell-bundled-dicts": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.17.3.tgz", + "integrity": "sha512-6uOF726o3JnExAUKM20OJJXZo+Qf9Jt64nkVwnVXx7Upqr5I9Pb1npYPEAIpUA03SnWYmKwUIqhAmkwrN+bLPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-ada": "^4.1.0", + "@cspell/dict-al": "^1.1.0", + "@cspell/dict-aws": "^4.0.9", + "@cspell/dict-bash": "^4.2.0", + "@cspell/dict-companies": "^3.1.13", + "@cspell/dict-cpp": "^6.0.3", + "@cspell/dict-cryptocurrencies": "^5.0.4", + "@cspell/dict-csharp": "^4.0.6", + "@cspell/dict-css": "^4.0.17", + "@cspell/dict-dart": "^2.3.0", + "@cspell/dict-data-science": "^2.0.7", + "@cspell/dict-django": "^4.1.4", + "@cspell/dict-docker": "^1.1.12", + "@cspell/dict-dotnet": "^5.0.9", + "@cspell/dict-elixir": "^4.0.7", + "@cspell/dict-en_us": "^4.3.30", + "@cspell/dict-en-common-misspellings": "^2.0.9", + "@cspell/dict-en-gb": "1.1.33", + "@cspell/dict-filetypes": "^3.0.10", + "@cspell/dict-flutter": "^1.1.0", + "@cspell/dict-fonts": "^4.0.4", + "@cspell/dict-fsharp": "^1.1.0", + "@cspell/dict-fullstack": "^3.2.3", + "@cspell/dict-gaming-terms": "^1.1.0", + "@cspell/dict-git": "^3.0.4", + "@cspell/dict-golang": "^6.0.18", + "@cspell/dict-google": "^1.0.8", + "@cspell/dict-haskell": "^4.0.5", + "@cspell/dict-html": "^4.0.11", + "@cspell/dict-html-symbol-entities": "^4.0.3", + "@cspell/dict-java": "^5.0.11", + "@cspell/dict-julia": "^1.1.0", + "@cspell/dict-k8s": "^1.0.10", + "@cspell/dict-kotlin": "^1.1.0", + "@cspell/dict-latex": "^4.0.3", + "@cspell/dict-lorem-ipsum": "^4.0.4", + "@cspell/dict-lua": "^4.0.7", + "@cspell/dict-makefile": "^1.0.4", + "@cspell/dict-markdown": "^2.0.9", + "@cspell/dict-monkeyc": "^1.0.10", + "@cspell/dict-node": "^5.0.6", + "@cspell/dict-npm": "^5.1.24", + "@cspell/dict-php": "^4.0.14", + "@cspell/dict-powershell": "^5.0.14", + "@cspell/dict-public-licenses": "^2.0.13", + "@cspell/dict-python": "^4.2.15", + "@cspell/dict-r": "^2.1.0", + "@cspell/dict-ruby": "^5.0.7", + "@cspell/dict-rust": "^4.0.11", + "@cspell/dict-scala": "^5.0.7", + "@cspell/dict-shell": "^1.1.0", + "@cspell/dict-software-terms": "^4.2.4", + "@cspell/dict-sql": "^2.2.0", + "@cspell/dict-svelte": "^1.0.6", + "@cspell/dict-swift": "^2.0.5", + "@cspell/dict-terraform": "^1.1.0", + "@cspell/dict-typescript": "^3.2.0", + "@cspell/dict-vue": "^3.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-json-reporter": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.17.3.tgz", + "integrity": "sha512-RWSfyHOin/d9CqLjz00JMvPkag3yUSsQZr6G9BnCT5cMEO/ws8wQZzA54CNj/LAOccbknTX65SSroPPAtxs56w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "8.17.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-pipe": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.17.3.tgz", + "integrity": "sha512-DqqSWKt9NLWPGloYxZTpzUhgdW8ObMkZmOOF6TyqpJ4IbckEct8ULgskNorTNRlmmjLniaNgvg6JSHuYO3Urxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-resolver": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.17.3.tgz", + "integrity": "sha512-yQlVaIsWiax6RRuuacZs++kl6Y9rwH9ZkVlsG9fhdeCJ5Xf3WCW+vmX1chzhhKDzRr8CF9fsvb1uagd/5/bBYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-service-bus": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.17.3.tgz", + "integrity": "sha512-CC3nob/Kbuesz5WTW+LjAHnDFXJrA49pW5ckmbufJxNnoAk7EJez/qr7/ELMTf6Fl3A5xZ776Lhq7738Hy/fmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-types": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.17.3.tgz", + "integrity": "sha512-ozgeuSioX9z2wtlargfgdw3LKwDFAfm8gxu+xwNREvXiLsevb+lb7ZlY5/ay+MahqR5Hfs7XzYzBLTKL/ldn9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/dict-ada": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.1.0.tgz", + "integrity": "sha512-7SvmhmX170gyPd+uHXrfmqJBY5qLcCX8kTGURPVeGxmt8XNXT75uu9rnZO+jwrfuU2EimNoArdVy5GZRGljGNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-al": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-al/-/dict-al-1.1.0.tgz", + "integrity": "sha512-PtNI1KLmYkELYltbzuoztBxfi11jcE9HXBHCpID2lou/J4VMYKJPNqe4ZjVzSI9NYbMnMnyG3gkbhIdx66VSXg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-aws": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.9.tgz", + "integrity": "sha512-bDYdnnJGwSkIZ4gzrauu7qzOs/ZAY/FnU4k11LgdMI8BhwMfsbsy2EI1iS+sD/BI5ZnNT9kU5YR3WADeNOmhRg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-bash": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.2.0.tgz", + "integrity": "sha512-HOyOS+4AbCArZHs/wMxX/apRkjxg6NDWdt0jF9i9XkvJQUltMwEhyA2TWYjQ0kssBsnof+9amax2lhiZnh3kCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-shell": "1.1.0" + } + }, + "node_modules/@cspell/dict-companies": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.1.14.tgz", + "integrity": "sha512-iqo1Ce4L7h0l0GFSicm2wCLtfuymwkvgFGhmu9UHyuIcTbdFkDErH+m6lH3Ed+QuskJlpQ9dM7puMIGqUlVERw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-cpp": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.3.tgz", + "integrity": "sha512-OFrVXdxCeGKnon36Pe3yFjBuY4kzzEwWFf3vDz+cJTodZDkjFkBifQeTtt5YfimgF8cfAJZXkBCsxjipAgmAiw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-cryptocurrencies": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.4.tgz", + "integrity": "sha512-6iFu7Abu+4Mgqq08YhTKHfH59mpMpGTwdzDB2Y8bbgiwnGFCeoiSkVkgLn1Kel2++hYcZ8vsAW/MJS9oXxuMag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-csharp": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.6.tgz", + "integrity": "sha512-w/+YsqOknjQXmIlWDRmkW+BHBPJZ/XDrfJhZRQnp0wzpPOGml7W0q1iae65P2AFRtTdPKYmvSz7AL5ZRkCnSIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-css": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.17.tgz", + "integrity": "sha512-2EisRLHk6X/PdicybwlajLGKF5aJf4xnX2uuG5lexuYKt05xV/J/OiBADmi8q9obhxf1nesrMQbqAt+6CsHo/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-dart": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.0.tgz", + "integrity": "sha512-1aY90lAicek8vYczGPDKr70pQSTQHwMFLbmWKTAI6iavmb1fisJBS1oTmMOKE4ximDf86MvVN6Ucwx3u/8HqLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-data-science": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.7.tgz", + "integrity": "sha512-XhAkK+nSW6zmrnWzusmZ1BpYLc62AWYHZc2p17u4nE2Z9XG5DleG55PCZxXQTKz90pmwlhFM9AfpkJsYaBWATA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-django": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.4.tgz", + "integrity": "sha512-fX38eUoPvytZ/2GA+g4bbdUtCMGNFSLbdJJPKX2vbewIQGfgSFJKY56vvcHJKAvw7FopjvgyS/98Ta9WN1gckg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-docker": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.12.tgz", + "integrity": "sha512-6d25ZPBnYZaT9D9An/x6g/4mk542R8bR3ipnby3QFCxnfdd6xaWiTcwDPsCgwN2aQZIQ1jX/fil9KmBEqIK/qA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-dotnet": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.9.tgz", + "integrity": "sha512-JGD6RJW5sHtO5lfiJl11a5DpPN6eKSz5M1YBa1I76j4dDOIqgZB6rQexlDlK1DH9B06X4GdDQwdBfnpAB0r2uQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-elixir": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.7.tgz", + "integrity": "sha512-MAUqlMw73mgtSdxvbAvyRlvc3bYnrDqXQrx5K9SwW8F7fRYf9V4vWYFULh+UWwwkqkhX9w03ZqFYRTdkFku6uA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-en_us": { + "version": "4.3.31", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.3.31.tgz", + "integrity": "sha512-MDc+1B0aFwQONS0JZ6w7ks2KFGkUcaNTFJ8kI6GHvFRmEl3zP5NJDwFEXFsoEdLDb86j2myauSWMJXR3JFuwbA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-en-common-misspellings": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.0.9.tgz", + "integrity": "sha512-O/jAr1VNtuyCFckbTmpeEf43ZFWVD9cJFvWaA6rO2IVmLirJViHWJUyBZOuQcesSplzEIw80MAYmnK06/MDWXQ==", + "dev": true, + "license": "CC BY-SA 4.0" + }, + "node_modules/@cspell/dict-en-gb": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb/-/dict-en-gb-1.1.33.tgz", + "integrity": "sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-filetypes": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.11.tgz", + "integrity": "sha512-bBtCHZLo7MiSRUqx5KEiPdGOmXIlDGY+L7SJEtRWZENpAKE+96rT7hj+TUUYWBbCzheqHr0OXZJFEKDgsG/uZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-flutter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.1.0.tgz", + "integrity": "sha512-3zDeS7zc2p8tr9YH9tfbOEYfopKY/srNsAa+kE3rfBTtQERAZeOhe5yxrnTPoufctXLyuUtcGMUTpxr3dO0iaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fonts": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.4.tgz", + "integrity": "sha512-cHFho4hjojBcHl6qxidl9CvUb492IuSk7xIf2G2wJzcHwGaCFa2o3gRcxmIg1j62guetAeDDFELizDaJlVRIOg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fsharp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.1.0.tgz", + "integrity": "sha512-oguWmHhGzgbgbEIBKtgKPrFSVAFtvGHaQS0oj+vacZqMObwkapcTGu7iwf4V3Bc2T3caf0QE6f6rQfIJFIAVsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-fullstack": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.4.tgz", + "integrity": "sha512-JRRvaOLBZ13BO9sP395W+06tyO1Jy/87aFlKe9xQiCWMiwpCo2kGq0xBGq0PDWe253lYLs+GKrNmLU0fSxrObg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-gaming-terms": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.1.0.tgz", + "integrity": "sha512-46AnDs9XkgJ2f1Sqol1WgfJ8gOqp60fojpc9Wxch7x+BA63g4JfMV5/M5x0sI0TLlLY8EBSglcr8wQF/7C80AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-git": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.4.tgz", + "integrity": "sha512-C44M+m56rYn6QCsLbiKiedyPTMZxlDdEYAsPwwlL5bhMDDzXZ3Ic8OCQIhMbiunhCOJJT+er4URmOmM+sllnjg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-golang": { + "version": "6.0.18", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.18.tgz", + "integrity": "sha512-Mt+7NwfodDwUk7423DdaQa0YaA+4UoV3XSxQwZioqjpFBCuxfvvv4l80MxCTAAbK6duGj0uHbGTwpv8fyKYPKg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-google": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.8.tgz", + "integrity": "sha512-BnMHgcEeaLyloPmBs8phCqprI+4r2Jb8rni011A8hE+7FNk7FmLE3kiwxLFrcZnnb7eqM0agW4zUaNoB0P+z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-haskell": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.5.tgz", + "integrity": "sha512-s4BG/4tlj2pPM9Ha7IZYMhUujXDnI0Eq1+38UTTCpatYLbQqDwRFf2KNPLRqkroU+a44yTUAe0rkkKbwy4yRtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-html": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.11.tgz", + "integrity": "sha512-QR3b/PB972SRQ2xICR1Nw/M44IJ6rjypwzA4jn+GH8ydjAX9acFNfc+hLZVyNe0FqsE90Gw3evLCOIF0vy1vQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-html-symbol-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.3.tgz", + "integrity": "sha512-aABXX7dMLNFdSE8aY844X4+hvfK7977sOWgZXo4MTGAmOzR8524fjbJPswIBK7GaD3+SgFZ2yP2o0CFvXDGF+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-java": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.11.tgz", + "integrity": "sha512-T4t/1JqeH33Raa/QK/eQe26FE17eUCtWu+JsYcTLkQTci2dk1DfcIKo8YVHvZXBnuM43ATns9Xs0s+AlqDeH7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-julia": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.1.0.tgz", + "integrity": "sha512-CPUiesiXwy3HRoBR3joUseTZ9giFPCydSKu2rkh6I2nVjXnl5vFHzOMLXpbF4HQ1tH2CNfnDbUndxD+I+7eL9w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-k8s": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.10.tgz", + "integrity": "sha512-313haTrX9prep1yWO7N6Xw4D6tvUJ0Xsx+YhCP+5YrrcIKoEw5Rtlg8R4PPzLqe6zibw6aJ+Eqq+y76Vx5BZkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-kotlin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-kotlin/-/dict-kotlin-1.1.0.tgz", + "integrity": "sha512-vySaVw6atY7LdwvstQowSbdxjXG6jDhjkWVWSjg1XsUckyzH1JRHXe9VahZz1i7dpoFEUOWQrhIe5B9482UyJQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-latex": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.3.tgz", + "integrity": "sha512-2KXBt9fSpymYHxHfvhUpjUFyzrmN4c4P8mwIzweLyvqntBT3k0YGZJSriOdjfUjwSygrfEwiuPI1EMrvgrOMJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-lorem-ipsum": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.4.tgz", + "integrity": "sha512-+4f7vtY4dp2b9N5fn0za/UR0kwFq2zDtA62JCbWHbpjvO9wukkbl4rZg4YudHbBgkl73HRnXFgCiwNhdIA1JPw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-lua": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.7.tgz", + "integrity": "sha512-Wbr7YSQw+cLHhTYTKV6cAljgMgcY+EUAxVIZW3ljKswEe4OLxnVJ7lPqZF5JKjlXdgCjbPSimsHqyAbC5pQN/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-makefile": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.4.tgz", + "integrity": "sha512-E4hG/c0ekPqUBvlkrVvzSoAA+SsDA9bLi4xSV3AXHTVru7Y2bVVGMPtpfF+fI3zTkww/jwinprcU1LSohI3ylw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-markdown": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.9.tgz", + "integrity": "sha512-j2e6Eg18BlTb1mMP1DkyRFMM/FLS7qiZjltpURzDckB57zDZbUyskOFdl4VX7jItZZEeY0fe22bSPOycgS1Z5A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@cspell/dict-css": "^4.0.17", + "@cspell/dict-html": "^4.0.11", + "@cspell/dict-html-symbol-entities": "^4.0.3", + "@cspell/dict-typescript": "^3.2.0" + } + }, + "node_modules/@cspell/dict-monkeyc": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.10.tgz", + "integrity": "sha512-7RTGyKsTIIVqzbvOtAu6Z/lwwxjGRtY5RkKPlXKHEoEAgIXwfDxb5EkVwzGQwQr8hF/D3HrdYbRT8MFBfsueZw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-node": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.6.tgz", + "integrity": "sha512-CEbhPCpxGvRNByGolSBTrXXW2rJA4bGqZuTx1KKO85mwR6aadeOmUE7xf/8jiCkXSy+qvr9aJeh+jlfXcsrziQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-npm": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.1.26.tgz", + "integrity": "sha512-JU0/9P4nLPPC3Py+sF42tUKm9J4KAvwXaJubp2a4QwhCPzFVlOJTP2tTseFlLbdZn23d61pt0hZ+Jhyy7N76Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-php": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.14.tgz", + "integrity": "sha512-7zur8pyncYZglxNmqsRycOZ6inpDoVd4yFfz1pQRe5xaRWMiK3Km4n0/X/1YMWhh3e3Sl/fQg5Axb2hlN68t1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-powershell": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.14.tgz", + "integrity": "sha512-ktjjvtkIUIYmj/SoGBYbr3/+CsRGNXGpvVANrY0wlm/IoGlGywhoTUDYN0IsGwI2b8Vktx3DZmQkfb3Wo38jBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-public-licenses": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.13.tgz", + "integrity": "sha512-1Wdp/XH1ieim7CadXYE7YLnUlW0pULEjVl9WEeziZw3EKCAw8ZI8Ih44m4bEa5VNBLnuP5TfqC4iDautAleQzQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-python": { + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.15.tgz", + "integrity": "sha512-VNXhj0Eh+hdHN89MgyaoSAexBQKmYtJaMhucbMI7XmBs4pf8fuFFN3xugk51/A4TZJr8+RImdFFsGMOw+I4bDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/dict-data-science": "^2.0.7" + } + }, + "node_modules/@cspell/dict-r": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.1.0.tgz", + "integrity": "sha512-k2512wgGG0lTpTYH9w5Wwco+lAMf3Vz7mhqV8+OnalIE7muA0RSuD9tWBjiqLcX8zPvEJr4LdgxVju8Gk3OKyA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-ruby": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.7.tgz", + "integrity": "sha512-4/d0hcoPzi5Alk0FmcyqlzFW9lQnZh9j07MJzPcyVO62nYJJAGKaPZL2o4qHeCS/od/ctJC5AHRdoUm0ktsw6Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-rust": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.11.tgz", + "integrity": "sha512-OGWDEEzm8HlkSmtD8fV3pEcO2XBpzG2XYjgMCJCRwb2gRKvR+XIm6Dlhs04N/K2kU+iH8bvrqNpM8fS/BFl0uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-scala": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.7.tgz", + "integrity": "sha512-yatpSDW/GwulzO3t7hB5peoWwzo+Y3qTc0pO24Jf6f88jsEeKmDeKkfgPbYuCgbE4jisGR4vs4+jfQZDIYmXPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-shell": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-shell/-/dict-shell-1.1.0.tgz", + "integrity": "sha512-D/xHXX7T37BJxNRf5JJHsvziFDvh23IF/KvkZXNSh8VqcRdod3BAz9VGHZf6VDqcZXr1VRqIYR3mQ8DSvs3AVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-software-terms": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-4.2.5.tgz", + "integrity": "sha512-CaRzkWti3AgcXoxuRcMijaNG7YUk/MH1rHjB8VX34v3UdCxXXeqvRyElRKnxhFeVLB/robb2UdShqh/CpskxRg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-sql": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.2.0.tgz", + "integrity": "sha512-MUop+d1AHSzXpBvQgQkCiok8Ejzb+nrzyG16E8TvKL2MQeDwnIvMe3bv90eukP6E1HWb+V/MA/4pnq0pcJWKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-svelte": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.6.tgz", + "integrity": "sha512-8LAJHSBdwHCoKCSy72PXXzz7ulGROD0rP1CQ0StOqXOOlTUeSFaJJlxNYjlONgd2c62XBQiN2wgLhtPN+1Zv7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-swift": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.5.tgz", + "integrity": "sha512-3lGzDCwUmnrfckv3Q4eVSW3sK3cHqqHlPprFJZD4nAqt23ot7fic5ALR7J4joHpvDz36nHX34TgcbZNNZOC/JA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-terraform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.1.0.tgz", + "integrity": "sha512-G55pcUUxeXAhejstmD35B47SkFd4uqCQimc+CMgq8Nx0dr03guL2iMsz8faRWQGkCnGimX8S91rbOhDv9p/heg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-typescript": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.0.tgz", + "integrity": "sha512-Pk3zNePLT8qg51l0M4g1ISowYAEGxTuNfZlgkU5SvHa9Cu7x/BWoyYq9Fvc3kAyoisCjRPyvWF4uRYrPitPDFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dict-vue": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.4.tgz", + "integrity": "sha512-0dPtI0lwHcAgSiQFx8CzvqjdoXROcH+1LyqgROCpBgppommWpVhbQ0eubnKotFEXgpUCONVkeZJ6Ql8NbTEu+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspell/dynamic-import": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.17.3.tgz", + "integrity": "sha512-Kg6IJhGHPv+9OxpxaXUpcqgnHEOhMLRWHLyx7FADZ+CJyO4AVeWQfhpTRM6KXhzIl7dPlLG1g8JAQxaoy88KTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "8.17.3", + "import-meta-resolve": "^4.1.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@cspell/filetypes": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-8.17.3.tgz", + "integrity": "sha512-UFqRmJPccOSo+RYP/jZ4cr0s7ni37GrvnNAg1H/qIIxfmBYsexTAmsNzMqxp1M31NeI1Cx3LL7PspPMT0ms+7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/strong-weak-map": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.17.3.tgz", + "integrity": "sha512-l/CaFc3CITI/dC+whEBZ05Om0KXR3V2whhVOWOBPIqA5lCjWAyvWWvmFD+CxWd0Hs6Qcb/YDnMyJW14aioXN4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/url": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-8.17.3.tgz", + "integrity": "sha512-gcsCz8g0qY94C8RXiAlUH/89n84Q9RSptP91XrvnLOT+Xva9Aibd7ywd5k9ameuf8Nagyl0ezB1MInZ30S9SRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", + "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.1.tgz", + "integrity": "sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz", + "integrity": "sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.1", + "@csstools/css-calc": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", + "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.10.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jimp/bmp": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz", + "integrity": "sha512-aeI64HD0npropd+AR76MCcvvRaa+Qck6loCOS03CkkxGHN5/r336qTM5HPUdHKMDOGzqknuVPA8+kK1t03z12g==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "bmp-js": "^0.1.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/core": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.12.tgz", + "integrity": "sha512-l0RR0dOPyzMKfjUW1uebzueFEDtCOj9fN6pyTYWWOM/VS4BciXQ1VVrJs8pO3kycGYZxncRKhCoygbNr8eEZQA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "exif-parser": "^0.1.12", + "file-type": "^16.5.4", + "isomorphic-fetch": "^3.0.0", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.6.0" + } + }, + "node_modules/@jimp/core/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/@jimp/custom": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz", + "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==", + "license": "MIT", + "dependencies": { + "@jimp/core": "^0.22.12" + } + }, + "node_modules/@jimp/gif": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.12.tgz", + "integrity": "sha512-y6BFTJgch9mbor2H234VSjd9iwAhaNf/t3US5qpYIs0TSbAvM02Fbc28IaDETj9+4YB4676sz4RcN/zwhfu1pg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "gifwrap": "^0.10.1", + "omggif": "^1.0.9" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/jpeg": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.12.tgz", + "integrity": "sha512-Rq26XC/uQWaQKyb/5lksCTCxXhtY01NJeBN+dQv5yNYedN0i7iYu+fXEoRsfaJ8xZzjoANH8sns7rVP4GE7d/Q==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "jpeg-js": "^0.4.4" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz", + "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz", + "integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.12.tgz", + "integrity": "sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz", + "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "tinycolor2": "^1.6.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.12.tgz", + "integrity": "sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQ==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.12.tgz", + "integrity": "sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/W56+4nJ1brhSd233HBJCo01BXA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz", + "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.12.tgz", + "integrity": "sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.12.tgz", + "integrity": "sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.12.tgz", + "integrity": "sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.12.tgz", + "integrity": "sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+inBIb7nlaLLxnvHX8Q==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-rotate": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-gaussian": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.12.tgz", + "integrity": "sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-invert": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.12.tgz", + "integrity": "sha512-N+6rwxdB+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6+xQ==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.12.tgz", + "integrity": "sha512-4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-normalize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.12.tgz", + "integrity": "sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGC+As1FtA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.12.tgz", + "integrity": "sha512-c7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "load-bmfont": "^1.4.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz", + "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz", + "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-scale": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz", + "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-shadow": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.12.tgz", + "integrity": "sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV3+WWwTg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blur": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz", + "integrity": "sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-color": ">=0.8.0", + "@jimp/plugin-resize": ">=0.8.0" + } + }, + "node_modules/@jimp/plugins": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.12.tgz", + "integrity": "sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4/XWILoTZsjTUASQcGoH0TuC0N7xm3ww==", + "license": "MIT", + "dependencies": { + "@jimp/plugin-blit": "^0.22.12", + "@jimp/plugin-blur": "^0.22.12", + "@jimp/plugin-circle": "^0.22.12", + "@jimp/plugin-color": "^0.22.12", + "@jimp/plugin-contain": "^0.22.12", + "@jimp/plugin-cover": "^0.22.12", + "@jimp/plugin-crop": "^0.22.12", + "@jimp/plugin-displace": "^0.22.12", + "@jimp/plugin-dither": "^0.22.12", + "@jimp/plugin-fisheye": "^0.22.12", + "@jimp/plugin-flip": "^0.22.12", + "@jimp/plugin-gaussian": "^0.22.12", + "@jimp/plugin-invert": "^0.22.12", + "@jimp/plugin-mask": "^0.22.12", + "@jimp/plugin-normalize": "^0.22.12", + "@jimp/plugin-print": "^0.22.12", + "@jimp/plugin-resize": "^0.22.12", + "@jimp/plugin-rotate": "^0.22.12", + "@jimp/plugin-scale": "^0.22.12", + "@jimp/plugin-shadow": "^0.22.12", + "@jimp/plugin-threshold": "^0.22.12", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/png": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.12.tgz", + "integrity": "sha512-Mrp6dr3UTn+aLK8ty/dSKELz+Otdz1v4aAXzV5q53UDD2rbB5joKVJ/ChY310B+eRzNxIovbUF1KVrUsYdE8Hg==", + "license": "MIT", + "dependencies": { + "@jimp/utils": "^0.22.12", + "pngjs": "^6.0.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/tiff": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.12.tgz", + "integrity": "sha512-E1LtMh4RyJsoCAfAkBRVSYyZDTtLq9p9LUiiYP0vPtXyxX4BiYBUYihTLSBlCQg5nF2e4OpQg7SPrLdJ66u7jg==", + "license": "MIT", + "dependencies": { + "utif2": "^4.0.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/types": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.12.tgz", + "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA==", + "license": "MIT", + "dependencies": { + "@jimp/bmp": "^0.22.12", + "@jimp/gif": "^0.22.12", + "@jimp/jpeg": "^0.22.12", + "@jimp/png": "^0.22.12", + "@jimp/tiff": "^0.22.12", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", + "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/nice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", + "integrity": "sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.0.1", + "@napi-rs/nice-android-arm64": "1.0.1", + "@napi-rs/nice-darwin-arm64": "1.0.1", + "@napi-rs/nice-darwin-x64": "1.0.1", + "@napi-rs/nice-freebsd-x64": "1.0.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.0.1", + "@napi-rs/nice-linux-arm64-gnu": "1.0.1", + "@napi-rs/nice-linux-arm64-musl": "1.0.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.0.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.0.1", + "@napi-rs/nice-linux-s390x-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-musl": "1.0.1", + "@napi-rs/nice-win32-arm64-msvc": "1.0.1", + "@napi-rs/nice-win32-ia32-msvc": "1.0.1", + "@napi-rs/nice-win32-x64-msvc": "1.0.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz", + "integrity": "sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz", + "integrity": "sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz", + "integrity": "sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz", + "integrity": "sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz", + "integrity": "sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nightwatch/chai": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.3.tgz", + "integrity": "sha512-1OIkOf/7jswOC3/t+Add/HVQO8ib75kz6BVYSNeWGghTlmHUqYEfNJ6vcACbXrn/4v3+9iRlWixuhFkxXkU/RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "1.1.0", + "check-error": "1.0.2", + "deep-eql": "4.0.1", + "loupe": "^2.3.7", + "pathval": "1.1.1", + "type-detect": "4.0.8" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nightwatch/html-reporter-template": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@nightwatch/html-reporter-template/-/html-reporter-template-0.3.0.tgz", + "integrity": "sha512-Mze1z6pmUz2O8N9w1/h3QWz1lzMig45PGyh8PrL9ERs3FxVnIX0RCn37vjZUYiV4wgjZOg41JjdcpriZ3dJxkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nightwatch/nightwatch-inspector": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@nightwatch/nightwatch-inspector/-/nightwatch-inspector-1.0.1.tgz", + "integrity": "sha512-/ax11EOB4eJXT5VioMztcalbCtsNeuFn6icfT75qPLBmkxLvThePSfyGTys+t9AULUR0ug0wMDMiLV1Oy586Fg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "archiver": "^5.3.1" + } + }, + "node_modules/@noble/hashes": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz", + "integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@testim/chrome-version": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.4.tgz", + "integrity": "sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "4.3.20", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", + "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.5.tgz", + "integrity": "sha512-GLZPrd9ckqEBFMcVM/qRFAP0Hg3qiVEojgEFsx/N/zKXsBzbGF6z5FBDpZ0+Xhp1xr+qRZYjfGr1cWHB9oFHSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.9.tgz", + "integrity": "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/selenium-webdriver": { + "version": "4.1.28", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-4.1.28.tgz", + "integrity": "sha512-Au7CXegiS7oapbB16zxPToY4Cjzi9UQQMf3W2ZZM8PigMLTGR3iUAHjPUTddyE5g1SBjT/qpmvlsAQLBfNAdKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ws": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/ws": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@wavesenterprise/crypto-gost-js": { + "version": "2.1.0-RC1", + "resolved": "https://registry.npmjs.org/@wavesenterprise/crypto-gost-js/-/crypto-gost-js-2.1.0-RC1.tgz", + "integrity": "sha512-liAR3/T/vxnEgNUE00Llt+sDvKYqo+sm/L7tqkJorg2ha3SsplOSXAqpH0t4Ya0gRj8qN8zXqO+WwLCxXXuQcw==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@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, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/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, + "license": "ISC" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-to-html": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.7.2.tgz", + "integrity": "sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^2.2.0" + }, + "bin": { + "ansi-to-html": "bin/ansi-to-html" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ansi-to-html/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argon2-browser": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz", + "integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/aria-query/node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aria-query/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/arrive": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/arrive/-/arrive-2.5.2.tgz", + "integrity": "sha512-A9asXhuUR6pgHtwUciZw4FRZDR09ZwVsz1ckEGE6W9gXWrYM9cAkuKzHplpYN0bd/iobg0xqQ9fpRr/GHPsXUw==", + "license": "MIT" + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha512-N+aAxov+CKVS3JuhDIQFr24XvZvwE96Wlhk9dytTg/GmwWoghdOvR8dspx8MVz71O+Y0pA3UPqHF68D6iy8UvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "util": "0.10.3" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/avsc": { + "version": "5.7.7", + "resolved": "https://registry.npmjs.org/avsc/-/avsc-5.7.7.tgz", + "integrity": "sha512-9cYNccliXZDByFsFliVwk5GvTq058Fj513CiR4E60ndDwmuXzTJEp/Bp8FyuRmGyYupLjHLs+JA9/CBoVS4/NQ==", + "license": "MIT", + "engines": { + "node": ">=0.11" + } + }, + "node_modules/axe-core": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "node_modules/babel-code-frame/node_modules/js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.3", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/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": "sha512-foUQxHjMiLNynzJaBKrIoZfoRY22S620MLafGC5UfnBEwqcBODIFcgwqqzHE8Hj590lbUJCQ2uBo9y08+sYIkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.2.0", + "babel-template": "^6.3.0" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "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" + } + }, + "node_modules/babel-traverse/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/babel-traverse/node_modules/globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-traverse/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "license": "MIT", + "bin": { + "babylon": "bin/babylon.js" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64-loader/-/base64-loader-1.0.0.tgz", + "integrity": "sha512-p32+F8dg+ANGx7s8QsZS74ZPHfIycmC2yZcoerzFgbersIYWitPbbF39G6SBx3gyvzyLH5nt1ooocxr0IHuWKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "license": "MIT" + }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", + "license": "MIT" + }, + "node_modules/bn": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bn/-/bn-1.0.5.tgz", + "integrity": "sha512-7TvGbqbZb6lDzsBtNz1VkdXXV0BVmZKPPViPmo2IpvwaryF7P+QKYKACyVkwo2mZPr2CpFiz7EtgPEcc3o/JFQ==", + "license": "BSD-3-Clause" + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "license": "MIT" + }, + "node_modules/body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==", + "dev": true, + "dependencies": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/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, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/bootstrap": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", + "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "jquery": "1.9.1 - 3", + "popper.js": "^1.16.1" + } + }, + "node_modules/bootstrap-colorpicker": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/bootstrap-colorpicker/-/bootstrap-colorpicker-3.4.0.tgz", + "integrity": "sha512-7vA0hvLrat3ptobEKlT9+6amzBUJcDAoh6hJRQY/AD+5dVZYXXf1ivRfrTwmuwiVLJo9rZwM8YB4lYzp6agzqg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "dependencies": { + "bootstrap": ">=4.0", + "jquery": ">=2.2", + "popper.js": ">=1.10" + } + }, + "node_modules/bootstrap-material-design": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/bootstrap-material-design/-/bootstrap-material-design-4.1.3.tgz", + "integrity": "sha512-jOB9io76BKLxwF+IAgObFH9f88ityqOiYsQe9Aa8m88h7sSP3eFL1K8ygb0FsYyIiVm194iodg9i4GMOSlLeRA==", + "license": "MIT" + }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/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, + "license": "ISC" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "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" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bson": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "license": "Apache-2.0", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/bson/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/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": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==", + "dev": true + }, + "node_modules/bzip-deflate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bzip-deflate/-/bzip-deflate-1.0.0.tgz", + "integrity": "sha512-9RMnpiJqMYMJcLdr4pxwowZ8Zh3P+tVswE/bnX6tZ14UGKNcdV5WVK2P+lGp2As+RCjl+i3SFJ117HyCaaHNDA==", + "license": "CC-SA 3.0" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001695", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz", + "integrity": "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/cbor": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.2.tgz", + "integrity": "sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==", + "license": "MIT", + "dependencies": { + "nofilter": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/centra": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz", + "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6" + } + }, + "node_modules/chai-nightwatch": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.5.3.tgz", + "integrity": "sha512-38ixH/mqpY6IwnZkz6xPqx8aB5/KVR+j6VPugcir3EGOsphnWXrPH/mUt8Jp+ninL6ghY0AaJDQ10hSfCPGy/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "1.1.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk-template": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", + "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/chi-squared": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chi-squared/-/chi-squared-1.1.0.tgz", + "integrity": "sha512-IFJA5igW44wzi7VHSsBcuHQ1+sF6noKRK6eFb+so4v9A/dH23RElM+8UBCi6+kWSINB/hDPNH+KDBCazPuKVwg==", + "license": "MIT", + "dependencies": { + "gamma": "^1.0.0" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/chromedriver": { + "version": "130.0.4", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-130.0.4.tgz", + "integrity": "sha512-lpR+PWXszij1k4Ig3t338Zvll9HtCTiwoLM7n4pCCswALHxzmgwaaIFBh3rt9+5wRk9D07oFblrazrBxwaYYAQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@testim/chrome-version": "^1.1.4", + "axios": "^1.7.4", + "compare-versions": "^6.1.0", + "extract-zip": "^2.0.1", + "proxy-agent": "^6.4.0", + "proxy-from-env": "^1.1.0", + "tcp-port-used": "^1.0.2" + }, + "bin": { + "chromedriver": "bin/chromedriver" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ci-info": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", + "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cipher-base": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clear-module": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", + "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clear-module/node_modules/parent-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", + "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clear-module/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/comment-json": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", + "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression-webpack-plugin": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-11.1.0.tgz", + "integrity": "sha512-zDOQYp10+upzLxW+VRSjEpRRwBXJdsb5lBMlRxx1g8hckIFBpe3DTI0en2w7h+beuq89576RVzfiXrkdPGrHhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-js": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", + "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", + "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.40.0.tgz", + "integrity": "sha512-AtDzVIgRrmRKQai62yuSIN5vNiQjcJakJb4fbhVw3ehxx7Lohphvw9SGNWKhLFqSxC4ilD0g/L1huAYFQU3Q6A==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "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" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-api": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/crypto-api/-/crypto-api-0.8.5.tgz", + "integrity": "sha512-kcif7fCeYZpUsA3Y1VidFrK4HRf2Lsx9X4cnl7pauTXjgnXfEjaTyUGxzIBJ6DZwEPgX/VyKkhAeBV+vXHwX2Q==", + "license": "MIT" + }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/cspell": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.17.3.tgz", + "integrity": "sha512-fBZg674Dir9y/FWMwm2JyixM/1eB2vnqHJjRxOgGS/ZiZ3QdQ3LkK02Aqvlni8ffWYDZnYnYY9rfWmql9bb42w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-json-reporter": "8.17.3", + "@cspell/cspell-pipe": "8.17.3", + "@cspell/cspell-types": "8.17.3", + "@cspell/dynamic-import": "8.17.3", + "@cspell/url": "8.17.3", + "chalk": "^5.4.1", + "chalk-template": "^1.1.0", + "commander": "^13.1.0", + "cspell-dictionary": "8.17.3", + "cspell-gitignore": "8.17.3", + "cspell-glob": "8.17.3", + "cspell-io": "8.17.3", + "cspell-lib": "8.17.3", + "fast-json-stable-stringify": "^2.1.0", + "file-entry-cache": "^9.1.0", + "get-stdin": "^9.0.0", + "semver": "^7.6.3", + "tinyglobby": "^0.2.10" + }, + "bin": { + "cspell": "bin.mjs", + "cspell-esm": "bin.mjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/streetsidesoftware/cspell?sponsor=1" + } + }, + "node_modules/cspell-config-lib": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-8.17.3.tgz", + "integrity": "sha512-+N32Q6xck3D2RqZIFwq8s0TnzHYMpyh4bgNtYqW5DIP3TLDiA4/MJGjwmLKAg/s9dkre6n8/++vVli3MZAOhIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "8.17.3", + "comment-json": "^4.2.5", + "yaml": "^2.7.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-dictionary": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.17.3.tgz", + "integrity": "sha512-89I/lpQKdkX17RCFrUIJnc70Rjfpup/o+ynHZen0hUxGTfLsEJPrK6H2oGvic3Yrv5q8IOtwM1p8vqPqBkBheA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.17.3", + "@cspell/cspell-types": "8.17.3", + "cspell-trie-lib": "8.17.3", + "fast-equals": "^5.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-gitignore": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.17.3.tgz", + "integrity": "sha512-rQamjb8R+Nwib/Bpcgf+xv5IdsOHgbP+fe4hCgv0jjgUPkeOR2c4dGwc0WS+2UkJbc+wQohpzBGDLRYGSB/hQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "8.17.3", + "cspell-glob": "8.17.3", + "cspell-io": "8.17.3", + "find-up-simple": "^1.0.0" + }, + "bin": { + "cspell-gitignore": "bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-glob": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.17.3.tgz", + "integrity": "sha512-0ov9A0E6OuOO7KOxlGCxJ09LR/ubZ6xcGwWc5bu+jp/8onUowQfe+9vZdznj/o8/vcf5JkDzyhRSBsdhWKqoAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/url": "8.17.3", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-grammar": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.17.3.tgz", + "integrity": "sha512-wfjkkvHthnKJtEaTgx3cPUPquGRXfgXSCwvMJaDyUi36KBlopXX38PejBTdmuqrvp7bINLSuHErml9wAfL5Fxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.17.3", + "@cspell/cspell-types": "8.17.3" + }, + "bin": { + "cspell-grammar": "bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-io": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.17.3.tgz", + "integrity": "sha512-NwEVb3Kr8loV1C8Stz9QSMgUrBkxqf2s7A9H2/RBnfvQBt9CWZS6NgoNxTPwHj3h1sUNl9reDkMQQzkKtgWGBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-service-bus": "8.17.3", + "@cspell/url": "8.17.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-lib": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.17.3.tgz", + "integrity": "sha512-KpwYIj8HwFyTzCCQcyezlmomvyNfPwZQmqTh4V126sFvf9HLoMdfyq8KYDZmZ//4HzwrF/ufJOF3CpuVUiJHfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-bundled-dicts": "8.17.3", + "@cspell/cspell-pipe": "8.17.3", + "@cspell/cspell-resolver": "8.17.3", + "@cspell/cspell-types": "8.17.3", + "@cspell/dynamic-import": "8.17.3", + "@cspell/filetypes": "8.17.3", + "@cspell/strong-weak-map": "8.17.3", + "@cspell/url": "8.17.3", + "clear-module": "^4.1.2", + "comment-json": "^4.2.5", + "cspell-config-lib": "8.17.3", + "cspell-dictionary": "8.17.3", + "cspell-glob": "8.17.3", + "cspell-grammar": "8.17.3", + "cspell-io": "8.17.3", + "cspell-trie-lib": "8.17.3", + "env-paths": "^3.0.0", + "fast-equals": "^5.2.2", + "gensequence": "^7.0.0", + "import-fresh": "^3.3.0", + "resolve-from": "^5.0.0", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-uri": "^3.0.8", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-lib/node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cspell-lib/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cspell-trie-lib": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.17.3.tgz", + "integrity": "sha512-6LE5BeT2Rwv0bkQckpxX0K1fnFCWfeJ8zVPFtYOaix0trtqj0VNuwWzYDnxyW+OwMioCH29yRAMODa+JDFfUrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.17.3", + "@cspell/cspell-types": "8.17.3", + "gensequence": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cspell/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell/node_modules/file-entry-cache": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.1.0.tgz", + "integrity": "sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell/node_modules/flat-cache": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", + "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.3.1", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", + "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^2.8.2", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ctph.js": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ctph.js/-/ctph.js-0.0.5.tgz", + "integrity": "sha512-xcgQ6zzamT6ESHBhFPZHjkcpjzv4rtWw9GLoeNwvQwQQdXNR49xKBF5/Nt1oLsE3X1JM8QHktbGknp5cH20PTA==" + }, + "node_modules/cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hexbin": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/d3-hexbin/-/d3-hexbin-0.2.2.tgz", + "integrity": "sha512-KS3fUT2ReD4RlGCjvCEm1RgMtp2NFZumdMu4DBzQK8AZv3fXRM6Xm8I4fSU07UXvH4xxg03NwWKWdvxfS/yc4w==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.0.1.tgz", + "integrity": "sha512-D/Oxqobjr+kxaHsgiQBZq9b6iAWdEj5W/JdJm8deNduAPc9CwXQ3BJJCuEqlrPXcy45iOMkGPZ0T81Dnz7UDCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-equal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "license": "MIT", + "dependencies": { + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.5.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-for-each": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/deep-for-each/-/deep-for-each-3.0.0.tgz", + "integrity": "sha512-pPN+0f8jlnNP+z90qqOdxGghJU5XM6oBDhvAR+qdQzjCg5pk/7VPPvKK1GqoXEFkHza6ZS+Otzzvmr0g3VUaKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.isplainobject": "^4.0.6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/devtools-protocol": { + "version": "0.0.1140464", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1140464.tgz", + "integrity": "sha512-I1jXnjpQh/6TBFyQ0A9dB2kXXk6DprpPFZoI8pUsxHtlNuOTQEdv9fUqYBsFtf8tOJCbdsZZyQrWeXu6GfK+Bw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", + "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.86", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.86.tgz", + "integrity": "sha512-/D7GAAaCRBQFBBcop6SfAAGH37djtpWkOuYhyAajw0l5vsfeSsUQYxaFPwr1c/mC/flARCDdKFo5gpFqNI+18w==", + "dev": true, + "license": "ISC" + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "dev": true, + "dependencies": { + "string-template": "~0.2.1" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", + "license": "MIT" + }, + "node_modules/es6-polyfills": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es6-polyfills/-/es6-polyfills-2.0.0.tgz", + "integrity": "sha512-daIt/MHqdYmxnuo5KcwAU9EqSxvaDRyajYOUU9fy+CLuU5+RFhpNCnL3oPsq7n+g673F3z/Vb+FXo/EmQjlkbw==", + "deprecated": "Use @natlibfi/es6-polyfills instead", + "license": " LGPL-3.0+", + "dependencies": { + "es6-object-assign": "^1.0.3", + "es6-promise-polyfill": "^1.2.0" + } + }, + "node_modules/es6-promise-polyfill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es6-promise-polyfill/-/es6-promise-polyfill-1.2.0.tgz", + "integrity": "sha512-HHb0vydCpoclpd0ySPkRXMmBw80MRt1wM4RBJBlXkux97K7gleabZdsR0gvE1nNPM9mgOZIBTzjjXiPxf4lIqQ==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-7.0.0.tgz", + "integrity": "sha512-ginqzK3J90Rd4/Yz7qRrqUeIpe3TwSXTPPZtPne7tGBPeAaQiU8qt4fpKApnxHcq1AwtUdHVg5P77x/yrggG8Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.10.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "48.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.11.0.tgz", + "integrity": "sha512-d12JHJDPNo7IFwTOAItCeJY1hcqoIxE0lHA8infQByLilQ9xkqrRa6laWCnsuCrf+8rUnvxXY1XuTbibRBNylA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.46.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.5", + "escape-string-regexp": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "dev": true, + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fernet": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/fernet/-/fernet-0.4.0.tgz", + "integrity": "sha512-FJgmKoMeG4eoM+bEbMyTzPx9US4W+sw4D0i6jSWSKHFYlFZv92UIWi6a/w6MEf2JNSO+Mth/T+u+d/ye1zwN9A==", + "license": "MIT", + "dependencies": { + "crypto-js": "~3.1.2-1", + "urlsafe-base64": "1.0.0" + } + }, + "node_modules/fernet/node_modules/crypto-js": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.8.tgz", + "integrity": "sha512-4E+PJNbUbBrzQLB9Vw86eaF5xZ8MB6dw4aaZ67YhLNduTmqJ5AziuLrYikPWHJkC59DYZp+cyj+qg3vBE8OnOQ==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, + "node_modules/file-sync-cmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha512-0k45oWBokCqh2MOexeYKpyqmGKG+8mQ2Wd8iawx+uWd/weWJQAZ6SoPybagdCI4xFisag8iAR77WPm4h3pTfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", + "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/findup-sync": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-6.0.1.tgz", + "integrity": "sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dev": true, + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gamma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gamma/-/gamma-1.0.0.tgz", + "integrity": "sha512-bPI4QJKNe/PfuO6PZU7ckMkBk0wEe3iQWx3DadEp5ceiAkJwc8CRukwncOZ5Hk5rc6nNTbIfByZKGz7Vv+xXdw==", + "license": "MIT" + }, + "node_modules/gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "globule": "^1.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/gensequence": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-7.0.0.tgz", + "integrity": "sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/geodesy": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/geodesy/-/geodesy-1.1.3.tgz", + "integrity": "sha512-H/0XSd1KjKZGZ2YGZcOYzRyY/foYAawwTEumNSo+YUwf+u5d4CfvBRg2i2Qimrx9yUEjWR8hLvMnhghuVFN0Zg==", + "license": "MIT" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/getobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", + "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "license": "MIT", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-directory/node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globule": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", + "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "~7.1.1", + "lodash": "^4.17.21", + "minimatch": "~3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/globule/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/grunt": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", + "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dateformat": "~4.6.2", + "eventemitter2": "~0.4.13", + "exit": "~0.1.2", + "findup-sync": "~5.0.0", + "glob": "~7.1.6", + "grunt-cli": "~1.4.3", + "grunt-known-options": "~2.0.0", + "grunt-legacy-log": "~3.0.0", + "grunt-legacy-util": "~2.0.1", + "iconv-lite": "~0.6.3", + "js-yaml": "~3.14.0", + "minimatch": "~3.0.4", + "nopt": "~3.0.6" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/grunt-chmod": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-chmod/-/grunt-chmod-1.1.1.tgz", + "integrity": "sha512-f807W/VOIhhaOW85JyeRd4DgB0RcbsGQV/4IvtcKctOWGvPJns4AqN7xW73PG9+RwDnSGxApS+6Xov5L2LeNXg==", + "dev": true, + "dependencies": { + "shelljs": "^0.5.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/grunt-cli": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", + "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "grunt-known-options": "~2.0.0", + "interpret": "~1.1.0", + "liftup": "~3.0.1", + "nopt": "~4.0.1", + "v8flags": "~3.2.0" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-cli/node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/grunt-concurrent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-3.0.0.tgz", + "integrity": "sha512-AgXtjUJESHEGeGX8neL3nmXBTHSj1QC48ABQ3ng2/vjuSBpDD8gKcVHSlXP71pFkIR8TQHf+eomOx6OSYSgfrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "arrify": "^2.0.1", + "async": "^3.1.0", + "indent-string": "^4.0.0", + "pad-stream": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "grunt": ">=1" + } + }, + "node_modules/grunt-contrib-clean": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz", + "integrity": "sha512-uRvnXfhiZt8akb/ZRDHJpQQtkkVkqc/opWO4Po/9ehC2hPxgptB9S6JHDC/Nxswo4CJSM0iFPT/Iym3cEMWzKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.3", + "rimraf": "^2.6.2" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "grunt": ">=0.4.5" + } + }, + "node_modules/grunt-contrib-connect": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-connect/-/grunt-contrib-connect-4.0.0.tgz", + "integrity": "sha512-VR2/+ailwTClAXrvI7bK78roCZzfY1C48vmpdRldohx8P1VXcb51NmBNhukBvG2RKFChNheEcKEcM+wSb/5nYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.0", + "connect": "^3.7.0", + "connect-livereload": "^0.6.1", + "morgan": "^1.10.0", + "node-http2": "^4.0.1", + "open": "^8.0.0", + "portscanner": "^2.2.0", + "serve-index": "^1.9.1", + "serve-static": "^1.14.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/grunt-contrib-copy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha512-gFRFUB0ZbLcjKb67Magz1yOHGBkyU6uL29hiEW1tdQ9gQt72NuMKIy/kS6dsCbV0cZ0maNCb0s6y+uT1FKU7jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.1.1", + "file-sync-cmp": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "async": "^2.6.0", + "gaze": "^1.1.0", + "lodash": "^4.17.10", + "tiny-lr": "^1.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-watch/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/grunt-eslint": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-25.0.0.tgz", + "integrity": "sha512-JIV5IPgOuacorFLmYtUTq0n+0qGIL9FSQJ4KVnNfCg/8Fm+K1t6OWrzXXI8TxWTwq2K9E3parFVXCpn1sGLbKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "eslint": "^9.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + }, + "peerDependencies": { + "grunt": ">=1" + } + }, + "node_modules/grunt-eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/grunt-eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/grunt-eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "grunt": ">=0.4" + } + }, + "node_modules/grunt-known-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", + "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-legacy-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", + "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.19" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/grunt-legacy-log-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", + "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "~4.1.0", + "lodash": "~4.17.19" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/grunt-legacy-log/node_modules/colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/grunt-legacy-util": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", + "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "~3.2.0", + "exit": "~0.1.2", + "getobject": "~1.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.21", + "underscore.string": "~3.3.5", + "which": "~2.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-retro": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/grunt-retro/-/grunt-retro-0.6.4.tgz", + "integrity": "sha512-kqnvNUAngOhkDckEQPYFDqNcRlculVp/Sy+gCe4ey7utM4BCaENVf2JfDeK488mj/0cgmAZyXwpW6w9l1OAxMg==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/grunt-webpack": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/grunt-webpack/-/grunt-webpack-6.0.0.tgz", + "integrity": "sha512-FtRVTGJGuV9Ic/OrCR80p5u601e0ekvTyHo7vnwVo3XlvRh5wR1ATAVT9FnnobHqZnQ/DeF84W97si5+roUWEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-for-each": "^3.0.0", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=16.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/grunt-zip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-zip/-/grunt-zip-1.0.0.tgz", + "integrity": "sha512-e5HOzf+BLFR6rXM67oGrFcVgfVbLDPBnv29YdWTupthwNg/1y91B0xXx667E6dKin8YvrwDShtQy48D1NRzn7g==", + "dev": true, + "dependencies": { + "grunt-retro": "~0.6.0", + "jszip": "^3.8.0" + }, + "bin": { + "grunt-zip": "bin/grunt-zip" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/grunt/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/grunt/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/grunt/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/grunt/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/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, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", + "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/http-parser-js": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", + "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha512-EjDQFbgJr1vDD/175UJeSX3ncQ3+RUnCL5NkthQGHvF4VNHlzTy8ifJfTqz47qiPRqaFH58+CbuG3x51WuB1XQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iced-error": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/iced-error/-/iced-error-0.0.13.tgz", + "integrity": "sha512-yEEaG8QfyyRL0SsbNNDw3rVgTyqwHFMCuV6jDvD43f/2shmdaFXkqvFLGhDlsYNSolzYHwVLM/CrXt9GygYopA==" + }, + "node_modules/iced-lock": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/iced-lock/-/iced-lock-1.1.0.tgz", + "integrity": "sha512-J9UMVitgTMYrkUil5EB9/Q4BPWiMpFH156yjDlmMoMRKs3s3PnXj/6G0UlzIOGnNi5JVNk/zVYLXVnuo+1QnqQ==", + "dependencies": { + "iced-runtime": "^1.0.0" + } + }, + "node_modules/iced-runtime": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/iced-runtime/-/iced-runtime-1.0.4.tgz", + "integrity": "sha512-rgiJXNF6ZgF2Clh/TKUlBDW3q51YPDJUXmxGQXx1b8tbZpVpTn+1RX9q1sjNkujXIIaVxZByQzPHHORg7KV51g==" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==", + "license": "Apache-2.0" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "license": "MIT", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "license": "MIT" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imports-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-5.0.0.tgz", + "integrity": "sha512-tXgL8xxZFjOjQLLiE7my00UUQfktg4G8fdpXcZphL0bJWbk9eCxKKFaCwmFRcwyRJQl95GXBL1DoE1rCS/tcPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map-js": "^1.0.2", + "strip-comments": "^2.0.1" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "license": "MIT" + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/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, + "license": "ISC", + "dependencies": { + "lodash.isfinite": "^3.3.2" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is2": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", + "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + }, + "engines": { + "node": ">=v0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jake/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jimp": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.12.tgz", + "integrity": "sha512-R5jZaYDnfkxKJy1dwLpj/7cvyjxiclxU3F4TrI/J4j2rS0niq6YDUMoPn5hs8GDpO+OZGo7Ky057CRtWesyhfg==", + "license": "MIT", + "dependencies": { + "@jimp/custom": "^0.22.12", + "@jimp/plugins": "^0.22.12", + "@jimp/types": "^0.22.12", + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause" + }, + "node_modules/jq-web": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/jq-web/-/jq-web-0.5.1.tgz", + "integrity": "sha512-3Fa3E6g3U1O1j46ljy0EM10yRr4txzILga8J7bqOG8F89gZ6Lilz82WG9z6TItWpYEO0YGa4W8yFGj+NMM1xqQ==", + "license": "ISC" + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "license": "MIT" + }, + "node_modules/js-sha3": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz", + "integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==", + "license": "MIT" + }, + "node_modules/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, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdom": { + "version": "24.1.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz", + "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/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, + "license": "MIT" + }, + "node_modules/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": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonpath-plus": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-9.0.0.tgz", + "integrity": "sha512-bqE77VIDStrOTV/czspZhTn+o27Xx9ZJRGVkdVShEtPoqsIx5yALv3lWVU6y+PqYvWPJNWE7ORCQheQkEe0DDA==", + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.2.1", + "@jsep-plugin/regex": "^1.0.3", + "jsep": "^1.3.8" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "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", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/jsqr": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz", + "integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==", + "license": "Apache-2.0" + }, + "node_modules/jsrsasign": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-11.1.0.tgz", + "integrity": "sha512-Ov74K9GihaK9/9WncTe1mPmvrO7Py665TUfUKvraXBpu+xcTWitrtuOwcjf4KMU9maPaYn0OuaWy0HOzy/GBXg==", + "license": "MIT", + "funding": { + "url": "https://github.com/kjur/jsrsasign#donations" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kbpgp": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/kbpgp/-/kbpgp-2.1.15.tgz", + "integrity": "sha512-iFdQT+m2Mi2DB14kEFydF2joNe9x3E2VZCGZUt7UXsiZnQx5TtSl4KofP7EPtjHvf7weCxNKlEPSYiiCNMZ2jA==", + "license": "BSD-3-Clause", + "dependencies": { + "bn": "^1.0.5", + "bzip-deflate": "^1.0.0", + "deep-equal": "^1.1.0", + "iced-error": "0.0.13", + "iced-lock": "^1.0.2", + "iced-runtime": "^1.0.4", + "keybase-ecurve": "^1.0.1", + "keybase-nacl": "^1.1.2", + "minimist": "^1.2.0", + "pgp-utils": "0.0.35", + "purepack": "^1.0.5", + "triplesec": "^4.0.3", + "tweetnacl": "^0.13.1" + } + }, + "node_modules/keybase-ecurve": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/keybase-ecurve/-/keybase-ecurve-1.0.1.tgz", + "integrity": "sha512-2GlVxDsNF+52LtYjgFsjoKuN7MQQgiVeR4HRdJxLuN8fm4mf4stGKPUjDJjky15c/98UsZseLjp7Ih5X0Sy1jQ==", + "dependencies": { + "bn": "^1.0.4" + } + }, + "node_modules/keybase-nacl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/keybase-nacl/-/keybase-nacl-1.1.4.tgz", + "integrity": "sha512-7TFyWLq42CQs7JES9arR+Vnv/eMk5D6JT1Y8samrEA5ff3FOmaiRcXIVrwJQd3KJduxmSjgAjdkXlQK7Q437xQ==", + "license": "BSD-3-Clause", + "dependencies": { + "iced-runtime": "^1.0.2", + "tweetnacl": "^0.13.1", + "uint64be": "^1.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libbzip2-wasm": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/libbzip2-wasm/-/libbzip2-wasm-0.0.4.tgz", + "integrity": "sha512-RqscTx95+RTKhFAyjedsboR0Lmo3zd8//EuRwQXkdWmsCwYlzarVRaiYg6kS1O8m10MCQkGdrnlK9L4eAmZUwA==", + "license": "ISC" + }, + "node_modules/libyara-wasm": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/libyara-wasm/-/libyara-wasm-1.2.1.tgz", + "integrity": "sha512-PNqUNWnwjZLe55iA8Rv6vLQRjSdO2OnVg24aRE8v+ytR8CRB8agIG6pS9h2VQejuJP1A/uR4pwcBggUxoNC7DA==", + "license": "ISC" + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/liftup": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", + "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend": "^3.0.2", + "findup-sync": "^4.0.0", + "fined": "^1.2.0", + "flagged-respawn": "^1.0.1", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.1", + "rechoir": "^0.7.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/liftup/node_modules/findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/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, + "license": "MIT" + }, + "node_modules/load-bmfont": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.2.tgz", + "integrity": "sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==", + "license": "MIT", + "dependencies": { + "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": "^3.7.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/loader-utils-webpack-v4": { + "name": "loader-utils", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isfinite": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", + "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-message-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/loglevel-message-prefix/-/loglevel-message-prefix-3.0.0.tgz", + "integrity": "sha512-/cBEOqsuU0vJsFm4n92R7h6mkiKqt8vh+JOmW722DTZVVD7egEpVOx66re3vWxO7pii3B4eQuqm2qfqq5cAs0w==", + "deprecated": "Use @natlibfi/loglevel-message-prefix instead", + "license": "MIT", + "dependencies": { + "es6-polyfills": "^2.0.0", + "loglevel": "^1.4.0" + } + }, + "node_modules/long": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz", + "integrity": "sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==", + "license": "Apache-2.0" + }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/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, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/lz4js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/lz4js/-/lz4js-0.2.0.tgz", + "integrity": "sha512-gY2Ia9Lm7Ep8qMiuGRhvUq0Q7qUereeldZPP1PMEJxPtEWHJLqw9pgX68oHajBH0nzJK4MaZEA/YNV3jT8u8Bg==", + "license": "ISC" + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", + "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/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==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", + "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "8.1.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/modify-source-webpack-plugin": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/modify-source-webpack-plugin/-/modify-source-webpack-plugin-4.1.0.tgz", + "integrity": "sha512-UaLQyFXoPWpWxkNUBFo9BotC20CCAGe7HEX9iKtB0P0MgNXgURf9TXUgNGuY5iVV5lDDQtcMjT2vneQWnNmwEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils-webpack-v4": "npm:loader-utils@^2.0.4", + "schema-utils": "^4.0.0" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.46", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz", + "integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/more-entropy": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/more-entropy/-/more-entropy-0.0.7.tgz", + "integrity": "sha512-e0TxQtU1F6/ZA8WnEA2JLQwwDqBTtZFLJSW7rWgUsQou35wx1IOL0g2O7q7oGoMgIJto+jHMnNGHLfSiylHRrw==", + "dependencies": { + "iced-runtime": ">=0.0.1" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/ngeohash": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/ngeohash/-/ngeohash-0.6.3.tgz", + "integrity": "sha512-kltF0cOxgx1AbmVzKxYZaoB0aj7mOxZeHaerEtQV0YaqnkXNq26WWqMmJ6lTqShYxVRWZ/mwvvTrNeOwdslWiw==", + "license": "MIT", + "engines": { + "node": ">=v0.2.0" + } + }, + "node_modules/nightwatch": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-3.11.0.tgz", + "integrity": "sha512-97YQRsWZTr48lw1MysYbynUtJqn5LMoDT5o2jShR3H3iQHfg7OyKuYsetCifBgU3/eya67UmfCi2OkTZf66u/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nightwatch/chai": "5.0.3", + "@nightwatch/html-reporter-template": "^0.3.0", + "@nightwatch/nightwatch-inspector": "^1.0.1", + "@types/chai": "^4.3.5", + "@types/selenium-webdriver": "^4.1.14", + "ansi-to-html": "0.7.2", + "aria-query": "5.1.3", + "assertion-error": "1.1.0", + "boxen": "5.1.2", + "chai-nightwatch": "^0.5.3", + "chalk": "^4.1.2", + "ci-info": "3.3.0", + "cli-table3": "^0.6.3", + "devtools-protocol": "^0.0.1140464", + "didyoumean": "^1.2.2", + "dotenv": "16.3.1", + "ejs": "^3.1.10", + "envinfo": "7.11.0", + "glob": "7.2.3", + "jsdom": "^24.1.0", + "lodash": "^4.17.21", + "minimatch": "3.1.2", + "minimist": "1.2.6", + "mocha": "10.3.0", + "nightwatch-axe-verbose": "^2.3.0", + "open": "8.4.2", + "ora": "5.4.1", + "piscina": "^4.3.1", + "selenium-webdriver": "4.27.0", + "semver": "7.5.4", + "stacktrace-parser": "0.1.10", + "strip-ansi": "6.0.1", + "untildify": "4.0.0", + "uuid": "8.3.2" + }, + "bin": { + "nightwatch": "bin/nightwatch" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "@cucumber/cucumber": "*" + }, + "peerDependenciesMeta": { + "@cucumber/cucumber": { + "optional": true + }, + "chromedriver": { + "optional": true + }, + "geckodriver": { + "optional": true + } + } + }, + "node_modules/nightwatch-axe-verbose": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.3.1.tgz", + "integrity": "sha512-C6N95bwPHsRnv04eVIwJ6w5m6X1+Pddvo6nzpzOHQlO0j+pYRVU7zaQmFUJ0L4cqeUxReNEXyTUg/R9WWfHk7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "axe-core": "^4.9.1" + } + }, + "node_modules/nightwatch/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/nightwatch/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nightwatch/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/nightwatch/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nightwatch/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nightwatch/node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/nightwatch/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nightwatch/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nightwatch/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nightwatch/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-http2": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/node-http2/-/node-http2-4.0.1.tgz", + "integrity": "sha512-AP21BjQsOAMTCJCCkdXUUMa1o7/Qx+yAWHnHZbCf8RhZ+hKMjB9rUkAtnfayk/yGj1qapZ5eBHZJBpk1dqdNlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "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" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/node-http2/node_modules/stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha512-nmQnY9D9TlnfQIkYJCCWxvCcQODilFRZIw14gCMYQVXOiY4E1Ze1VMxB+6y3qdXHpTordULo2qWloHmNcNAQYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/node-md6": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/node-md6/-/node-md6-0.1.0.tgz", + "integrity": "sha512-n+s6dhxfV2JCRFgNGzdX0SxxL/SyUwR68uGR/rlNVbXhrdti4M1xKHO+PzXtYLUo1DEsWFZe29B3Z9y5JgpfvA==", + "license": "CC0-1.0" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodom": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/nodom/-/nodom-2.4.0.tgz", + "integrity": "sha512-qhfYgpoCSi37HLiViMlf94YqMQdvk3n3arI1uGbAWZK9NKCYRSI42W8lATeGloYGLYxb8us1C5rTvtsXjwdWQg==", + "license": "ISC" + }, + "node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "license": "MIT", + "engines": { + "node": ">=12.19" + } + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/notepack.io": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz", + "integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg==", + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ntlm": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ntlm/-/ntlm-0.1.3.tgz", + "integrity": "sha512-pPlHxhAegZP4QAaOYd51vRd6VXTGfF7VLKJwuwN0iEB1aIi3SnqXYuS/bH/6wWBOq+Ehdil49mHm1Nseon085w==", + "engines": [ + "node" + ] + }, + "node_modules/nwmatcher": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", + "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==", + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/otpauth": { + "version": "9.3.6", + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.3.6.tgz", + "integrity": "sha512-eIcCvuEvcAAPHxUKC9Q4uCe0Fh/yRc5jv9z+f/kvyIF2LPrhgAOuLB7J9CssGYhND/BL8M9hlHBTFmffpoQlMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.6.1" + }, + "funding": { + "url": "https://github.com/hectorm/otpauth?sponsor=1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.1.0.tgz", + "integrity": "sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pad-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-2.0.0.tgz", + "integrity": "sha512-3QeQw19K48BQzUGZ9dEf/slX5Jbfy5ZeBTma2XICketO7kFNK7omF00riVcecOKN+DSiJZcK2em1eYKaVOeXKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pumpify": "^1.3.3", + "split2": "^2.1.1", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "license": "MIT", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==", + "license": "MIT" + }, + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "license": "Apache-2.0 AND MIT", + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pgp-utils": { + "version": "0.0.35", + "resolved": "https://registry.npmjs.org/pgp-utils/-/pgp-utils-0.0.35.tgz", + "integrity": "sha512-gCT6EbSTgljgycVa5qGpfRITaLOLbIKsEVRTdsNRgmLMAJpuJNNdrTn/95r8IWo9rFLlccfmGMJXkG9nVDwmrA==", + "dependencies": { + "iced-error": ">=0.0.8", + "iced-runtime": ">=0.0.1" + } + }, + "node_modules/phin": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz", + "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==", + "license": "MIT", + "dependencies": { + "centra": "^2.7.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/piscina": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.8.0.tgz", + "integrity": "sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@napi-rs/nice": "^1.0.1" + } + }, + "node_modules/pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", + "license": "ISC", + "dependencies": { + "pngjs": "^3.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/portscanner": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^2.6.0", + "is-number-like": "^1.0.3" + }, + "engines": { + "node": ">=0.4", + "npm": ">=1.0.0" + } + }, + "node_modules/portscanner/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-css-variables": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/postcss-css-variables/-/postcss-css-variables-0.19.0.tgz", + "integrity": "sha512-Hr0WEYKLK9VCrY15anHXOd4RCvJy/xRvCnWdplGBeLInwEj6Z14hgzTb2W/39dYTCnS8hnHUfU4/F1zxX0IZuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "escape-string-regexp": "^1.0.3", + "extend": "^3.0.1" + }, + "peerDependencies": { + "postcss": "^8.2.6" + } + }, + "node_modules/postcss-import": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.0.tgz", + "integrity": "sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompt": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", + "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "1.5.0", + "async": "3.2.3", + "read": "1.0.x", + "revalidator": "0.1.x", + "winston": "2.x" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/prompt/node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", + "dependencies": { + "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" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/purepack": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/purepack/-/purepack-1.0.6.tgz", + "integrity": "sha512-L/e3qq/3m/TrYtINo2aBB98oz6w8VHGyFy+arSKwPMZDUNNw2OaQxYnZO6UIZZw2OnRl2qkxGmuSOEfsuHXJdA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/qr-image": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/qr-image/-/qr-image-3.2.0.tgz", + "integrity": "sha512-rXKDS5Sx3YipVsqmlMJsJsk6jXylEpiHRC2+nJy66fxA5ExYyGa4PqwteW69SaVmAb2OQ18HbYriT7cGQMbduw==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "1", + "string_decoder": "0.10" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/raw-body/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "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" + } + }, + "node_modules/readable-stream/node_modules/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==", + "license": "MIT" + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/renderkid/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/revalidator": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", + "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==", + "dev": true, + "license": "Apache 2.0", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/rison": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/rison/-/rison-0.1.1.tgz", + "integrity": "sha512-8C+/PKKTaAYE2quDtOUwny/eQpNn9YGby7T80wntbVWSGvw0aUT9M0YgLdLkUgIQzQwaB1ZTr80rwLVKyohHig==", + "license": "Apache-2.0" + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/scryptsy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", + "license": "MIT" + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selenium-webdriver": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.27.0.tgz", + "integrity": "sha512-LkTJrNz5socxpPnWPODQ2bQ65eYx9JK+DQMYNihpTjMCqHwgWGYQnQTCAAche2W3ZP87alA+1zYPvgS8tHNzMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/SeleniumHQ" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/selenium" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@bazel/runfiles": "^6.3.1", + "jszip": "^3.10.1", + "tmp": "^0.2.3", + "ws": "^8.18.0" + }, + "engines": { + "node": ">= 14.21.0" + } + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", + "integrity": "sha512-C2FisSSW8S6TIYHHiMHN0NqzdjWfTekdMpA2FJTbRWnQMLO1RRIXEB9eVZYOlofYmjZA7fY3ChoFu09MeI3wlQ==", + "dev": true, + "license": "BSD*", + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sitemap": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.0.tgz", + "integrity": "sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "license": "ISC" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/snackbarjs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/snackbarjs/-/snackbarjs-1.1.0.tgz", + "integrity": "sha512-WIMYor1cy6Q2r1GdfvKG4F+Rg9VpQM5/DiemAQFMlVEpnPA7HSk7dBoYam195+y3rMfzhg87v7SW5J0QGoMj2g==", + "license": "ISC" + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sockjs/node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sortablejs": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz", + "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "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" + } + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/split.js": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz", + "integrity": "sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==", + "license": "MIT" + }, + "node_modules/split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "through2": "^2.0.2" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ssdeep.js": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/ssdeep.js/-/ssdeep.js-0.0.3.tgz", + "integrity": "sha512-QXKADMuEsOmRYGlB9JvrulcF5NLAjAvYLg3qDHUrEEilUFLKoL4fUQDw1s7hF+mOL96jwcdyXpnmCqyNu7Wm3Q==" + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/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==", + "license": "MIT" + }, + "node_modules/string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tcp-port-used": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4.3.1", + "is2": "^2.0.6" + } + }, + "node_modules/tcp-port-used/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/tcp-port-used/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", + "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tesseract.js": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-5.1.0.tgz", + "integrity": "sha512-2fH9pqWdS2C6ue/3OoGg91Wtv7Rt/1atYu/g0Q1SGFrowEW/kIBkG361hLienHsWe4KWEjxOJBrCQYpIBWG6WA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bmp-js": "^0.1.0", + "idb-keyval": "^6.2.0", + "is-electron": "^2.2.2", + "is-url": "^1.2.4", + "node-fetch": "^2.6.9", + "opencollective-postinstall": "^2.0.3", + "regenerator-runtime": "^0.13.3", + "tesseract.js-core": "^5.1.0", + "wasm-feature-detect": "^1.2.11", + "zlibjs": "^0.3.1" + } + }, + "node_modules/tesseract.js-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-5.1.1.tgz", + "integrity": "sha512-KX3bYSU5iGcO1XJa+QGPbi+Zjo2qq6eBhNjSGR5E5q0JtzkoipJKOUQD7ph8kFyteCEfEQ0maWLu8MCXtvX5uQ==", + "license": "Apache-2.0" + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/timers-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz", + "integrity": "sha512-O7UB405+hxP2OWqlBdlUMxZVEdsi8NOWL2c730Cs6zeO1l1AkxygvTm6yC4nTw84iGbFcqxbIkkrdNKzq/3Fvg==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/timm": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==", + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "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" + } + }, + "node_modules/tiny-lr/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", + "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/triplesec": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/triplesec/-/triplesec-4.0.3.tgz", + "integrity": "sha512-fug70e1nJoCMxsXQJlETisAALohm84vl++IiTTHEqM7Lgqwz62jrlwqOC/gJEAJjO/ByN127sEcioB56HW3wIw==", + "dependencies": { + "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" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tweetnacl": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz", + "integrity": "sha512-iNWodk4oBsZ03Qfw/Yvv0KB90uYrJqvL4Je7Gy4C5t/GS3sCXPRmIT1lxmId4RzvUp0XG62bcxJ2CBu/3L5DSg==", + "license": "Public domain" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz", + "integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uint64be": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uint64be/-/uint64be-1.0.1.tgz", + "integrity": "sha512-w+VZSp8hSZ/xWZfZNMppWNF6iqY+dcMYtG5CpwRDgxi94HIE6ematSdkzHGzVC4SDEaTsG65zrajN+oKoWG6ew==", + "license": "MIT" + }, + "node_modules/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, + "license": "MIT" + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/underscore.string": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", + "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "^1.1.1", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unorm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", + "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==", + "license": "MIT or GPL-2.0", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/urlsafe-base64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", + "integrity": "sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==" + }, + "node_modules/utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "license": "MIT" + }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.11" + } + }, + "node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", + "license": "ISC" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vkbeautify": { + "version": "0.99.3", + "resolved": "https://registry.npmjs.org/vkbeautify/-/vkbeautify-0.99.3.tgz", + "integrity": "sha512-2ozZEFfmVvQcHWoHLNuiKlUfDKlhh4KGsy54U0UrlLMR1SO+XKAIDqBxtBwHgNrekurlJwE8A9K6L49T78ZQ9Q==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.4.0", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/webpack-dev-server/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-stream": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.5.2.tgz", + "integrity": "sha512-8z49MKIHbGk3C4HtuHWDtYX8mYej1wWabjthC/RupM9ngeukU4IWoM46dgth1UOS/T4/IqgEdCDJuMe2039OQQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "duplexify": "^3.5.1", + "inherits": "^2.0.1", + "readable-stream": "^2.3.3", + "safe-buffer": "^5.1.2", + "ws": "^3.2.0", + "xtend": "^4.0.0" + } + }, + "node_modules/websocket-stream/node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/websocket-stream/node_modules/ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/winston": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz", + "integrity": "sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^2.6.4", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/winston/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/winston/node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/worker-loader": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", + "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/worker-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "license": "MIT", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/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": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xpath": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz", + "integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==", + "license": "MIT", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/xregexp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.1.tgz", + "integrity": "sha512-fKXeVorD+CzWvFs7VBuKTYIW63YD1e1osxwQ8caZ6o1jg6pDAbABDG54LCIq0j5cy7PjRvGIq6sef9DYPXpncg==", + "license": "MIT", + "dependencies": { + "@babel/runtime-corejs3": "^7.16.5" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/zlibjs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", + "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", + "license": "MIT", + "engines": { + "node": "*" + } + } + } +} diff --git a/package.json b/package.json index c8027fc7..b3492a8e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cyberchef", - "version": "5.0.0", - "description": "CyberChef is a simple, intuitive web app for analysing and decoding data within a web browser.", + "version": "10.19.4", + "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", "copyright": "Crown copyright 2016", @@ -19,67 +19,187 @@ "hex", "encoding", "format", - "cybersecurity" + "cybersecurity", + "data manipulation", + "data analysis" ], "repository": { "type": "git", "url": "https://github.com/gchq/CyberChef/" }, + "main": "src/node/wrapper.js", + "exports": { + "import": "./src/node/index.mjs", + "require": "./src/node/wrapper.js" + }, + "bugs": "https://github.com/gchq/CyberChef/issues", + "browserslist": [ + "Chrome >= 50", + "Firefox >= 38", + "node >= 16" + ], "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", + "@babel/core": "^7.24.7", + "@babel/eslint-parser": "^7.24.7", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/runtime": "^7.24.7", + "@codemirror/commands": "^6.6.0", + "@codemirror/language": "^6.10.2", + "@codemirror/search": "^6.5.6", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.28.0", + "autoprefixer": "^10.4.19", + "babel-loader": "^9.1.3", + "babel-plugin-dynamic-import-node": "^2.3.3", + "babel-plugin-transform-builtin-extend": "1.1.2", + "base64-loader": "^1.0.0", + "chromedriver": "^130.0.0", + "cli-progress": "^3.12.0", + "colors": "^1.4.0", + "compression-webpack-plugin": "^11.1.0", + "copy-webpack-plugin": "^12.0.2", + "core-js": "^3.37.1", + "cspell": "^8.17.3", + "css-loader": "7.1.2", + "eslint": "^9.4.0", + "eslint-plugin-jsdoc": "^48.2.9", + "globals": "^15.4.0", + "grunt": "^1.6.1", "grunt-chmod": "~1.1.1", - "grunt-contrib-clean": "~1.0.0", + "grunt-concurrent": "^3.0.0", + "grunt-contrib-clean": "~2.0.1", + "grunt-contrib-connect": "^4.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", - "less": "^2.7.2", - "less-loader": "^4.0.2", - "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": "^25.0.0", + "grunt-exec": "~3.0.0", + "grunt-webpack": "^6.0.0", + "grunt-zip": "^1.0.0", + "html-webpack-plugin": "^5.6.0", + "imports-loader": "^5.0.0", + "mini-css-extract-plugin": "2.9.0", + "modify-source-webpack-plugin": "^4.1.0", + "nightwatch": "^3.6.3", + "postcss": "^8.4.38", + "postcss-css-variables": "^0.19.0", + "postcss-import": "^16.1.0", + "postcss-loader": "^8.1.1", + "prompt": "^1.3.0", + "sitemap": "^8.0.0", + "terser": "^5.31.1", + "webpack": "^5.91.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "5.0.4", + "webpack-node-externals": "^3.0.0", + "worker-loader": "^3.0.8" }, "dependencies": { - "bootstrap": "^3.3.7", - "bootstrap-colorpicker": "^2.5.1", - "bootstrap-switch": "^3.3.4", - "crypto-api": "^0.6.2", - "crypto-js": "^3.1.9-1", - "diff": "^3.2.0", - "escodegen": "^1.8.1", - "esmangle": "^1.0.1", - "esprima": "^3.1.3", - "google-code-prettify": "^1.0.5", - "jquery": "^3.1.1", - "jsbn": "^1.1.0", - "jsrsasign": "^7.1.0", - "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", - "xmldom": "^0.1.27", - "xpath": "0.0.24", - "zlibjs": "^0.2.0" + "@astronautlabs/amf": "^0.0.6", + "@babel/polyfill": "^7.12.1", + "@blu3r4y/lzma": "^2.3.3", + "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", + "@xmldom/xmldom": "^0.8.10", + "argon2-browser": "^1.18.0", + "arrive": "^2.4.1", + "avsc": "^5.7.7", + "bcryptjs": "^2.4.3", + "bignumber.js": "^9.1.2", + "blakejs": "^1.2.1", + "bootstrap": "4.6.2", + "bootstrap-colorpicker": "^3.4.0", + "bootstrap-material-design": "^4.1.3", + "browserify-zlib": "^0.2.0", + "bson": "^4.7.2", + "buffer": "^6.0.3", + "cbor": "9.0.2", + "chi-squared": "^1.1.0", + "codepage": "^1.15.0", + "crypto-api": "^0.8.5", + "crypto-browserify": "^3.12.0", + "crypto-js": "^4.2.0", + "ctph.js": "0.0.5", + "d3": "7.9.0", + "d3-hexbin": "^0.2.2", + "diff": "^5.2.0", + "dompurify": "^3.2.5", + "es6-promisify": "^7.0.0", + "escodegen": "^2.1.0", + "esprima": "^4.0.1", + "exif-parser": "^0.1.12", + "fernet": "^0.4.0", + "file-saver": "^2.0.5", + "flat": "^6.0.1", + "geodesy": "1.1.3", + "highlight.js": "^11.9.0", + "ieee754": "^1.2.1", + "jimp": "^0.22.12", + "jq-web": "^0.5.1", + "jquery": "3.7.1", + "js-sha3": "^0.9.3", + "jsesc": "^3.0.2", + "json5": "^2.2.3", + "jsonpath-plus": "^9.0.0", + "jsonwebtoken": "8.5.1", + "jsqr": "^1.4.0", + "jsrsasign": "^11.1.0", + "kbpgp": "2.1.15", + "libbzip2-wasm": "0.0.4", + "libyara-wasm": "^1.2.1", + "lodash": "^4.17.21", + "loglevel": "^1.9.1", + "loglevel-message-prefix": "^3.0.0", + "lz-string": "^1.5.0", + "lz4js": "^0.2.0", + "markdown-it": "^14.1.0", + "moment": "^2.30.1", + "moment-timezone": "^0.5.45", + "ngeohash": "^0.6.3", + "node-forge": "^1.3.1", + "node-md6": "^0.1.0", + "nodom": "^2.4.0", + "notepack.io": "^3.0.1", + "ntlm": "^0.1.3", + "nwmatcher": "^1.4.4", + "otpauth": "9.3.6", + "path": "^0.12.7", + "popper.js": "^1.16.1", + "process": "^0.11.10", + "protobufjs": "^7.3.1", + "qr-image": "^3.2.0", + "reflect-metadata": "^0.2.2", + "rison": "^0.1.1", + "scryptsy": "^2.1.0", + "snackbarjs": "^1.1.0", + "sortablejs": "^1.15.2", + "split.js": "^1.6.5", + "ssdeep.js": "0.0.3", + "stream-browserify": "^3.0.0", + "tesseract.js": "5.1.0", + "ua-parser-js": "^1.0.38", + "unorm": "^1.6.0", + "utf8": "^3.0.0", + "vkbeautify": "^0.99.3", + "xpath": "0.0.34", + "xregexp": "^5.1.1", + "zlibjs": "^0.3.1" }, "scripts": { - "build": "grunt prod", - "test": "grunt test", - "docs": "grunt docs" + "start": "npx grunt dev", + "build": "npx grunt prod", + "node": "npx grunt node", + "repl": "node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-experimental-fetch --no-warnings src/node/repl.mjs", + "test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch --trace-uncaught tests/operations/index.mjs", + "testnodeconsumer": "npx grunt testnodeconsumer", + "testui": "npx grunt testui", + "testuidev": "npx nightwatch --env=dev", + "lint": "npx grunt lint", + "lint:grammar": "cspell ./src", + "postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup && npx grunt exec:fixJimpModule", + "newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs", + "minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs", + "getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'", + "setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..2d46543d --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,9 @@ +module.exports = { + plugins: [ + require("postcss-import"), + require("autoprefixer"), + require("postcss-css-variables")({ + preserve: true + }), + ] +}; diff --git a/src/.eslintrc.json b/src/.eslintrc.json deleted file mode 100755 index 677146b5..00000000 --- a/src/.eslintrc.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 6, - "ecmaFeatures": { - "impliedStrict": true - }, - "sourceType": "module" - }, - "env": { - "browser": true, - "es6": true, - "node": true - }, - "extends": "eslint:recommended", - "rules": { - // enable additional rules - "no-eval": "error", - "no-implied-eval": "error", - "dot-notation": "error", - "eqeqeq": ["error", "smart"], - "no-caller": "error", - "no-extra-bind": "error", - "no-unused-expressions": "error", - "no-useless-call": "error", - "no-useless-return": "error", - "radix": "warn", - - // 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]" - }], - "no-empty": ["error", { - "allowEmptyCatch": true - }], - - // disable rules from base configurations - "no-console": "off", - "no-control-regex": "off", - - // stylistic conventions - "brace-style": ["error", "1tbs"], - "block-spacing": "error", - "array-bracket-spacing": "error", - "comma-spacing": "error", - "comma-style": "error", - "computed-property-spacing": "error", - "no-trailing-spaces": "warn", - "eol-last": "error", - "func-call-spacing": "error", - "indent": ["error", 4, { - "ArrayExpression": "first", - "SwitchCase": 1 - }], - "linebreak-style": ["error", "unix"], - "quotes": ["error", "double", { - "avoidEscape": true - }], - "camelcase": ["error", { - "properties": "always" - }], - "semi": ["error", "always"], - "unicode-bom": "error", - "require-jsdoc": ["error", { - "require": { - "FunctionDeclaration": true, - "MethodDefinition": true, - "ClassDeclaration": true, - "ArrowFunctionExpression": true - } - }], - "keyword-spacing": ["error", { - "before": true, - "after": true - }], - "no-multiple-empty-lines": ["warn", { - "max": 2, - "maxEOF": 1, - "maxBOF": 0 - }], - "no-whitespace-before-property": "error", - "operator-linebreak": ["error", "after"], - "space-in-parens": "error" - }, - "globals": { - "$": false, - "jQuery": false, - "moment": false, - - "COMPILE_TIME": false, - "COMPILE_MSG": false - } -} diff --git a/src/core/Chef.js b/src/core/Chef.js deleted file mode 100755 index 8192640f..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 - */ -var 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 = function(inputText, recipeConfig, options, progress, step) { - var 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 = 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) { - var 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..ab8f83de --- /dev/null +++ b/src/core/Chef.mjs @@ -0,0 +1,183 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Dish from "./Dish.mjs"; +import Recipe from "./Recipe.mjs"; +import log from "loglevel"; +import { isWorkerEnvironment } from "./Utils.mjs"; + +/** + * 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 {string} [options.returnType] - What type to return the result as + * + * @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={}) { + log.debug("Chef baking"); + const startTime = Date.now(), + recipe = new Recipe(recipeConfig), + containsFc = recipe.containsFlowControl(); + let error = false, + progress = 0; + + if (containsFc && isWorkerEnvironment()) self.setOption("attemptHighlight", false); + + // Load data + const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING; + this.dish.set(input, type); + + try { + progress = await recipe.execute(this.dish, progress); + } catch (err) { + log.error(err); + error = { + displayStr: err.displayStr, + }; + progress = err.progress; + } + + // Create a raw version of the dish, unpresented + const rawDish = this.dish.clone(); + + // Present the raw result + await recipe.present(this.dish); + + const returnType = + this.dish.type === Dish.HTML ? Dish.HTML : + options?.returnType ? options.returnType : Dish.ARRAY_BUFFER; + + return { + dish: rawDish, + result: await this.dish.get(returnType), + type: Dish.enumLookup(this.dish.type), + progress: progress, + duration: Date.now() - 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 = Date.now(), + recipe = new Recipe(recipeConfig), + dish = new Dish(); + + try { + recipe.execute(dish); + } catch (err) { + // Suppress all errors + } + return Date.now() - 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") { + try { + pos = func(pos, highlights[i].args); + } catch (err) { + // Throw away highlighting errors + pos = []; + } + } + } + + 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); + } + + /** + * Gets the title of a dish and returns it + * + * @param {Dish} dish + * @param {number} [maxLength=100] + * @returns {string} + */ + async getDishTitle(dish, maxLength=100) { + const newDish = new Dish(dish); + return await newDish.getTitle(maxLength); + } + +} + +export default Chef; diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js new file mode 100644 index 00000000..a43993f9 --- /dev/null +++ b/src/core/ChefWorker.js @@ -0,0 +1,290 @@ +/** + * 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 Chef from "./Chef.mjs"; +import OperationConfig from "./config/OperationConfig.json" assert {type: "json"}; +import OpModules from "./config/modules/OpModules.mjs"; +import loglevelMessagePrefix from "loglevel-message-prefix"; + + +// Set up Chef instance +self.chef = new Chef(); + +self.OpModules = OpModules; +self.OperationConfig = OperationConfig; +self.inputNum = -1; + + +// Tell the app that the worker has loaded and is ready to operate +self.postMessage({ + action: "workerLoaded", + data: {} +}); + +/** + * Respond to message from parent thread. + * + * inputNum is optional and only used for baking multiple inputs. + * Defaults to -1 when one isn't sent with the bake message. + * + * Messages should have the following format: + * { + * action: "bake" | "silentBake", + * data: { + * input: {string}, + * recipeConfig: {[Object]}, + * options: {Object}, + * progress: {number}, + * step: {boolean}, + * [inputNum=-1]: {number} + * } + * } + */ +self.addEventListener("message", function(e) { + // Handle message + const r = e.data; + log.debug(`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 "getDishTitle": + getDishTitle(r.data); + break; + case "docURL": + // Used to set the URL of the current document so that scripts can be + // imported into an inline worker. + 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; + case "setLogPrefix": + loglevelMessagePrefix(log, { + prefixes: [], + staticPrefixes: [r.data] + }); + break; + default: + break; + } +}); + + +/** + * Baking handler + * + * @param {Object} data + */ +async function bake(data) { + // Ensure the relevant modules are loaded + self.loadRequiredModules(data.recipeConfig); + try { + self.inputNum = data.inputNum === undefined ? -1 : data.inputNum; + 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 + ); + + const transferable = (response.dish.value instanceof ArrayBuffer) ? + [response.dish.value] : + undefined; + + self.postMessage({ + action: "bakeComplete", + data: Object.assign(response, { + id: data.id, + inputNum: data.inputNum, + bakeId: data.bakeId + }) + }, transferable); + + } catch (err) { + self.postMessage({ + action: "bakeError", + data: { + error: err.message || err, + id: data.id, + inputNum: data.inputNum + } + }); + } + self.inputNum = -1; +} + + +/** + * 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); + const transferable = (data.type === "ArrayBuffer") ? [value] : undefined; + self.postMessage({ + action: "dishReturned", + data: { + value: value, + id: data.id + } + }, transferable); +} + + +/** + * Gets the dish title + * + * @param {object} data + * @param {Dish} data.dish + * @param {number} data.maxLength + * @param {number} data.id + */ +async function getDishTitle(data) { + const title = await self.chef.getDishTitle(data.dish, data.maxLength); + self.postMessage({ + action: "dishReturned", + data: { + value: title, + id: data.id + } + }); +} + + +/** + * 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 (!(module in OpModules)) { + log.info(`Loading ${module} module`); + self.sendStatusMessage(`Loading ${module} module`); + self.importScripts(`${self.docURL}/modules/${module}.js`); // lgtm [js/client-side-unvalidated-url-redirection] + self.sendStatusMessage(""); + } + }); +}; + + +/** + * Send status update to the app. + * + * @param {string} msg + */ +self.sendStatusMessage = function(msg) { + self.postMessage({ + action: "statusMessage", + data: { + message: msg, + inputNum: self.inputNum + } + }); +}; + + +/** + * Send progress update to the app. + * + * @param {number} progress + * @param {number} total + */ +self.sendProgressMessage = function(progress, total) { + self.postMessage({ + action: "progressMessage", + data: { + progress: progress, + total: total, + inputNum: self.inputNum + } + }); +}; + + +/** + * 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 fe880571..00000000 --- a/src/core/Dish.js +++ /dev/null @@ -1,207 +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. - */ -var 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()) { - var 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 (var 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..11b1ff9f --- /dev/null +++ b/src/core/Dish.mjs @@ -0,0 +1,569 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils, { isNodeEnvironment } from "./Utils.mjs"; +import DishError from "./errors/DishError.mjs"; +import BigNumber from "bignumber.js"; +import { detectFileType } from "./lib/FileType.mjs"; +import log from "loglevel"; + +import DishByteArray from "./dishTypes/DishByteArray.mjs"; +import DishBigNumber from "./dishTypes/DishBigNumber.mjs"; +import DishFile from "./dishTypes/DishFile.mjs"; +import DishHTML from "./dishTypes/DishHTML.mjs"; +import DishJSON from "./dishTypes/DishJSON.mjs"; +import DishListFile from "./dishTypes/DishListFile.mjs"; +import DishNumber from "./dishTypes/DishNumber.mjs"; +import DishString from "./dishTypes/DishString.mjs"; + + +/** + * The data being operated on by each operation. + */ +class Dish { + + /** + * Dish constructor + * + * @param {Dish || *} [dishOrInput=null] - A dish to clone OR an object + * literal to make into a dish + * @param {Enum} [type=null] (optional) - A type to accompany object + * literal input + */ + constructor(dishOrInput=null, type = null) { + this.value = new ArrayBuffer(0); + this.type = Dish.ARRAY_BUFFER; + + // Case: dishOrInput is dish object + if (dishOrInput && + Object.prototype.hasOwnProperty.call(dishOrInput, "value") && + Object.prototype.hasOwnProperty.call(dishOrInput, "type")) { + this.set(dishOrInput.value, dishOrInput.type); + // input and type defined separately + } else if (dishOrInput && type !== null) { + this.set(dishOrInput, type); + // No type declared, so infer it. + } else if (dishOrInput) { + const inferredType = Dish.typeEnum(dishOrInput.constructor.name); + this.set(dishOrInput, inferredType); + } + } + + + /** + * 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": + case "object": // object constructor name. To allow JSON input in node. + 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."); + } + } + + + /** + * Returns the value of the data in the type format specified. + * + * If running in a browser, get is asynchronous. + * + * @param {number} type - The data type of value, see Dish enums. + * @returns {* | Promise} - (Browser) A promise | (Node) value of dish in given type + */ + get(type) { + if (typeof type === "string") { + type = Dish.typeEnum(type); + } + + if (this.type !== type) { + + // Node environment => _translate is sync + if (isNodeEnvironment()) { + this._translate(type); + return this.value; + + // Browser environment => _translate is async + } else { + return new Promise((resolve, reject) => { + this._translate(type) + .then(() => { + resolve(this.value); + }) + .catch(reject); + }); + } + } + + return this.value; + } + + + /** + * 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), 25); + throw new DishError(`Data is not a valid ${Dish.enumLookup(type)}: ${sample}`); + } + } + + /** + * Returns the Dish as the given type, without mutating the original dish. + * + * If running in a browser, get is asynchronous. + * + * @Node + * + * @param {number} type - The data type of value, see Dish enums. + * @returns {Dish | Promise} - (Browser) A promise | (Node) value of dish in given type + */ + presentAs(type) { + const clone = this.clone(); + return clone.get(type); + } + + + /** + * Detects the MIME type of the current dish + * @returns {string} + */ + detectDishType() { + const data = new Uint8Array(this.value.slice(0, 2048)), + types = detectFileType(data); + + if (!types.length || !types[0].mime || !(types[0].mime === "text/plain")) { + return null; + } else { + return types[0].mime; + } + } + + + /** + * Returns the title of the data up to the specified length + * + * @param {number} maxLength - The maximum title length + * @returns {string} + */ + async getTitle(maxLength) { + let title = ""; + let cloned; + + switch (this.type) { + case Dish.FILE: + title = this.value.name; + break; + case Dish.LIST_FILE: + title = `${this.value.length} file(s)`; + break; + case Dish.JSON: + title = "application/json"; + break; + case Dish.NUMBER: + case Dish.BIG_NUMBER: + title = this.value.toString(); + break; + case Dish.ARRAY_BUFFER: + case Dish.BYTE_ARRAY: + title = this.detectDishType(); + if (title !== null) break; + // fall through if no mime type was detected + default: + try { + cloned = this.clone(); + cloned.value = cloned.value.slice(0, 256); + title = await cloned.get(Dish.STRING); + } catch (err) { + log.error(`${Dish.enumLookup(this.type)} cannot be sliced. ${err}`); + } + } + + return title.slice(0, maxLength); + } + + /** + * Validates that the value is the type that has been specified. + * May have to disable parts of BYTE_ARRAY validation if it effects performance. + * + * @returns {boolean} Whether the data is valid or not. + */ + valid() { + switch (this.type) { + case Dish.BYTE_ARRAY: + if (!(this.value instanceof Uint8Array) && !(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: + if (BigNumber.isBigNumber(this.value)) return true; + /* + If a BigNumber is passed between WebWorkers it is serialised as a JSON + object with a coefficient (c), exponent (e) and sign (s). We detect this + and reinitialise it as a BigNumber object. + */ + if (Object.keys(this.value).sort().equals(["c", "e", "s"])) { + const temp = new BigNumber(); + temp.c = this.value.c; + temp.e = this.value.e; + temp.s = this.value.s; + this.value = temp; + return true; + } + return false; + case Dish.JSON: + // All values can be serialised in some manner, so we return true in all cases + return true; + 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; + } + + /** + * Translates the data to the given type format. + * + * If running in the browser, _translate is asynchronous. + * + * @param {number} toType - The data type of value, see Dish enums. + * @returns {Promise || undefined} + */ + _translate(toType) { + log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); + + // Node environment => translate is sync + if (isNodeEnvironment()) { + this._toArrayBuffer(); + this.type = Dish.ARRAY_BUFFER; + this._fromArrayBuffer(toType); + + // Browser environment => translate is async + } else { + return new Promise((resolve, reject) => { + this._toArrayBuffer() + .then(() => this.type = Dish.ARRAY_BUFFER) + .then(() => { + this._fromArrayBuffer(toType); + resolve(); + }) + .catch(reject); + }); + } + + } + + /** + * Convert this.value to an ArrayBuffer + * + * If running in a browser, _toByteArray is asynchronous. + * + * @returns {Promise || undefined} + */ + _toArrayBuffer() { + // Using 'bind' here to allow this.value to be mutated within translation functions + const toByteArrayFuncs = { + browser: { + [Dish.STRING]: () => Promise.resolve(DishString.toArrayBuffer.bind(this)()), + [Dish.NUMBER]: () => Promise.resolve(DishNumber.toArrayBuffer.bind(this)()), + [Dish.HTML]: () => Promise.resolve(DishHTML.toArrayBuffer.bind(this)()), + [Dish.ARRAY_BUFFER]: () => Promise.resolve(), + [Dish.BIG_NUMBER]: () => Promise.resolve(DishBigNumber.toArrayBuffer.bind(this)()), + [Dish.JSON]: () => Promise.resolve(DishJSON.toArrayBuffer.bind(this)()), + [Dish.FILE]: () => DishFile.toArrayBuffer.bind(this)(), + [Dish.LIST_FILE]: () => Promise.resolve(DishListFile.toArrayBuffer.bind(this)()), + [Dish.BYTE_ARRAY]: () => Promise.resolve(DishByteArray.toArrayBuffer.bind(this)()), + }, + node: { + [Dish.STRING]: () => DishString.toArrayBuffer.bind(this)(), + [Dish.NUMBER]: () => DishNumber.toArrayBuffer.bind(this)(), + [Dish.HTML]: () => DishHTML.toArrayBuffer.bind(this)(), + [Dish.ARRAY_BUFFER]: () => {}, + [Dish.BIG_NUMBER]: () => DishBigNumber.toArrayBuffer.bind(this)(), + [Dish.JSON]: () => DishJSON.toArrayBuffer.bind(this)(), + [Dish.FILE]: () => DishFile.toArrayBuffer.bind(this)(), + [Dish.LIST_FILE]: () => DishListFile.toArrayBuffer.bind(this)(), + [Dish.BYTE_ARRAY]: () => DishByteArray.toArrayBuffer.bind(this)(), + } + }; + + try { + return toByteArrayFuncs[isNodeEnvironment() && "node" || "browser"][this.type](); + } catch (err) { + throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to ArrayBuffer: ${err}`); + } + } + + /** + * Convert this.value to the given type from ArrayBuffer + * + * @param {number} toType - the Dish enum to convert to + */ + _fromArrayBuffer(toType) { + + // Using 'bind' here to allow this.value to be mutated within translation functions + const toTypeFunctions = { + [Dish.STRING]: () => DishString.fromArrayBuffer.bind(this)(), + [Dish.NUMBER]: () => DishNumber.fromArrayBuffer.bind(this)(), + [Dish.HTML]: () => DishHTML.fromArrayBuffer.bind(this)(), + [Dish.ARRAY_BUFFER]: () => {}, + [Dish.BIG_NUMBER]: () => DishBigNumber.fromArrayBuffer.bind(this)(), + [Dish.JSON]: () => DishJSON.fromArrayBuffer.bind(this)(), + [Dish.FILE]: () => DishFile.fromArrayBuffer.bind(this)(), + [Dish.LIST_FILE]: () => DishListFile.fromArrayBuffer.bind(this)(), + [Dish.BYTE_ARRAY]: () => DishByteArray.fromArrayBuffer.bind(this)(), + }; + + try { + toTypeFunctions[toType](); + this.type = toType; + } catch (err) { + throw new DishError(`Error translating from ArrayBuffer to ${Dish.enumLookup(toType)}: ${err}`); + } + } + +} + + +/** + * 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 54541f74..00000000 --- a/src/core/FlowControl.js +++ /dev/null @@ -1,198 +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: function(state) { - var 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 = []; - - if (input) - inputs = input.split(splitDelim); - - // Create subOpList for each tranche to operate on - // (all remaining operations unless we encounter a Merge) - for (var i = state.progress + 1; i < opList.length; i++) { - if (opList[i].name === "Merge" && !opList[i].isDisabled()) { - break; - } else { - subOpList.push(opList[i]); - } - } - - var recipe = new Recipe(), - output = "", - progress = 0; - - recipe.addOperations(subOpList); - - // Run recipe over each tranche - for (i = 0; i < inputs.length; i++) { - var dish = new Dish(inputs[i], inputType); - try { - progress = 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) { - var 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) { - var 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; - }, - -}; - -export default FlowControl; diff --git a/src/core/Ingredient.js b/src/core/Ingredient.js deleted file mode 100755 index 2a3c36a9..00000000 --- a/src/core/Ingredient.js +++ /dev/null @@ -1,90 +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 - */ -var 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) { - 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": - var number = parseFloat(data); - if (isNaN(number)) { - var 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..319dfb15 --- /dev/null +++ b/src/core/Ingredient.mjs @@ -0,0 +1,132 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "./Utils.mjs"; +import {fromHex} from "./lib/Hex.mjs"; + +/** + * 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; + this.maxLength = null; + this.min = null; + this.max = null; + this.step = 1; + + 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; + this.maxLength = ingredientConfig.maxLength || null; + this.min = ingredientConfig.min; + this.max = ingredientConfig.max; + this.step = ingredientConfig.step; + } + + + /** + * 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": + if (data === null) return data; + 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 cf8932e2..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 - */ -var 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 (var a = 0; a < operationConfig.args.length; a++) { - var ingredientConfig = operationConfig.args[a]; - var 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() { - var ingredientConfig = []; - - for (var o = 0; o < this.ingList.length; o++) { - ingredientConfig.push(this.ingList[o].getConfig()); - } - - var 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 (var 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() { - var ingValues = []; - for (var 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..24739d3f --- /dev/null +++ b/src/core/Operation.mjs @@ -0,0 +1,322 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Dish from "./Dish.mjs"; +import Ingredient from "./Ingredient.mjs"; + +/** + * 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; + if (ing.maxLength) conf.maxLength = ing.maxLength; + if (typeof ing.min === "number") conf.min = ing.min; + if (typeof ing.max === "number") conf.max = ing.max; + if (ing.step) conf.step = ing.step; + 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 b0a39673..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 - */ -var 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 (var c = 0; c < recipeConfig.length; c++) { - var operationName = recipeConfig[c].op; - var operationConfig = OperationConfig[operationName]; - var 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() { - var recipeConfig = []; - - for (var 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 (var 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 (var 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) { - var 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 = function(dish, startFrom) { - startFrom = startFrom || 0; - var op, input, output, numJumps = 0; - - for (var 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 - var state = { - "progress" : i, - "dish" : dish, - "opList" : this.opList, - "numJumps" : numJumps - }; - - state = op.run(state); - i = state.progress; - numJumps = state.numJumps; - } else { - output = op.run(input, op.getIngValues()); - dish.set(output, op.outputType); - } - } catch (err) { - var 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) { - var 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..3ce40aa4 --- /dev/null +++ b/src/core/Recipe.mjs @@ -0,0 +1,346 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import OperationConfig from "./config/OperationConfig.json" assert {type: "json"}; +import OperationError from "./errors/OperationError.mjs"; +import Operation from "./Operation.mjs"; +import DishError from "./errors/DishError.mjs"; +import log from "loglevel"; +import { isWorkerEnvironment } from "./Utils.mjs"; + +// Cache container for modules +let modules = null; + +/** + * 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 || c.op === "Comment", + }); + }); + } + + + /** + * 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.mjs"); + 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 '${op.name}'`); + + if (isWorkerEnvironment()) { + self.sendStatusMessage(`Baking... (${i+1}/${this.opList.length})`); + self.sendProgressMessage(i + 1, this.opList.length); + } + + if (op.flowControl) { + // Package up the current state + 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 === "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 === "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 3794d972..00000000 --- a/src/core/Utils.js +++ /dev/null @@ -1,1325 +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"; - var 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; - var 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 (window && window.app && !window.app.options.treatAsUtf8) { - str = Utils.byteArrayToChars(Utils.strToByteArray(str)); - } - - var 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; - var 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) { - var alphArr = []; - - for (var i = 0; i < alphStr.length; i++) { - if (i < alphStr.length - 2 && - alphStr[i+1] === "-" && - alphStr[i] !== "\\") { - var start = Utils.ord(alphStr[i]), - end = Utils.ord(alphStr[i+2]); - - for (var 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, ""); - var byteArray = []; - for (var 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 ""; - var hexStr = ""; - for (var 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) { - var byteArray = new Array(str.length); - var 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) { - var wordArray = CryptoJS.enc.Utf8.parse(str), - byteArray = Utils.wordArrayToByteArray(wordArray); - - if (window && 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) { - var byteArray = new Array(str.length); - var 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 - var words = []; - for (var i = 0; i < byteArray.length; i++) { - words[i >>> 2] |= byteArray[i] << (24 - (i % 4) * 8); - } - var wordArray = new CryptoJS.lib.WordArray.init(words, byteArray.length), - str = CryptoJS.enc.Utf8.stringify(wordArray); - - if (window && 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 ""; - var str = ""; - for (var 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 []; - - var words = wordArray.words, - byteArray = []; - - for (var i = 0; i < wordArray.sigBytes; i++) { - byteArray.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff); - } - - return byteArray; - }, - - - /** - * Mapping of Unicode code points to Windows-1251 - * @private - * @constant - */ - UNIC_WIN1251_MAP: { - 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, - 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, - 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, - 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, - 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, - 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, - 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, - 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, - 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99, 100: 100, 101: 101, - 102: 102, 103: 103, 104: 104, 105: 105, 106: 106, 107: 107, 108: 108, 109: 109, 110: 110, - 111: 111, 112: 112, 113: 113, 114: 114, 115: 115, 116: 116, 117: 117, 118: 118, 119: 119, - 120: 120, 121: 121, 122: 122, 123: 123, 124: 124, 125: 125, 126: 126, 127: 127, 1027: 129, - 8225: 135, 1046: 198, 8222: 132, 1047: 199, 1168: 165, 1048: 200, 1113: 154, 1049: 201, - 1045: 197, 1050: 202, 1028: 170, 160: 160, 1040: 192, 1051: 203, 164: 164, 166: 166, - 167: 167, 169: 169, 171: 171, 172: 172, 173: 173, 174: 174, 1053: 205, 176: 176, 177: 177, - 1114: 156, 181: 181, 182: 182, 183: 183, 8221: 148, 187: 187, 1029: 189, 1056: 208, - 1057: 209, 1058: 210, 8364: 136, 1112: 188, 1115: 158, 1059: 211, 1060: 212, 1030: 178, - 1061: 213, 1062: 214, 1063: 215, 1116: 157, 1064: 216, 1065: 217, 1031: 175, 1066: 218, - 1067: 219, 1068: 220, 1069: 221, 1070: 222, 1032: 163, 8226: 149, 1071: 223, 1072: 224, - 8482: 153, 1073: 225, 8240: 137, 1118: 162, 1074: 226, 1110: 179, 8230: 133, 1075: 227, - 1033: 138, 1076: 228, 1077: 229, 8211: 150, 1078: 230, 1119: 159, 1079: 231, 1042: 194, - 1080: 232, 1034: 140, 1025: 168, 1081: 233, 1082: 234, 8212: 151, 1083: 235, 1169: 180, - 1084: 236, 1052: 204, 1085: 237, 1035: 142, 1086: 238, 1087: 239, 1088: 240, 1089: 241, - 1090: 242, 1036: 141, 1041: 193, 1091: 243, 1092: 244, 8224: 134, 1093: 245, 8470: 185, - 1094: 246, 1054: 206, 1095: 247, 1096: 248, 8249: 139, 1097: 249, 1098: 250, 1044: 196, - 1099: 251, 1111: 191, 1055: 207, 1100: 252, 1038: 161, 8220: 147, 1101: 253, 8250: 155, - 1102: 254, 8216: 145, 1103: 255, 1043: 195, 1105: 184, 1039: 143, 1026: 128, 1106: 144, - 8218: 130, 1107: 131, 8217: 146, 1108: 186, 1109: 190 - }, - - /** - * Mapping of Windows-1251 code points to Unicode - * @private - * @constant - */ - WIN1251_UNIC_MAP: { - 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, - 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, - 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, - 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, - 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, - 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, - 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, - 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, - 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99, 100: 100, 101: 101, - 102: 102, 103: 103, 104: 104, 105: 105, 106: 106, 107: 107, 108: 108, 109: 109, 110: 110, - 111: 111, 112: 112, 113: 113, 114: 114, 115: 115, 116: 116, 117: 117, 118: 118, 119: 119, - 120: 120, 121: 121, 122: 122, 123: 123, 124: 124, 125: 125, 126: 126, 127: 127, 160: 160, - 164: 164, 166: 166, 167: 167, 169: 169, 171: 171, 172: 172, 173: 173, 174: 174, 176: 176, - 177: 177, 181: 181, 182: 182, 183: 183, 187: 187, 168: 1025, 128: 1026, 129: 1027, - 170: 1028, 189: 1029, 178: 1030, 175: 1031, 163: 1032, 138: 1033, 140: 1034, 142: 1035, - 141: 1036, 161: 1038, 143: 1039, 192: 1040, 193: 1041, 194: 1042, 195: 1043, 196: 1044, - 197: 1045, 198: 1046, 199: 1047, 200: 1048, 201: 1049, 202: 1050, 203: 1051, 204: 1052, - 205: 1053, 206: 1054, 207: 1055, 208: 1056, 209: 1057, 210: 1058, 211: 1059, 212: 1060, - 213: 1061, 214: 1062, 215: 1063, 216: 1064, 217: 1065, 218: 1066, 219: 1067, 220: 1068, - 221: 1069, 222: 1070, 223: 1071, 224: 1072, 225: 1073, 226: 1074, 227: 1075, 228: 1076, - 229: 1077, 230: 1078, 231: 1079, 232: 1080, 233: 1081, 234: 1082, 235: 1083, 236: 1084, - 237: 1085, 238: 1086, 239: 1087, 240: 1088, 241: 1089, 242: 1090, 243: 1091, 244: 1092, - 245: 1093, 246: 1094, 247: 1095, 248: 1096, 249: 1097, 250: 1098, 251: 1099, 252: 1100, - 253: 1101, 254: 1102, 255: 1103, 184: 1105, 144: 1106, 131: 1107, 186: 1108, 190: 1109, - 179: 1110, 191: 1111, 188: 1112, 154: 1113, 156: 1114, 158: 1115, 157: 1116, 162: 1118, - 159: 1119, 165: 1168, 180: 1169, 150: 8211, 151: 8212, 145: 8216, 146: 8217, 130: 8218, - 147: 8220, 148: 8221, 132: 8222, 134: 8224, 135: 8225, 149: 8226, 133: 8230, 137: 8240, - 139: 8249, 155: 8250, 136: 8364, 185: 8470, 153: 8482 - }, - - - /** - * Converts a Unicode string to Windows-1251 encoding - * - * @param {string} unicStr - * @returns {string} String encoded in Windows-1251 - * - * @example - * // returns "îáíîâëåííàÿ òåõíè÷êà ïî Áîèíãó. îðèãèíàë ó ìåíÿ. çàáåðåòå êîãäà áóäåòå â ÊÈ" - * Utils.unicodeToWin1251("обновленная техничка по Боингу. оригинал у меня. заберете когда будете в КИ"); - */ - unicodeToWin1251: function(unicStr) { - var res = []; - - for (var i = 0; i < unicStr.length; i++) { - var ord = unicStr.charCodeAt(i); - if (!(ord in Utils.UNIC_WIN1251_MAP)) - throw "Character '" + unicStr.charAt(i) + "' isn't supported by Windows-1251"; - res.push(String.fromCharCode(Utils.UNIC_WIN1251_MAP[ord])); - } - - return res.join(""); - }, - - - /** - * Converts a Windows-1251 string to Unicode encoding - * - * @param {string} win1251Str - * @returns {string} String encoded in Unicode - * - * @example - * // returns "обновленная техничка по Боингу. оригинал у меня. заберете когда будете в КИ" - * Utils.unicodeToWin1251("îáíîâëåííàÿ òåõíè÷êà ïî Áîèíãó. îðèãèíàë ó ìåíÿ. çàáåðåòå êîãäà áóäåòå â ÊÈ"); - */ - win1251ToUnicode: function(win1251Str) { - var res = []; - - for (var i = 0; i < win1251Str.length; i++) { - var ord = win1251Str.charCodeAt(i); - if (!(ord in Utils.WIN1251_UNIC_MAP)) - throw "Character '" + win1251Str.charAt(i) + "' isn't supported by Windows-1251"; - res.push(String.fromCharCode(Utils.WIN1251_UNIC_MAP[ord])); - } - - return res.join(""); - }, - - - /** - * 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+/="; - var 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; - - var output = [], - chr1, chr2, chr3, - enc1, enc2, enc3, enc4, - i = 0; - - if (removeNonAlphChars) { - var 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; - var output = ""; - - for (var 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 ""; - - var output = []; - - for (var 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") { - var delimRegex = Utils.regexRep[delim]; - data = data.replace(delimRegex, ""); - } - - var output = []; - for (var 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) { - - var b, - ignoreNext = false, - inString = false, - cell = "", - line = [], - lines = []; - - for (var 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..53b8a92e --- /dev/null +++ b/src/core/operations/Bcrypt.mjs @@ -0,0 +1,56 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import bcrypt from "bcryptjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * 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 (isWorkerEnvironment()) + 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..8d9a6937 --- /dev/null +++ b/src/core/operations/BcryptCompare.mjs @@ -0,0 +1,58 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import bcrypt from "bcryptjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + + +/** + * 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 (isWorkerEnvironment()) + 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..600a7dc3 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; +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..29318a32 --- /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.mjs"; +import { genPolybiusSquare } from "../lib/Ciphers.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..db38a3f2 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { genPolybiusSquare } from "../lib/Ciphers.mjs"; + +/** + * 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..cd9f4568 --- /dev/null +++ b/src/core/operations/BitShiftLeft.mjs @@ -0,0 +1,77 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Amount", + "type": "number", + "value": 1 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const amount = args[0]; + input = new Uint8Array(input); + + return input.map(b => { + return (b << amount) & 0xff; + }).buffer; + } + + /** + * 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..2d70849e --- /dev/null +++ b/src/core/operations/BitShiftRight.mjs @@ -0,0 +1,84 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Amount", + "type": "number", + "value": 1 + }, + { + "name": "Type", + "type": "option", + "value": ["Logical shift", "Arithmetic shift"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const amount = args[0], + type = args[1], + mask = type === "Logical shift" ? 0 : 0x80; + input = new Uint8Array(input); + + return input.map(b => { + return (b >>> amount) ^ (b & mask); + }).buffer; + } + + /** + * 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 36d4373e..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]; - var result = [], - x = null, - k = null, - o = null; - - for (var 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) { - var 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) { - var 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; - - var output = "", - result, - resultUtf8; - - input = input.slice(sampleOffset, sampleOffset + sampleLength); - - if (crib !== "") { - regex = new RegExp(crib, "im"); - } - - - for (var 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) { - var 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) { - var 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) { - var 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) { - var 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) { - var 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..afb26007 --- /dev/null +++ b/src/core/operations/BlowfishDecrypt.mjs @@ -0,0 +1,99 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; +import OperationError from "../errors/OperationError.mjs"; +import { Blowfish } from "../lib/Blowfish.mjs"; + +/** + * 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", "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.convertToByteString(args[1].string, args[1].option), + mode = args[2], + inputType = args[3], + outputType = args[4]; + + if (key.length < 4 || key.length > 56) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`); + } + + if (mode !== "ECB" && iv.length !== 8) { + throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`); + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = Blowfish.createDecipher(key, mode); + 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 BlowfishDecrypt; diff --git a/src/core/operations/BlowfishEncrypt.mjs b/src/core/operations/BlowfishEncrypt.mjs new file mode 100644 index 00000000..1d5dcf02 --- /dev/null +++ b/src/core/operations/BlowfishEncrypt.mjs @@ -0,0 +1,99 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; +import OperationError from "../errors/OperationError.mjs"; +import { Blowfish } from "../lib/Blowfish.mjs"; + +/** + * 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", "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.convertToByteString(args[1].string, args[1].option), + mode = args[2], + inputType = args[3], + outputType = args[4]; + + if (key.length < 4 || key.length > 56) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`); + } + + if (mode !== "ECB" && iv.length !== 8) { + throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`); + } + + input = Utils.convertToByteString(input, inputType); + + const cipher = Blowfish.createCipher(key, mode); + cipher.start({iv: iv}); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + if (outputType === "Hex") { + return cipher.output.toHex(); + } else { + return cipher.output.getBytes(); + } + } + +} + +export default BlowfishEncrypt; diff --git a/src/core/operations/BlurImage.mjs b/src/core/operations/BlurImage.mjs new file mode 100644 index 00000000..17904180 --- /dev/null +++ b/src/core/operations/BlurImage.mjs @@ -0,0 +1,112 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { gaussianBlur } from "../lib/ImageManipulation.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Blur Image operation + */ +class BlurImage extends Operation { + + /** + * BlurImage constructor + */ + constructor() { + super(); + + this.name = "Blur Image"; + this.module = "Image"; + this.description = "Applies a blur effect to the image.

Gaussian blur is much slower than fast blur, but produces better results."; + this.infoURL = "https://wikipedia.org/wiki/Gaussian_blur"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Amount", + type: "number", + value: 5, + min: 1 + }, + { + name: "Type", + type: "option", + value: ["Fast", "Gaussian"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [blurAmount, blurType] = args; + + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + switch (blurType) { + case "Fast": + if (isWorkerEnvironment()) + self.sendStatusMessage("Fast blurring image..."); + image.blur(blurAmount); + break; + case "Gaussian": + if (isWorkerEnvironment()) + self.sendStatusMessage("Gaussian blurring image..."); + image = gaussianBlur(image, blurAmount); + break; + } + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error blurring image. (${err})`); + } + } + + /** + * Displays the blurred image using HTML for web apps + * + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default BlurImage; diff --git a/src/core/operations/Bombe.mjs b/src/core/operations/Bombe.mjs new file mode 100644 index 00000000..a635b430 --- /dev/null +++ b/src/core/operations/Bombe.mjs @@ -0,0 +1,179 @@ +/** + * Emulation of the Bombe machine. + * + * Tested against the Bombe Rebuild at Bletchley Park's TNMOC + * using a variety of inputs and settings to confirm correctness. + * + * @author s2224834 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import { BombeMachine } from "../lib/Bombe.mjs"; +import { ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector } from "../lib/Enigma.mjs"; + +/** + * Bombe operation + */ +class Bombe extends Operation { + /** + * Bombe constructor + */ + constructor() { + super(); + + this.name = "Bombe"; + this.module = "Bletchley"; + this.description = "Emulation of the Bombe machine used at Bletchley Park to attack Enigma, based on work by Polish and British cryptanalysts.

To run this you need to have a 'crib', which is some known plaintext for a chunk of the target ciphertext, and know the rotors used. (See the 'Bombe (multiple runs)' operation if you don't know the rotors.) The machine will suggest possible configurations of the Enigma. Each suggestion has the rotor start positions (left to right) and known plugboard pairs.

Choosing a crib: First, note that Enigma cannot encrypt a letter to itself, which allows you to rule out some positions for possible cribs. Secondly, the Bombe does not simulate the Enigma's middle rotor stepping. The longer your crib, the more likely a step happened within it, which will prevent the attack working. However, other than that, longer cribs are generally better. The attack produces a 'menu' which maps ciphertext letters to plaintext, and the goal is to produce 'loops': for example, with ciphertext ABC and crib CAB, we have the mappings A<->C, B<->A, and C<->B, which produces a loop A-B-C-A. The more loops, the better the crib. The operation will output this: if your menu has too few loops or is too short, a large number of incorrect outputs will usually be produced. Try a different crib. If the menu seems good but the right answer isn't produced, your crib may be wrong, or you may have overlapped the middle rotor stepping - try a different crib.

Output is not sufficient to fully decrypt the data. You will have to recover the rest of the plugboard settings by inspection. And the ring position is not taken into account: this affects when the middle rotor steps. If your output is correct for a bit, and then goes wrong, adjust the ring and start position on the right-hand rotor together until the output improves. If necessary, repeat for the middle rotor.

By default this operation runs the checking machine, a manual process to verify the quality of Bombe stops, on each stop, discarding stops which fail. If you want to see how many times the hardware actually stops for a given input, disable the checking machine.

More detailed descriptions of the Enigma, Typex and Bombe operations
can be found here."; + this.infoURL = "https://wikipedia.org/wiki/Bombe"; + this.inputType = "string"; + this.outputType = "JSON"; + this.presentType = "html"; + this.args = [ + { + name: "Model", + type: "argSelector", + value: [ + { + name: "3-rotor", + off: [1] + }, + { + name: "4-rotor", + on: [1] + } + ] + }, + { + name: "Left-most (4th) rotor", + type: "editableOption", + value: ROTORS_FOURTH, + defaultIndex: 0 + }, + { + name: "Left-hand rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 0 + }, + { + name: "Middle rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 1 + }, + { + name: "Right-hand rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 2 + }, + { + name: "Reflector", + type: "editableOption", + value: REFLECTORS + }, + { + name: "Crib", + type: "string", + value: "" + }, + { + name: "Crib offset", + type: "number", + value: 0 + }, + { + name: "Use checking machine", + type: "boolean", + value: true + } + ]; + } + + /** + * Format and send a status update message. + * @param {number} nLoops - Number of loops in the menu + * @param {number} nStops - How many stops so far + * @param {number} progress - Progress (as a float in the range 0..1) + */ + updateStatus(nLoops, nStops, progress) { + const msg = `Bombe run with ${nLoops} loop${nLoops === 1 ? "" : "s"} in menu (2+ desirable): ${nStops} stops, ${Math.floor(100 * progress)}% done`; + self.sendStatusMessage(msg); + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const model = args[0]; + const reflectorstr = args[5]; + let crib = args[6]; + const offset = args[7]; + const check = args[8]; + const rotors = []; + for (let i=0; i<4; i++) { + if (i === 0 && model === "3-rotor") { + // No fourth rotor + continue; + } + let rstr = args[i + 1]; + // The Bombe doesn't take stepping into account so we'll just ignore it here + if (rstr.includes("<")) { + rstr = rstr.split("<", 2)[0]; + } + rotors.push(rstr); + } + // Rotors are handled in reverse + rotors.reverse(); + if (crib.length === 0) { + throw new OperationError("Crib cannot be empty"); + } + if (offset < 0) { + throw new OperationError("Offset cannot be negative"); + } + // For symmetry with the Enigma op, for the input we'll just remove all invalid characters + input = input.replace(/[^A-Za-z]/g, "").toUpperCase(); + crib = crib.replace(/[^A-Za-z]/g, "").toUpperCase(); + const ciphertext = input.slice(offset); + const reflector = new Reflector(reflectorstr); + let update; + if (isWorkerEnvironment()) { + update = this.updateStatus; + } else { + update = undefined; + } + const bombe = new BombeMachine(rotors, reflector, ciphertext, crib, check, update); + const result = bombe.run(); + return { + nLoops: bombe.nLoops, + result: result + }; + } + + + /** + * Displays the Bombe results in an HTML table + * + * @param {Object} output + * @param {number} output.nLoops + * @param {Array[]} output.result + * @returns {html} + */ + present(output) { + let html = `Bombe run on menu with ${output.nLoops} loop${output.nLoops === 1 ? "" : "s"} (2+ desirable). Note: Rotor positions are listed left to right and start at the beginning of the crib, and ignore stepping and the ring setting. Some plugboard settings are determined. A decryption preview starting at the beginning of the crib and ignoring stepping is also provided.\n\n`; + html += "\n"; + for (const [setting, stecker, decrypt] of output.result) { + html += `\n`; + } + html += "
Rotor stops Partial plugboard Decryption preview
${setting} ${stecker} ${decrypt}
"; + return html; + } +} + +export default Bombe; diff --git a/src/core/operations/ByteRepr.js b/src/core/operations/ByteRepr.js deleted file mode 100755 index a439d1e1..00000000 --- a/src/core/operations/ByteRepr.js +++ /dev/null @@ -1,398 +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) { - var 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) { - var delim = args[0] || "Space"; - return Utils.fromHex(input, delim, 2); - }, - - - /** - * @constant - * @default - */ - CHARCODE_BASE: 16, - - /** - * To Charcode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToCharcode: function(input, args) { - var 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 (var 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) { - var 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)); - } - } - - var 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) { - var 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) { - var 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) { - var delim = Utils.charRep[args[0]]; - return input.join(delim); - }, - - - /** - * From Decimal operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromDecimal: function(input, args) { - var delim = Utils.charRep[args[0]]; - var byteStr = input.split(delim), output = []; - if (byteStr[byteStr.length-1] === "") - byteStr = byteStr.slice(0, byteStr.length-1); - - for (var 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) { - var delim = Utils.charRep[args[0] || "Space"], - output = "", - padding = 8; - - for (var 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") { - var delimRegex = Utils.regexRep[args[0] || "Space"]; - input = input.replace(delimRegex, ""); - } - - var output = []; - var byteLen = 8; - for (var 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) { - var 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) { - var 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) { - var convert = args[0]; - var spaces = args[1]; - if (convert === "All chars") { - var result = "|" + Utils.toHex(input) + "|"; - if (!spaces) result = result.replace(/ /g, ""); - return result; - } - - var output = "", - inHex = false, - convertSpaces = convert === "Only special chars including spaces", - b; - for (var 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) { - var regex = /\|([a-f\d ]{2,})\|/gi; - var 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 - var bytes = Utils.fromHex(m[1]); - if (bytes) { - for (var 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/Bzip2Compress.mjs b/src/core/operations/Bzip2Compress.mjs new file mode 100644 index 00000000..45dbfae6 --- /dev/null +++ b/src/core/operations/Bzip2Compress.mjs @@ -0,0 +1,73 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Bzip2 from "libbzip2-wasm"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * Bzip2 Compress operation + */ +class Bzip2Compress extends Operation { + + /** + * Bzip2Compress constructor + */ + constructor() { + super(); + + this.name = "Bzip2 Compress"; + this.module = "Compression"; + this.description = "Bzip2 is a compression library developed by Julian Seward (of GHC fame) that uses the Burrows-Wheeler algorithm. It only supports compressing single files and its compression is slow, however is more effective than Deflate (.gz & .zip)."; + this.infoURL = "https://wikipedia.org/wiki/Bzip2"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Block size (100s of kb)", + type: "number", + value: 9, + min: 1, + max: 9 + }, + { + name: "Work factor", + type: "number", + value: 30 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {File} + */ + run(input, args) { + const [blockSize, workFactor] = args; + if (input.byteLength <= 0) { + throw new OperationError("Please provide an input."); + } + if (isWorkerEnvironment()) self.sendStatusMessage("Loading Bzip2..."); + return new Promise((resolve, reject) => { + Bzip2().then(bzip2 => { + if (isWorkerEnvironment()) self.sendStatusMessage("Compressing data..."); + const inpArray = new Uint8Array(input); + const bzip2cc = bzip2.compressBZ2(inpArray, blockSize, workFactor); + if (bzip2cc.error !== 0) { + reject(new OperationError(bzip2cc.error_msg)); + } else { + const output = bzip2cc.output; + resolve(output.buffer.slice(output.byteOffset, output.byteLength + output.byteOffset)); + } + }); + }); + } + +} + +export default Bzip2Compress; diff --git a/src/core/operations/Bzip2Decompress.mjs b/src/core/operations/Bzip2Decompress.mjs new file mode 100644 index 00000000..fe419265 --- /dev/null +++ b/src/core/operations/Bzip2Decompress.mjs @@ -0,0 +1,73 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Bzip2 from "libbzip2-wasm"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * Bzip2 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 = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Use low-memory, slower decompression algorithm", + type: "boolean", + value: false + } + ]; + this.checks = [ + { + "pattern": "^\\x42\\x5a\\x68", + "flags": "", + "args": [] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [small] = args; + if (input.byteLength <= 0) { + throw new OperationError("Please provide an input."); + } + if (isWorkerEnvironment()) self.sendStatusMessage("Loading Bzip2..."); + return new Promise((resolve, reject) => { + Bzip2().then(bzip2 => { + if (isWorkerEnvironment()) self.sendStatusMessage("Decompressing data..."); + const inpArray = new Uint8Array(input); + const bzip2cc = bzip2.decompressBZ2(inpArray, small ? 1 : 0); + if (bzip2cc.error !== 0) { + reject(new OperationError(bzip2cc.error_msg)); + } else { + const output = bzip2cc.output; + resolve(output.buffer.slice(output.byteOffset, output.byteLength + output.byteOffset)); + } + }); + }); + } + +} + +export default Bzip2Decompress; diff --git a/src/core/operations/CBORDecode.mjs b/src/core/operations/CBORDecode.mjs new file mode 100644 index 00000000..f4bda7c0 --- /dev/null +++ b/src/core/operations/CBORDecode.mjs @@ -0,0 +1,41 @@ +/** + * @author Danh4 [dan.h4@ncsc.gov.uk] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Cbor from "cbor"; + +/** + * CBOR Decode operation + */ +class CBORDecode extends Operation { + + /** + * CBORDecode constructor + */ + constructor() { + super(); + + this.name = "CBOR Decode"; + this.module = "Serialise"; + this.description = "Concise Binary Object Representation (CBOR) is a binary data serialization format loosely based on JSON. Like JSON it allows the transmission of data objects that contain name–value pairs, but in a more concise manner. This increases processing and transfer speeds at the cost of human readability. It is defined in IETF RFC 8949."; + this.infoURL = "https://wikipedia.org/wiki/CBOR"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + return Cbor.decodeFirstSync(Buffer.from(input).toString("hex")); + } + +} + +export default CBORDecode; diff --git a/src/core/operations/CBOREncode.mjs b/src/core/operations/CBOREncode.mjs new file mode 100644 index 00000000..c6e094a9 --- /dev/null +++ b/src/core/operations/CBOREncode.mjs @@ -0,0 +1,41 @@ +/** + * @author Danh4 [dan.h4@ncsc.gov.uk] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Cbor from "cbor"; + +/** + * CBOR Encode operation + */ +class CBOREncode extends Operation { + + /** + * CBOREncode constructor + */ + constructor() { + super(); + + this.name = "CBOR Encode"; + this.module = "Serialise"; + this.description = "Concise Binary Object Representation (CBOR) is a binary data serialization format loosely based on JSON. Like JSON it allows the transmission of data objects that contain name–value pairs, but in a more concise manner. This increases processing and transfer speeds at the cost of human readability. It is defined in IETF RFC 8949."; + this.infoURL = "https://wikipedia.org/wiki/CBOR"; + this.inputType = "JSON"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + return new Uint8Array(Cbor.encodeCanonical(input)).buffer; + } + +} + +export default CBOREncode; diff --git a/src/core/operations/CMAC.mjs b/src/core/operations/CMAC.mjs new file mode 100644 index 00000000..d6491362 --- /dev/null +++ b/src/core/operations/CMAC.mjs @@ -0,0 +1,149 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; +import { toHexFast } from "../lib/Hex.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * CMAC operation + */ +class CMAC extends Operation { + + /** + * CMAC constructor + */ + constructor() { + super(); + + this.name = "CMAC"; + this.module = "Crypto"; + this.description = "CMAC is a block-cipher based message authentication code algorithm.

RFC4493 defines AES-CMAC that uses AES encryption with a 128-bit key.
NIST SP 800-38B suggests usages of AES with other key lengths and Triple DES."; + this.infoURL = "https://wikipedia.org/wiki/CMAC"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Encryption algorithm", + "type": "option", + "value": ["AES", "Triple DES"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option); + const algo = args[1]; + + const info = (function() { + switch (algo) { + case "AES": + if (key.length !== 16 && key.length !== 24 && key.length !== 32) { + throw new OperationError("The key for AES must be either 16, 24, or 32 bytes (currently " + key.length + " bytes)"); + } + return { + "algorithm": "AES-ECB", + "key": key, + "blockSize": 16, + "Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]), + }; + case "Triple DES": + if (key.length !== 16 && key.length !== 24) { + throw new OperationError("The key for Triple DES must be 16 or 24 bytes (currently " + key.length + " bytes)"); + } + return { + "algorithm": "3DES-ECB", + "key": key.length === 16 ? key + key.substring(0, 8) : key, + "blockSize": 8, + "Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]), + }; + default: + throw new OperationError("Undefined encryption algorithm"); + } + })(); + + const xor = function(a, b, out) { + if (!out) out = new Uint8Array(a.length); + for (let i = 0; i < a.length; i++) { + out[i] = a[i] ^ b[i]; + } + return out; + }; + + const leftShift1 = function(a) { + const out = new Uint8Array(a.length); + let carry = 0; + for (let i = a.length - 1; i >= 0; i--) { + out[i] = (a[i] << 1) | carry; + carry = a[i] >> 7; + } + return out; + }; + + const cipher = forge.cipher.createCipher(info.algorithm, info.key); + const encrypt = function(a, out) { + if (!out) out = new Uint8Array(a.length); + cipher.start(); + cipher.update(forge.util.createBuffer(a)); + cipher.finish(); + const cipherText = cipher.output.getBytes(); + for (let i = 0; i < a.length; i++) { + out[i] = cipherText.charCodeAt(i); + } + return out; + }; + + const L = encrypt(new Uint8Array(info.blockSize)); + const K1 = leftShift1(L); + if (L[0] & 0x80) xor(K1, info.Rb, K1); + const K2 = leftShift1(K1); + if (K1[0] & 0x80) xor(K2, info.Rb, K2); + + const n = Math.ceil(input.byteLength / info.blockSize); + const lastBlock = (function() { + if (n === 0) { + const data = new Uint8Array(K2); + data[0] ^= 0x80; + return data; + } + const inputLast = new Uint8Array(input, info.blockSize * (n - 1)); + if (inputLast.length === info.blockSize) { + return xor(inputLast, K1, inputLast); + } else { + const data = new Uint8Array(info.blockSize); + data.set(inputLast, 0); + data[inputLast.length] = 0x80; + return xor(data, K2, data); + } + })(); + + const X = new Uint8Array(info.blockSize); + const Y = new Uint8Array(info.blockSize); + for (let i = 0; i < n - 1; i++) { + xor(X, new Uint8Array(input, info.blockSize * i, info.blockSize), Y); + encrypt(Y, X); + } + xor(lastBlock, X, Y); + const T = encrypt(Y); + return toHexFast(T); + } + +} + +export default CMAC; diff --git a/src/core/operations/CRCChecksum.mjs b/src/core/operations/CRCChecksum.mjs new file mode 100644 index 00000000..88da2fa5 --- /dev/null +++ b/src/core/operations/CRCChecksum.mjs @@ -0,0 +1,1110 @@ +/** + * @author r4mos [2k95ljkhg@mozmail.com] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * CRC Checksum operation + */ +class CRCChecksum extends Operation { + + /** + * CRCChecksum constructor + */ + constructor() { + super(); + + this.name = "CRC Checksum"; + this.module = "Default"; + 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."; + this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "Custom", + on: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-3/GSM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-3/ROHC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-4/G-704", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-4/INTERLAKEN", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-4/ITU", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-5/EPC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-5/EPC-C1G2", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-5/G-704", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-5/ITU", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-5/USB", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-6/CDMA2000-A", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-6/CDMA2000-B", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-6/DARC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-6/G-704", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-6/GSM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-6/ITU", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-7/MMC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-7/ROHC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-7/UMTS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/8H2F", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/AES", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/AUTOSAR", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/BLUETOOTH", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/CDMA2000", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/DARC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/DVB-S2", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/EBU", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/GSM-A", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/GSM-B", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/HITAG", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/I-432-1", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/I-CODE", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/ITU", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/LTE", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/MAXIM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/MAXIM-DOW", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/MIFARE-MAD", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/NRSC-5", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/OPENSAFETY", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/ROHC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/SAE-J1850", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/SAE-J1850-ZERO", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/SMBUS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/TECH-3250", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-8/WCDMA", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-10/ATM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-10/CDMA2000", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-10/GSM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-10/I-610", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-11/FLEXRAY", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-11/UMTS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-12/3GPP", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-12/CDMA2000", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-12/DECT", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-12/GSM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-12/UMTS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-13/BBC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-14/DARC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-14/GSM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-15/CAN", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-15/MPT1327", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/A", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/ACORN", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/ARC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/AUG-CCITT", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/AUTOSAR", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/B", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/BLUETOOTH", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/BUYPASS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/CCITT", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/CCITT-FALSE", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/CCITT-TRUE", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/CCITT-ZERO", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/CDMA2000", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/CMS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/DARC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/DDS-110", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/DECT-R", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/DECT-X", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/DNP", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/EN-13757", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/EPC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/EPC-C1G2", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/GENIBUS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/GSM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/I-CODE", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/IBM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/IBM-3740", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/IBM-SDLC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/IEC-61158-2", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/ISO-HDLC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/ISO-IEC-14443-3-A", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/ISO-IEC-14443-3-B", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/KERMIT", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/LHA", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/LJ1200", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/LTE", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/M17", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/MAXIM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/MAXIM-DOW", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/MCRF4XX", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/MODBUS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/NRSC-5", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/OPENSAFETY-A", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/OPENSAFETY-B", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/PROFIBUS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/RIELLO", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/SPI-FUJITSU", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/T10-DIF", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/TELEDISK", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/TMS37157", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/UMTS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/USB", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/V-41-LSB", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/V-41-MSB", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/VERIFONE", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/X-25", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/XMODEM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-16/ZMODEM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-17/CAN-FD", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-21/CAN-FD", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-24/BLE", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-24/FLEXRAY-A", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-24/FLEXRAY-B", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-24/INTERLAKEN", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-24/LTE-A", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-24/LTE-B", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-24/OPENPGP", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-24/OS-9", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-30/CDMA", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-31/PHILIPS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/AAL5", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/ADCCP", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/AIXM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/AUTOSAR", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/BASE91-C", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/BASE91-D", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/BZIP2", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/C", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/CASTAGNOLI", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/CD-ROM-EDC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/CKSUM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/D", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/DECT-B", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/INTERLAKEN", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/ISCSI", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/ISO-HDLC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/JAMCRC", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/MEF", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/MPEG-2", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/NVME", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/PKZIP", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/POSIX", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/Q", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/SATA", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/V-42", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/XFER", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-32/XZ", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-40/GSM", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-64/ECMA-182", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-64/GO-ECMA", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-64/GO-ISO", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-64/MS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-64/NVME", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-64/REDIS", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-64/WE", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-64/XZ", + off: [1, 2, 3, 4, 5, 6] + }, + { + name: "CRC-82/DARC", + off: [1, 2, 3, 4, 5, 6] + } + ] + }, + { + name: "Width (bits)", + type: "toggleString", + value: "0", + toggleValues: ["Decimal"] + }, + { + name: "Polynomial", + type: "toggleString", + value: "0", + toggleValues: ["Hex"] + }, + { + name: "Initialization", + type: "toggleString", + value: "0", + toggleValues: ["Hex"] + }, + { + name: "Reflect input", + type: "option", + value: ["True", "False"] + }, + { + name: "Reflect output", + type: "option", + value: ["True", "False"] + }, + { + name: "Xor Output", + type: "toggleString", + value: "0", + toggleValues: ["Hex"] + } + ]; + } + + /** + * Reverse the order of bits in a number + * + * @param {BigInt} data + * @param {BigInt} reflect + */ + reflectData(data, reflect) { + let value = 0n; + for (let bit = 0n; bit < reflect; bit++) { + if ((data & 1n) === 1n) { + value |= (1n << ((reflect - 1n) - bit)); + } + data >>= 1n; + } + return value; + } + + /** + * Performs the CRC Checksum calculation bit per bit without acceleration + * + * @param {BigInt} width + * @param {ArrayBuffer} input + * @param {BigInt} poly + * @param {BigInt} remainder + * @param {boolean} reflectIn + * @param {boolean} reflectOut + * @param {BigInt} xorOut + */ + calculateCrcBitPerBit(width, input, poly, remainder, reflectIn, reflectOut, xorOut) { + const TOP_BIT = 1n << (width - 1n); + const MASK = (1n << width) - 1n; + + for (let byte of input) { + byte = BigInt(byte); + if (reflectIn) { + byte = this.reflectData(byte, 8n); + } + + for (let i = 0x80n; i !== 0n; i >>= 1n) { + let bit = remainder & TOP_BIT; + + remainder = (remainder << 1n) & MASK; + + if ((byte & i) !== 0n) { + bit ^= TOP_BIT; + } + + if (bit !== 0n) { + remainder ^= poly; + } + } + } + + if (reflectOut) { + remainder = this.reflectData(remainder, width); + } + + return remainder ^ xorOut; + } + + /** + * Generates the necessary table to speed up the calculation + * + * @param {BigInt} width + * @param {BigInt} poly + * @param {BigInt} MASK + * @param {BigInt} TOP_BIT + */ + generateTable(width, poly, MASK, TOP_BIT) { + const table = new Array(256n); + for (let byte = 0n; byte < 256n; byte++) { + let value = ((byte << width - 8n) & MASK); + for (let bit = 0n; bit < 8n; bit++) { + value = (value & TOP_BIT) === 0n ? + ((value << 1n) & MASK) : + ((value << 1n) & MASK) ^ poly; + } + table[byte] = value; + } + return table; + } + + /** + * Performs the CRC Checksum calculation byte per byte using a computed table to accelerate it + * + * @param {BigInt} width + * @param {ArrayBuffer} input + * @param {BigInt} poly + * @param {BigInt} remainder + * @param {boolean} reflectIn + * @param {boolean} reflectOut + * @param {BigInt} xorOut + */ + calculateCrcBytePerByte(width, input, poly, remainder, reflectIn, reflectOut, xorOut) { + const TOP_BIT = 1n << (width - 1n); + const MASK = (1n << width) - 1n; + const TABLE = this.generateTable(width, poly, MASK, TOP_BIT); + + for (let byte of input) { + byte = BigInt(byte); + if (reflectIn) { + byte = this.reflectData(byte, 8n); + } + remainder ^= (byte << width - 8n) & MASK; + + const INDEX = remainder >> width - 8n; + remainder = (remainder << 8n) & MASK; + remainder ^= TABLE[INDEX]; + } + + if (reflectOut) { + remainder = this.reflectData(remainder, width); + } + return remainder ^ xorOut; + } + + /** + * Calculates the CRC Checksum using Bigint (https://developer.mozilla.org/en-US/docs/Glossary/BigInt) + * + * @param {BigInt} width + * @param {ArrayBuffer} input + * @param {BigInt} poly + * @param {BigInt} init + * @param {boolean} reflectIn + * @param {boolean} reflectOut + * @param {BigInt} xorOut + */ + crc(width, input, poly, init, reflectIn, reflectOut, xorOut) { + const VALUE = width < 8n ? + this.calculateCrcBitPerBit(width, input, poly, init, reflectIn, reflectOut, xorOut) : + this.calculateCrcBytePerByte(width, input, poly, init, reflectIn, reflectOut, xorOut); + + return VALUE.toString(16).padStart(Math.ceil(Number(width) / 4), "0"); + } + + /** + * Validates user input to perform a custom CRC + * + * @param {Object} widthObject + * @param {ArrayBuffer} input + * @param {Object} polyObject + * @param {Object} initObject + * @param {Object} reflectInObject + * @param {Object} reflectOutObject + * @param {Object} xorOutObject + */ + custom(widthObject, input, polyObject, initObject, reflectInObject, reflectOutObject, xorOutObject) { + try { + const width = BigInt(widthObject.string); + const poly = BigInt("0x" + polyObject.string); + const init = BigInt("0x" + initObject.string); + const reflectIn = reflectInObject === "True"; + const reflectOut = reflectOutObject === "True"; + const xorOut = BigInt("0x" + xorOutObject.string); + + return this.crc(width, input, poly, init, reflectIn, reflectOut, xorOut); + } catch (error) { + throw new OperationError("Invalid custom CRC arguments"); + } + } + + /** + * Calculation of all known CRCs. Names and constants extracted from https://reveng.sourceforge.io/crc-catalogue/all.htm + * + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const algorithm = args[0]; + input = new Uint8Array(input); + + switch (algorithm) { + case "Custom": return this.custom(args[1], input, args[2], args[3], args[4], args[5], args[6]); + case "CRC-3/GSM": return this.crc(3n, input, 0x3n, 0x0n, false, false, 0x7n); + case "CRC-3/ROHC": return this.crc(3n, input, 0x3n, 0x7n, true, true, 0x0n); + case "CRC-4/G-704": return this.crc(4n, input, 0x3n, 0x0n, true, true, 0x0n); + case "CRC-4/INTERLAKEN": return this.crc(4n, input, 0x3n, 0xFn, false, false, 0xFn); + case "CRC-4/ITU": return this.crc(4n, input, 0x3n, 0x0n, true, true, 0x0n); + case "CRC-5/EPC": return this.crc(5n, input, 0x09n, 0x09n, false, false, 0x00n); + case "CRC-5/EPC-C1G2": return this.crc(5n, input, 0x09n, 0x09n, false, false, 0x00n); + case "CRC-5/G-704": return this.crc(5n, input, 0x15n, 0x00n, true, true, 0x00n); + case "CRC-5/ITU": return this.crc(5n, input, 0x15n, 0x00n, true, true, 0x00n); + case "CRC-5/USB": return this.crc(5n, input, 0x05n, 0x1Fn, true, true, 0x1Fn); + case "CRC-6/CDMA2000-A": return this.crc(6n, input, 0x27n, 0x3Fn, false, false, 0x00n); + case "CRC-6/CDMA2000-B": return this.crc(6n, input, 0x07n, 0x3Fn, false, false, 0x00n); + case "CRC-6/DARC": return this.crc(6n, input, 0x19n, 0x00n, true, true, 0x00n); + case "CRC-6/G-704": return this.crc(6n, input, 0x03n, 0x00n, true, true, 0x00n); + case "CRC-6/GSM": return this.crc(6n, input, 0x2Fn, 0x00n, false, false, 0x3Fn); + case "CRC-6/ITU": return this.crc(6n, input, 0x03n, 0x00n, true, true, 0x00n); + case "CRC-7/MMC": return this.crc(7n, input, 0x09n, 0x00n, false, false, 0x00n); + case "CRC-7/ROHC": return this.crc(7n, input, 0x4Fn, 0x7Fn, true, true, 0x00n); + case "CRC-7/UMTS": return this.crc(7n, input, 0x45n, 0x00n, false, false, 0x00n); + case "CRC-8": return this.crc(8n, input, 0x07n, 0x00n, false, false, 0x00n); + case "CRC-8/8H2F": return this.crc(8n, input, 0x2Fn, 0xFFn, false, false, 0xFFn); + case "CRC-8/AES": return this.crc(8n, input, 0x1Dn, 0xFFn, true, true, 0x00n); + case "CRC-8/AUTOSAR": return this.crc(8n, input, 0x2Fn, 0xFFn, false, false, 0xFFn); + case "CRC-8/BLUETOOTH": return this.crc(8n, input, 0xA7n, 0x00n, true, true, 0x00n); + case "CRC-8/CDMA2000": return this.crc(8n, input, 0x9Bn, 0xFFn, false, false, 0x00n); + case "CRC-8/DARC": return this.crc(8n, input, 0x39n, 0x00n, true, true, 0x00n); + case "CRC-8/DVB-S2": return this.crc(8n, input, 0xD5n, 0x00n, false, false, 0x00n); + case "CRC-8/EBU": return this.crc(8n, input, 0x1Dn, 0xFFn, true, true, 0x00n); + case "CRC-8/GSM-A": return this.crc(8n, input, 0x1Dn, 0x00n, false, false, 0x00n); + case "CRC-8/GSM-B": return this.crc(8n, input, 0x49n, 0x00n, false, false, 0xFFn); + case "CRC-8/HITAG": return this.crc(8n, input, 0x1Dn, 0xFFn, false, false, 0x00n); + case "CRC-8/I-432-1": return this.crc(8n, input, 0x07n, 0x00n, false, false, 0x55n); + case "CRC-8/I-CODE": return this.crc(8n, input, 0x1Dn, 0xFDn, false, false, 0x00n); + case "CRC-8/ITU": return this.crc(8n, input, 0x07n, 0x00n, false, false, 0x55n); + case "CRC-8/LTE": return this.crc(8n, input, 0x9Bn, 0x00n, false, false, 0x00n); + case "CRC-8/MAXIM": return this.crc(8n, input, 0x31n, 0x00n, true, true, 0x00n); + case "CRC-8/MAXIM-DOW": return this.crc(8n, input, 0x31n, 0x00n, true, true, 0x00n); + case "CRC-8/MIFARE-MAD": return this.crc(8n, input, 0x1Dn, 0xC7n, false, false, 0x00n); + case "CRC-8/NRSC-5": return this.crc(8n, input, 0x31n, 0xFFn, false, false, 0x00n); + case "CRC-8/OPENSAFETY": return this.crc(8n, input, 0x2Fn, 0x00n, false, false, 0x00n); + case "CRC-8/ROHC": return this.crc(8n, input, 0x07n, 0xFFn, true, true, 0x00n); + case "CRC-8/SAE-J1850": return this.crc(8n, input, 0x1Dn, 0xFFn, false, false, 0xFFn); + case "CRC-8/SAE-J1850-ZERO": return this.crc(8n, input, 0x1Dn, 0x00n, false, false, 0x00n); + case "CRC-8/SMBUS": return this.crc(8n, input, 0x07n, 0x00n, false, false, 0x00n); + case "CRC-8/TECH-3250": return this.crc(8n, input, 0x1Dn, 0xFFn, true, true, 0x00n); + case "CRC-8/WCDMA": return this.crc(8n, input, 0x9Bn, 0x00n, true, true, 0x00n); + case "CRC-10/ATM": return this.crc(10n, input, 0x233n, 0x000n, false, false, 0x000n); + case "CRC-10/CDMA2000": return this.crc(10n, input, 0x3D9n, 0x3FFn, false, false, 0x000n); + case "CRC-10/GSM": return this.crc(10n, input, 0x175n, 0x000n, false, false, 0x3FFn); + case "CRC-10/I-610": return this.crc(10n, input, 0x233n, 0x000n, false, false, 0x000n); + case "CRC-11/FLEXRAY": return this.crc(11n, input, 0x385n, 0x01An, false, false, 0x000n); + case "CRC-11/UMTS": return this.crc(11n, input, 0x307n, 0x000n, false, false, 0x000n); + case "CRC-12/3GPP": return this.crc(12n, input, 0x80Fn, 0x000n, false, true, 0x000n); + case "CRC-12/CDMA2000": return this.crc(12n, input, 0xF13n, 0xFFFn, false, false, 0x000n); + case "CRC-12/DECT": return this.crc(12n, input, 0x80Fn, 0x000n, false, false, 0x000n); + case "CRC-12/GSM": return this.crc(12n, input, 0xD31n, 0x000n, false, false, 0xFFFn); + case "CRC-12/UMTS": return this.crc(12n, input, 0x80Fn, 0x000n, false, true, 0x000n); + case "CRC-13/BBC": return this.crc(13n, input, 0x1CF5n, 0x0000n, false, false, 0x0000n); + case "CRC-14/DARC": return this.crc(14n, input, 0x0805n, 0x0000n, true, true, 0x0000n); + case "CRC-14/GSM": return this.crc(14n, input, 0x202Dn, 0x0000n, false, false, 0x3FFFn); + case "CRC-15/CAN": return this.crc(15n, input, 0x4599n, 0x0000n, false, false, 0x0000n); + case "CRC-15/MPT1327": return this.crc(15n, input, 0x6815n, 0x0000n, false, false, 0x0001n); + case "CRC-16": return this.crc(16n, input, 0x8005n, 0x0000n, true, true, 0x0000n); + case "CRC-16/A": return this.crc(16n, input, 0x1021n, 0xC6C6n, true, true, 0x0000n); + case "CRC-16/ACORN": return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n); + case "CRC-16/ARC": return this.crc(16n, input, 0x8005n, 0x0000n, true, true, 0x0000n); + case "CRC-16/AUG-CCITT": return this.crc(16n, input, 0x1021n, 0x1D0Fn, false, false, 0x0000n); + case "CRC-16/AUTOSAR": return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0x0000n); + case "CRC-16/B": return this.crc(16n, input, 0x1021n, 0xFFFFn, true, true, 0xFFFFn); + case "CRC-16/BLUETOOTH": return this.crc(16n, input, 0x1021n, 0x0000n, true, true, 0x0000n); + case "CRC-16/BUYPASS": return this.crc(16n, input, 0x8005n, 0x0000n, false, false, 0x0000n); + case "CRC-16/CCITT": return this.crc(16n, input, 0x1021n, 0x0000n, true, true, 0x0000n); + case "CRC-16/CCITT-FALSE": return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0x0000n); + case "CRC-16/CCITT-TRUE": return this.crc(16n, input, 0x1021n, 0x0000n, true, true, 0x0000n); + case "CRC-16/CCITT-ZERO": return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n); + case "CRC-16/CDMA2000": return this.crc(16n, input, 0xC867n, 0xFFFFn, false, false, 0x0000n); + case "CRC-16/CMS": return this.crc(16n, input, 0x8005n, 0xFFFFn, false, false, 0x0000n); + case "CRC-16/DARC": return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0xFFFFn); + case "CRC-16/DDS-110": return this.crc(16n, input, 0x8005n, 0x800Dn, false, false, 0x0000n); + case "CRC-16/DECT-R": return this.crc(16n, input, 0x0589n, 0x0000n, false, false, 0x0001n); + case "CRC-16/DECT-X": return this.crc(16n, input, 0x0589n, 0x0000n, false, false, 0x0000n); + case "CRC-16/DNP": return this.crc(16n, input, 0x3D65n, 0x0000n, true, true, 0xFFFFn); + case "CRC-16/EN-13757": return this.crc(16n, input, 0x3D65n, 0x0000n, false, false, 0xFFFFn); + case "CRC-16/EPC": return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0xFFFFn); + case "CRC-16/EPC-C1G2": return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0xFFFFn); + case "CRC-16/GENIBUS": return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0xFFFFn); + case "CRC-16/GSM": return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0xFFFFn); + case "CRC-16/I-CODE": return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0xFFFFn); + case "CRC-16/IBM": return this.crc(16n, input, 0x8005n, 0x0000n, true, true, 0x0000n); + case "CRC-16/IBM-3740": return this.crc(16n, input, 0x1021n, 0xFFFFn, false, false, 0x0000n); + case "CRC-16/IBM-SDLC": return this.crc(16n, input, 0x1021n, 0xFFFFn, true, true, 0xFFFFn); + case "CRC-16/IEC-61158-2": return this.crc(16n, input, 0x1DCFn, 0xFFFFn, false, false, 0xFFFFn); + case "CRC-16/ISO-HDLC": return this.crc(16n, input, 0x1021n, 0xFFFFn, true, true, 0xFFFFn); + case "CRC-16/ISO-IEC-14443-3-A": return this.crc(16n, input, 0x1021n, 0xC6C6n, true, true, 0x0000n); + case "CRC-16/ISO-IEC-14443-3-B": return this.crc(16n, input, 0x1021n, 0xFFFFn, true, true, 0xFFFFn); + case "CRC-16/KERMIT": return this.crc(16n, input, 0x1021n, 0x0000n, true, true, 0x0000n); + case "CRC-16/LHA": return this.crc(16n, input, 0x8005n, 0x0000n, true, true, 0x0000n); + case "CRC-16/LJ1200": return this.crc(16n, input, 0x6F63n, 0x0000n, false, false, 0x0000n); + case "CRC-16/LTE": return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n); + case "CRC-16/M17": return this.crc(16n, input, 0x5935n, 0xFFFFn, false, false, 0x0000n); + case "CRC-16/MAXIM": return this.crc(16n, input, 0x8005n, 0x0000n, true, true, 0xFFFFn); + case "CRC-16/MAXIM-DOW": return this.crc(16n, input, 0x8005n, 0x0000n, true, true, 0xFFFFn); + case "CRC-16/MCRF4XX": return this.crc(16n, input, 0x1021n, 0xFFFFn, true, true, 0x0000n); + case "CRC-16/MODBUS": return this.crc(16n, input, 0x8005n, 0xFFFFn, true, true, 0x0000n); + case "CRC-16/NRSC-5": return this.crc(16n, input, 0x080Bn, 0xFFFFn, true, true, 0x0000n); + case "CRC-16/OPENSAFETY-A": return this.crc(16n, input, 0x5935n, 0x0000n, false, false, 0x0000n); + case "CRC-16/OPENSAFETY-B": return this.crc(16n, input, 0x755Bn, 0x0000n, false, false, 0x0000n); + case "CRC-16/PROFIBUS": return this.crc(16n, input, 0x1DCFn, 0xFFFFn, false, false, 0xFFFFn); + case "CRC-16/RIELLO": return this.crc(16n, input, 0x1021n, 0xB2AAn, true, true, 0x0000n); + case "CRC-16/SPI-FUJITSU": return this.crc(16n, input, 0x1021n, 0x1D0Fn, false, false, 0x0000n); + case "CRC-16/T10-DIF": return this.crc(16n, input, 0x8BB7n, 0x0000n, false, false, 0x0000n); + case "CRC-16/TELEDISK": return this.crc(16n, input, 0xA097n, 0x0000n, false, false, 0x0000n); + case "CRC-16/TMS37157": return this.crc(16n, input, 0x1021n, 0x89ECn, true, true, 0x0000n); + case "CRC-16/UMTS": return this.crc(16n, input, 0x8005n, 0x0000n, false, false, 0x0000n); + case "CRC-16/USB": return this.crc(16n, input, 0x8005n, 0xFFFFn, true, true, 0xFFFFn); + case "CRC-16/V-41-LSB": return this.crc(16n, input, 0x1021n, 0x0000n, true, true, 0x0000n); + case "CRC-16/V-41-MSB": return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n); + case "CRC-16/VERIFONE": return this.crc(16n, input, 0x8005n, 0x0000n, false, false, 0x0000n); + case "CRC-16/X-25": return this.crc(16n, input, 0x1021n, 0xFFFFn, true, true, 0xFFFFn); + case "CRC-16/XMODEM": return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n); + case "CRC-16/ZMODEM": return this.crc(16n, input, 0x1021n, 0x0000n, false, false, 0x0000n); + case "CRC-17/CAN-FD": return this.crc(17n, input, 0x1685Bn, 0x00000n, false, false, 0x00000n); + case "CRC-21/CAN-FD": return this.crc(21n, input, 0x102899n, 0x000000n, false, false, 0x000000n); + case "CRC-24/BLE": return this.crc(24n, input, 0x00065Bn, 0x555555n, true, true, 0x000000n); + case "CRC-24/FLEXRAY-A": return this.crc(24n, input, 0x5D6DCBn, 0xFEDCBAn, false, false, 0x000000n); + case "CRC-24/FLEXRAY-B": return this.crc(24n, input, 0x5D6DCBn, 0xABCDEFn, false, false, 0x000000n); + case "CRC-24/INTERLAKEN": return this.crc(24n, input, 0x328B63n, 0xFFFFFFn, false, false, 0xFFFFFFn); + case "CRC-24/LTE-A": return this.crc(24n, input, 0x864CFBn, 0x000000n, false, false, 0x000000n); + case "CRC-24/LTE-B": return this.crc(24n, input, 0x800063n, 0x000000n, false, false, 0x000000n); + case "CRC-24/OPENPGP": return this.crc(24n, input, 0x864CFBn, 0xB704CEn, false, false, 0x000000n); + case "CRC-24/OS-9": return this.crc(24n, input, 0x800063n, 0xFFFFFFn, false, false, 0xFFFFFFn); + case "CRC-30/CDMA": return this.crc(30n, input, 0x2030B9C7n, 0x3FFFFFFFn, false, false, 0x3FFFFFFFn); + case "CRC-31/PHILIPS": return this.crc(31n, input, 0x04C11DB7n, 0x7FFFFFFFn, false, false, 0x7FFFFFFFn); + case "CRC-32": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/AAL5": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, false, false, 0xFFFFFFFFn); + case "CRC-32/ADCCP": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/AIXM": return this.crc(32n, input, 0x814141ABn, 0x00000000n, false, false, 0x00000000n); + case "CRC-32/AUTOSAR": return this.crc(32n, input, 0xF4ACFB13n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/BASE91-C": return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/BASE91-D": return this.crc(32n, input, 0xA833982Bn, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/BZIP2": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, false, false, 0xFFFFFFFFn); + case "CRC-32/C": return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/CASTAGNOLI": return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/CD-ROM-EDC": return this.crc(32n, input, 0x8001801Bn, 0x00000000n, true, true, 0x00000000n); + case "CRC-32/CKSUM": return this.crc(32n, input, 0x04C11DB7n, 0x00000000n, false, false, 0xFFFFFFFFn); + case "CRC-32/D": return this.crc(32n, input, 0xA833982Bn, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/DECT-B": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, false, false, 0xFFFFFFFFn); + case "CRC-32/INTERLAKEN": return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/ISCSI": return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/ISO-HDLC": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/JAMCRC": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true, true, 0x00000000n); + case "CRC-32/MEF": return this.crc(32n, input, 0x741B8CD7n, 0xFFFFFFFFn, true, true, 0x00000000n); + case "CRC-32/MPEG-2": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, false, false, 0x00000000n); + case "CRC-32/NVME": return this.crc(32n, input, 0x1EDC6F41n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/PKZIP": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/POSIX": return this.crc(32n, input, 0x04C11DB7n, 0x00000000n, false, false, 0xFFFFFFFFn); + case "CRC-32/Q": return this.crc(32n, input, 0x814141ABn, 0x00000000n, false, false, 0x00000000n); + case "CRC-32/SATA": return this.crc(32n, input, 0x04C11DB7n, 0x52325032n, false, false, 0x00000000n); + case "CRC-32/V-42": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-32/XFER": return this.crc(32n, input, 0x000000AFn, 0x00000000n, false, false, 0x00000000n); + case "CRC-32/XZ": return this.crc(32n, input, 0x04C11DB7n, 0xFFFFFFFFn, true, true, 0xFFFFFFFFn); + case "CRC-40/GSM": return this.crc(40n, input, 0x0004820009n, 0x0000000000n, false, false, 0xFFFFFFFFFFn); + case "CRC-64/ECMA-182": return this.crc(64n, input, 0x42F0E1EBA9EA3693n, 0x0000000000000000n, false, false, 0x0000000000000000n); + case "CRC-64/GO-ECMA": return this.crc(64n, input, 0x42F0E1EBA9EA3693n, 0xFFFFFFFFFFFFFFFFn, true, true, 0xFFFFFFFFFFFFFFFFn); + case "CRC-64/GO-ISO": return this.crc(64n, input, 0x000000000000001Bn, 0xFFFFFFFFFFFFFFFFn, true, true, 0xFFFFFFFFFFFFFFFFn); + case "CRC-64/MS": return this.crc(64n, input, 0x259C84CBA6426349n, 0xFFFFFFFFFFFFFFFFn, true, true, 0x0000000000000000n); + case "CRC-64/NVME": return this.crc(64n, input, 0xAD93D23594C93659n, 0xFFFFFFFFFFFFFFFFn, true, true, 0xFFFFFFFFFFFFFFFFn); + case "CRC-64/REDIS": return this.crc(64n, input, 0xAD93D23594C935A9n, 0x0000000000000000n, true, true, 0x0000000000000000n); + case "CRC-64/WE": return this.crc(64n, input, 0x42F0E1EBA9EA3693n, 0xFFFFFFFFFFFFFFFFn, false, false, 0xFFFFFFFFFFFFFFFFn); + case "CRC-64/XZ": return this.crc(64n, input, 0x42F0E1EBA9EA3693n, 0xFFFFFFFFFFFFFFFFn, true, true, 0xFFFFFFFFFFFFFFFFn); + case "CRC-82/DARC": return this.crc(82n, input, 0x0308C0111011401440411n, 0x000000000000000000000n, true, true, 0x000000000000000000000n); + default: throw new OperationError("Unknown checksum algorithm"); + } + } + +} + +export default CRCChecksum; diff --git a/src/core/operations/CSSBeautify.mjs b/src/core/operations/CSSBeautify.mjs new file mode 100644 index 00000000..3491b618 --- /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.mjs"; + +/** + * 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..3aa96bb1 --- /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.mjs"; + +/** + * 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..639726c4 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import xmldom from "@xmldom/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..ca9f1ceb --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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..6b6a487d --- /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.mjs"; +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://forensics.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/CaesarBoxCipher.mjs b/src/core/operations/CaesarBoxCipher.mjs new file mode 100644 index 00000000..680db900 --- /dev/null +++ b/src/core/operations/CaesarBoxCipher.mjs @@ -0,0 +1,61 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Caesar Box Cipher operation + */ +class CaesarBoxCipher extends Operation { + + /** + * CaesarBoxCipher constructor + */ + constructor() { + super(); + + this.name = "Caesar Box Cipher"; + this.module = "Ciphers"; + this.description = "Caesar Box is a transposition cipher used in the Roman Empire, in which letters of the message are written in rows in a square (or a rectangle) and then, read by column."; + this.infoURL = "https://www.dcode.fr/caesar-box-cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Box Height", + type: "number", + value: 1 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const tableHeight = args[0]; + const tableWidth = Math.ceil(input.length / tableHeight); + while (input.indexOf(" ") !== -1) + input = input.replace(" ", ""); + for (let i = 0; i < (tableHeight * tableWidth) - input.length; i++) { + input += "\x00"; + } + let result = ""; + for (let i = 0; i < tableHeight; i++) { + for (let j = i; j < input.length; j += tableHeight) { + if (input.charAt(j) !== "\x00") { + result += input.charAt(j); + } + } + } + return result; + } + +} + +export default CaesarBoxCipher; diff --git a/src/core/operations/CaretMdecode.mjs b/src/core/operations/CaretMdecode.mjs new file mode 100644 index 00000000..68c6dacb --- /dev/null +++ b/src/core/operations/CaretMdecode.mjs @@ -0,0 +1,98 @@ +/** + * @author tedk [tedk@ted.do] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Caret/M-decode operation + * + * https://gist.githubusercontent.com/JaHIY/3c91bbf7bea5661e6abfbd1349ee81a2/raw/c7b480e9ff24bcb8f5287a8a8a2dcb9bf5628506/decode_m_notation.cpp + */ +class CaretMdecode extends Operation { + + /** + * CaretMdecode constructor + */ + constructor() { + super(); + + this.name = "Caret/M-decode"; + this.module = "Default"; + this.description = "Decodes caret or M-encoded strings, i.e. ^M turns into a newline, M-^] turns into 0x9d. Sources such as `cat -v`.\n\nPlease be aware that when using `cat -v` ^_ (caret-underscore) will not be encoded, but represents a valid encoding (namely that of 0x1f)."; + this.infoURL = "https://en.wikipedia.org/wiki/Caret_notation"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + + const bytes = []; + + let prev = ""; + + for (let i = 0; i < input.length; i++) { + + const charCode = input.charCodeAt(i); + const curChar = input.charAt(i); + + if (prev === "M-^") { + if (charCode > 63 && charCode <= 95) { + bytes.push(charCode + 64); + } else if (charCode === 63) { + bytes.push(255); + } else { + bytes.push(77, 45, 94, charCode); + } + prev = ""; + } else if (prev === "M-") { + if (curChar === "^") { + prev = prev + "^"; + } else if (charCode >= 32 && charCode <= 126) { + bytes.push(charCode + 128); + prev = ""; + } else { + bytes.push(77, 45, charCode); + prev = ""; + } + } else if (prev === "M") { + if (curChar === "-") { + prev = prev + "-"; + } else { + bytes.push(77, charCode); + prev = ""; + } + } else if (prev === "^") { + if (charCode > 63 && charCode <= 126) { + bytes.push(charCode - 64); + } else if (charCode === 63) { + bytes.push(127); + } else { + bytes.push(94, charCode); + } + prev = ""; + } else { + if (curChar === "M") { + prev = "M"; + } else if (curChar === "^") { + prev = "^"; + } else { + bytes.push(charCode); + } + } + + } + return bytes; + } + +} + +export default CaretMdecode; diff --git a/src/core/operations/CartesianProduct.mjs b/src/core/operations/CartesianProduct.mjs new file mode 100644 index 00000000..07ac575c --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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/CetaceanCipherDecode.mjs b/src/core/operations/CetaceanCipherDecode.mjs new file mode 100644 index 00000000..a50fe6b7 --- /dev/null +++ b/src/core/operations/CetaceanCipherDecode.mjs @@ -0,0 +1,63 @@ +/** + * @author dolphinOnKeys [robin@weird.io] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Cetacean Cipher Decode operation + */ +class CetaceanCipherDecode extends Operation { + + /** + * CetaceanCipherDecode constructor + */ + constructor() { + super(); + + this.name = "Cetacean Cipher Decode"; + this.module = "Ciphers"; + this.description = "Decode Cetacean Cipher input.

e.g. EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe becomes hi"; + this.infoURL = "https://hitchhikers.fandom.com/wiki/Dolphins"; + this.inputType = "string"; + this.outputType = "string"; + + this.checks = [ + { + pattern: "^(?:[eE]{16,})(?: [eE]{16,})*$", + flags: "", + args: [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const binaryArray = []; + for (const char of input) { + if (char === " ") { + binaryArray.push(...[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]); + } else { + binaryArray.push(char === "e" ? 1 : 0); + } + } + + const byteArray = []; + + for (let i = 0; i < binaryArray.length; i += 16) { + byteArray.push(binaryArray.slice(i, i + 16).join("")); + } + + return byteArray.map(byte => + String.fromCharCode(parseInt(byte, 2)) + ).join(""); + } +} + +export default CetaceanCipherDecode; diff --git a/src/core/operations/CetaceanCipherEncode.mjs b/src/core/operations/CetaceanCipherEncode.mjs new file mode 100644 index 00000000..ec5f76d6 --- /dev/null +++ b/src/core/operations/CetaceanCipherEncode.mjs @@ -0,0 +1,51 @@ +/** + * @author dolphinOnKeys [robin@weird.io] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {toBinary} from "../lib/Binary.mjs"; + +/** + * Cetacean Cipher Encode operation + */ +class CetaceanCipherEncode extends Operation { + + /** + * CetaceanCipherEncode constructor + */ + constructor() { + super(); + + this.name = "Cetacean Cipher Encode"; + this.module = "Ciphers"; + this.description = "Converts any input into Cetacean Cipher.

e.g. hi becomes EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe"; + this.infoURL = "https://hitchhikers.fandom.com/wiki/Dolphins"; + this.inputType = "string"; + this.outputType = "string"; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const result = []; + const charArray = input.split(""); + + charArray.map(character => { + if (character === " ") { + result.push(character); + } else { + const binaryArray = toBinary(character.charCodeAt(0), "None", 16).split(""); + result.push(binaryArray.map(str => str === "1" ? "e" : "E").join("")); + } + }); + + return result.join(""); + } +} + +export default CetaceanCipherEncode; diff --git a/src/core/operations/ChaCha.mjs b/src/core/operations/ChaCha.mjs new file mode 100644 index 00000000..2d186d35 --- /dev/null +++ b/src/core/operations/ChaCha.mjs @@ -0,0 +1,234 @@ +/** + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHex } from "../lib/Hex.mjs"; + +/** + * Computes the ChaCha block function + * + * @param {byteArray} key + * @param {byteArray} nonce + * @param {byteArray} counter + * @param {integer} rounds + * @returns {byteArray} + */ +function chacha(key, nonce, counter, rounds) { + const tau = "expand 16-byte k"; + const sigma = "expand 32-byte k"; + + let state, c; + if (key.length === 16) { + c = Utils.strToByteArray(tau); + state = c.concat(key).concat(key); + } else { + c = Utils.strToByteArray(sigma); + state = c.concat(key); + } + state = state.concat(counter).concat(nonce); + + const x = Array(); + for (let i = 0; i < 64; i += 4) { + x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little")); + } + const a = [...x]; + + /** + * Macro to compute a 32-bit rotate-left operation + * + * @param {integer} x + * @param {integer} n + * @returns {integer} + */ + function ROL32(x, n) { + return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n)); + } + + /** + * Macro to compute a single ChaCha quarterround operation + * + * @param {integer} x + * @param {integer} a + * @param {integer} b + * @param {integer} c + * @param {integer} d + * @returns {integer} + */ + function quarterround(x, a, b, c, d) { + x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 16); + x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 12); + x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 8); + x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 7); + } + + for (let i = 0; i < rounds / 2; i++) { + quarterround(x, 0, 4, 8, 12); + quarterround(x, 1, 5, 9, 13); + quarterround(x, 2, 6, 10, 14); + quarterround(x, 3, 7, 11, 15); + quarterround(x, 0, 5, 10, 15); + quarterround(x, 1, 6, 11, 12); + quarterround(x, 2, 7, 8, 13); + quarterround(x, 3, 4, 9, 14); + } + + for (let i = 0; i < 16; i++) { + x[i] = (x[i] + a[i]) & 0xFFFFFFFF; + } + + let output = Array(); + for (let i = 0; i < 16; i++) { + output = output.concat(Utils.intToByteArray(x[i], 4, "little")); + } + return output; +} + +/** + * ChaCha operation + */ +class ChaCha extends Operation { + + /** + * ChaCha constructor + */ + constructor() { + super(); + + this.name = "ChaCha"; + this.module = "Ciphers"; + this.description = "ChaCha is a stream cipher designed by Daniel J. Bernstein. It is a variant of the Salsa stream cipher. Several parameterizations exist; 'ChaCha' may refer to the original construction, or to the variant as described in RFC-8439. ChaCha is often used with Poly1305, in the ChaCha20-Poly1305 AEAD construction.

Key: ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).

Nonce: ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).

Counter: ChaCha uses a counter of 4 or 8 bytes (32 or 64 bits); together, the nonce and counter must add up to 16 bytes. The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes."; + this.infoURL = "https://wikipedia.org/wiki/Salsa20#ChaCha_variant"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Nonce", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"] + }, + { + "name": "Counter", + "type": "number", + "value": 0, + "min": 0 + }, + { + "name": "Rounds", + "type": "option", + "value": ["20", "12", "8"] + }, + { + "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.convertToByteArray(args[0].string, args[0].option), + nonceType = args[1].option, + rounds = parseInt(args[3], 10), + inputType = args[4], + outputType = args[5]; + + if (key.length !== 16 && key.length !== 32) { + throw new OperationError(`Invalid key length: ${key.length} bytes. + +ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`); + } + + let counter, nonce, counterLength; + if (nonceType === "Integer") { + nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 12, "little"); + counterLength = 4; + } else { + nonce = Utils.convertToByteArray(args[1].string, args[1].option); + if (!(nonce.length === 12 || nonce.length === 8)) { + throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. + +ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`); + } + counterLength = 16 - nonce.length; + } + counter = Utils.intToByteArray(args[2], counterLength, "little"); + + const output = []; + input = Utils.convertToByteArray(input, inputType); + + let counterAsInt = Utils.byteArrayToInt(counter, "little"); + for (let i = 0; i < input.length; i += 64) { + counter = Utils.intToByteArray(counterAsInt, counterLength, "little"); + const stream = chacha(key, nonce, counter, rounds); + for (let j = 0; j < 64 && i + j < input.length; j++) { + output.push(input[i + j] ^ stream[j]); + } + counterAsInt++; + } + + if (outputType === "Hex") { + return toHex(output); + } else { + return Utils.arrayBufferToStr(Uint8Array.from(output).buffer); + } + } + + /** + * Highlight ChaCha + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + + /** + * Highlight ChaCha in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + +} + +export default ChaCha; diff --git a/src/core/operations/ChangeIPFormat.mjs b/src/core/operations/ChangeIPFormat.mjs new file mode 100644 index 00000000..c9adc5d8 --- /dev/null +++ b/src/core/operations/ChangeIPFormat.mjs @@ -0,0 +1,138 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {fromHex} from "../lib/Hex.mjs"; + +/** + * 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", "Octal", "Hex"] + }, + { + "name": "Output format", + "type": "option", + "value": ["Dotted Decimal", "Decimal", "Octal", "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; + + 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": + baIp = this.fromNumber(lines[i].toString(), 10); + break; + case "Octal": + baIp = this.fromNumber(lines[i].toString(), 8); + 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 "Octal": + decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0; + output += "0" + decIp.toString(8) + "\n"; + break; + case "Hex": + hexIp = ""; + for (j = 0; j < baIp.length; j++) { + hexIp += Utils.hex(baIp[j]); + } + output += hexIp + "\n"; + break; + default: + throw new OperationError("Unsupported output IP format"); + } + } + + return output.slice(0, output.length-1); + } + + /** + * Constructs an array of IP address octets from a numerical value. + * @param {string} value The value of the IP address + * @param {number} radix The numeral system to be used + * @returns {number[]} + */ + fromNumber(value, radix) { + const decimal = parseInt(value, radix); + const baIp = []; + baIp.push(decimal >> 24 & 255); + baIp.push(decimal >> 16 & 255); + baIp.push(decimal >> 8 & 255); + baIp.push(decimal & 255); + return baIp; + } + +} + +export default ChangeIPFormat; diff --git a/src/core/operations/CharEnc.js b/src/core/operations/CharEnc.js deleted file mode 100755 index 96659272..00000000 --- a/src/core/operations/CharEnc.js +++ /dev/null @@ -1,50 +0,0 @@ -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: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Windows-1251", "Hex", "Base64"], - - /** - * Text encoding operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - run: function(input, args) { - var inputFormat = args[0], - outputFormat = args[1]; - - if (inputFormat === "Windows-1251") { - input = Utils.win1251ToUnicode(input); - input = CryptoJS.enc.Utf8.parse(input); - } else { - input = Utils.format[inputFormat].parse(input); - } - - if (outputFormat === "Windows-1251") { - input = CryptoJS.enc.Utf8.stringify(input); - return Utils.unicodeToWin1251(input); - } else { - return Utils.format[outputFormat].stringify(input); - } - }, - -}; - -export default CharEnc; diff --git a/src/core/operations/Checksum.js b/src/core/operations/Checksum.js deleted file mode 100755 index e3bf996d..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) { - var a = 0, - b = 0; - - for (var 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) { - var a = 0, - b = 0; - - for (var 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) { - var a = 0, - b = 0; - - for (var 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) { - var a = 0, - b = 0; - - for (var 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) { - var MOD_ADLER = 65521, - a = 1, - b = 0; - - for (var 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) { - var crcTable = global.crcTable || (global.crcTable = Checksum._genCRCTable()), - crc = 0 ^ (-1); - - for (var 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) { - var csum = 0; - - for (var 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() { - var c, - crcTable = []; - - for (var n = 0; n < 256; n++) { - c = n; - for (var 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..f78574d6 --- /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.mjs"; + +/** + * 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 934dbe18..00000000 --- a/src/core/operations/Cipher.js +++ /dev/null @@ -1,661 +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) { - var 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); - } - - var encrypted = algo.encrypt(input, key, { - salt: salt.sigBytes > 0 ? salt : false, - iv: iv.sigBytes > 0 ? iv : null, - mode: mode, - padding: padding - }); - - var 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) { - var 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"; - } - - var 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); - } - - var decrypted = algo.decrypt({ - ciphertext: ciphertext, - salt: salt.sigBytes > 0 ? salt : false - }, key, { - iv: iv.sigBytes > 0 ? iv : null, - mode: mode, - padding: padding - }); - - var 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) { - var 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"; - - var 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) { - var 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, - - /** - * Derive PBKDF2 key operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runPbkdf2: function (input, args) { - var keySize = args[0] / 32, - iterations = args[1], - salt = CryptoJS.enc.Hex.parse(args[2] || ""), - inputFormat = args[3], - outputFormat = args[4], - passphrase = Utils.format[inputFormat].parse(input), - key = CryptoJS.PBKDF2(passphrase, salt, { keySize: keySize, iterations: iterations }); - - return key.toString(Utils.format[outputFormat]); - }, - - - /** - * Derive EVP key operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runEvpkdf: function (input, args) { - var keySize = args[0] / 32, - iterations = args[1], - salt = CryptoJS.enc.Hex.parse(args[2] || ""), - inputFormat = args[3], - outputFormat = args[4], - passphrase = Utils.format[inputFormat].parse(input), - key = CryptoJS.EvpKDF(passphrase, salt, { keySize: keySize, iterations: iterations }); - - return key.toString(Utils.format[outputFormat]); - }, - - - /** - * RC4 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRc4: function (input, args) { - var 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) { - var 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) { - var 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 (var 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) { - var 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 (var 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) { - var 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 (var 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) { - var 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 (var 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) { - var 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 (var 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 - var key = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); - - // Separate key and IV - var 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/CipherSaber2Decrypt.mjs b/src/core/operations/CipherSaber2Decrypt.mjs new file mode 100644 index 00000000..53d61468 --- /dev/null +++ b/src/core/operations/CipherSaber2Decrypt.mjs @@ -0,0 +1,61 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { encode } from "../lib/CipherSaber2.mjs"; +import Utils from "../Utils.mjs"; + +/** + * CipherSaber2 Decrypt operation + */ +class CipherSaber2Decrypt extends Operation { + + /** + * CipherSaber2Decrypt constructor + */ + constructor() { + super(); + + this.name = "CipherSaber2 Decrypt"; + this.module = "Crypto"; + this.description = "CipherSaber is a simple symmetric encryption protocol based on the RC4 stream cipher. It gives reasonably strong protection of message confidentiality, yet it's designed to be simple enough that even novice programmers can memorize the algorithm and implement it from scratch."; + this.infoURL = "https://wikipedia.org/wiki/CipherSaber"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Rounds", + type: "number", + value: 20 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + input = new Uint8Array(input); + const result = [], + key = Utils.convertToByteArray(args[0].string, args[0].option), + rounds = args[1]; + + const tempIVP = input.slice(0, 10); + input = input.slice(10); + return new Uint8Array(result.concat(encode(tempIVP, key, rounds, input))).buffer; + } + +} + +export default CipherSaber2Decrypt; diff --git a/src/core/operations/CipherSaber2Encrypt.mjs b/src/core/operations/CipherSaber2Encrypt.mjs new file mode 100644 index 00000000..dd86bd52 --- /dev/null +++ b/src/core/operations/CipherSaber2Encrypt.mjs @@ -0,0 +1,65 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import crypto from "crypto"; +import { encode } from "../lib/CipherSaber2.mjs"; +import Utils from "../Utils.mjs"; + +/** + * CipherSaber2 Encrypt operation + */ +class CipherSaber2Encrypt extends Operation { + + /** + * CipherSaber2Encrypt constructor + */ + constructor() { + super(); + + this.name = "CipherSaber2 Encrypt"; + this.module = "Crypto"; + this.description = "CipherSaber is a simple symmetric encryption protocol based on the RC4 stream cipher. It gives reasonably strong protection of message confidentiality, yet it's designed to be simple enough that even novice programmers can memorize the algorithm and implement it from scratch."; + this.infoURL = "https://wikipedia.org/wiki/CipherSaber"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Rounds", + type: "number", + value: 20 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + input = new Uint8Array(input); + const result = [], + key = Utils.convertToByteArray(args[0].string, args[0].option), + rounds = args[1]; + + // Assign into initialisation vector based on cipher mode. + const tempIVP = crypto.randomBytes(10); + for (let m = 0; m < 10; m++) + result.push(tempIVP[m]); + + return new Uint8Array(result.concat(encode(tempIVP, key, rounds, input))).buffer; + } + +} + +export default CipherSaber2Encrypt; diff --git a/src/core/operations/CitrixCTX1Decode.mjs b/src/core/operations/CitrixCTX1Decode.mjs new file mode 100644 index 00000000..f002c8a2 --- /dev/null +++ b/src/core/operations/CitrixCTX1Decode.mjs @@ -0,0 +1,59 @@ +/** + * @author bwhitn [brian.m.whitney@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import cptable from "codepage"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + if (input.length % 4 !== 0) { + throw new OperationError("Incorrect hash length"); + } + 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..3c0dfcdd --- /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.mjs"; +import cptable from "codepage"; + +/** + * 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 32c147ef..00000000 --- a/src/core/operations/Code.js +++ /dev/null @@ -1,427 +0,0 @@ -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) { - var 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) { - var indentStr = args[0]; - return vkbeautify.xml(input, indentStr); - }, - - - /** - * JSON Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runJsonBeautify: function(input, args) { - var 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) { - var indentStr = args[0]; - return vkbeautify.css(input, indentStr); - }, - - - /** - * SQL Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSqlBeautify: function(input, args) { - var 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) { - var 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) { - var 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) { - var code = input, - t = 0, - preservedTokens = [], - m; - - // Remove strings - var sstrings = /'([^'\\]|\\.)*'/g; - while ((m = sstrings.exec(code))) { - code = preserveToken(code, m, t++); - sstrings.lastIndex = m.index; - } - - var dstrings = /"([^"\\]|\\.)*"/g; - while ((m = dstrings.exec(code))) { - code = preserveToken(code, m, t++); - dstrings.lastIndex = m.index; - } - - // Remove comments - var scomments = /\/\/[^\n\r]*/g; - while ((m = scomments.exec(code))) { - code = preserveToken(code, m, t++); - scomments.lastIndex = m.index; - } - - var mcomments = /\/\*[\s\S]*?\*\//gm; - while ((m = mcomments.exec(code))) { - code = preserveToken(code, m, t++); - mcomments.lastIndex = m.index; - } - - var hcomments = /(^|\n)#[^\n\r#]+/g; - while ((m = hcomments.exec(code))) { - code = preserveToken(code, m, t++); - hcomments.lastIndex = m.index; - } - - // Remove regexes - var regexes = /\/.*?[^\\]\/[gim]{0,3}/gi; - while ((m = regexes.exec(code))) { - code = preserveToken(code, m, t++); - regexes.lastIndex = m.index; - } - - // Create newlines after ; - code = code.replace(/;/g, ";\n"); - - // Create newlines after { and around } - code = code.replace(/{/g, "{\n"); - code = code.replace(/}/g, "\n}\n"); - - // Remove carriage returns - code = code.replace(/\r/g, ""); - - // Remove all indentation - code = code.replace(/^\s+/g, ""); - code = code.replace(/\n\s+/g, "\n"); - - // Remove trailing spaces - code = code.replace(/\s*$/g, ""); - - // Remove newlines before { - code = code.replace(/\n{/g, "{"); - - // Indent - var i = 0, - level = 0; - while (i < code.length) { - switch (code[i]) { - case "{": - level++; - break; - case "\n": - if (i+1 >= code.length) break; - - if (code[i+1] === "}") level--; - var 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++; - } - - // Add strategic spaces - code = code.replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= "); - code = code.replace(/\s*<([=]?)\s*/g, " <$1 "); - code = code.replace(/\s*>([=]?)\s*/g, " >$1 "); - code = code.replace(/([^+])\+([^+=])/g, "$1 + $2"); - code = code.replace(/([^-])-([^-=])/g, "$1 - $2"); - code = code.replace(/([^*])\*([^*=])/g, "$1 * $2"); - code = code.replace(/([^/])\/([^/=])/g, "$1 / $2"); - code = code.replace(/\s*,\s*/g, ", "); - code = code.replace(/\s*{/g, " {"); - code = code.replace(/}\n/g, "}\n\n"); - - // Just... don't look at this - code = code.replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3"); - code = code.replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3"); - code = code.replace(/else\s*\n([^{])/gim, "else\n $1"); - code = code.replace(/else\s+([^{])/gim, "else $1"); - - // Remove strategic spaces - code = code.replace(/\s+;/g, ";"); - code = code.replace(/\{\s+\}/g, "{}"); - code = code.replace(/\[\s+\]/g, "[]"); - code = code.replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1"); - - - // Replace preserved tokens - var ptokens = /###preservedToken(\d+)###/g; - while ((m = ptokens.exec(code))) { - var 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) { - var query = args[0], - delimiter = args[1]; - - var doc; - try { - doc = new dom().parseFromString(input); - } catch (err) { - return "Invalid input XML."; - } - - var nodes; - try { - nodes = xpath.select(query, doc); - } catch (err) { - return "Invalid XPath. Details:\n" + err.message; - } - - var 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) { - var 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; - } - - var 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); - }, - -}; - -export default Code; diff --git a/src/core/operations/Colossus.mjs b/src/core/operations/Colossus.mjs new file mode 100644 index 00000000..e9f00cf1 --- /dev/null +++ b/src/core/operations/Colossus.mjs @@ -0,0 +1,586 @@ +/** + * Emulation of Colossus. + * + * Tested against the Colossus Rebuild at Bletchley Park's TNMOC + * using a variety of inputs and settings to confirm correctness. + * + * @author VirtualColossus [martin@virtualcolossus.co.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { ColossusComputer } from "../lib/Colossus.mjs"; +import { SWITCHES, VALID_ITA2 } from "../lib/Lorenz.mjs"; + +/** + * Colossus operation + */ +class Colossus extends Operation { + + /** + * Colossus constructor + */ + constructor() { + super(); + this.name = "Colossus"; + this.module = "Bletchley"; + this.description = "Colossus is the name of the world's first electronic computer. Ten Colossi were designed by Tommy Flowers and built at the Post Office Research Labs at Dollis Hill in 1943 during World War 2. They assisted with the breaking of the German Lorenz cipher attachment, a machine created to encipher communications between Hitler and his generals on the front lines.

To learn more, Virtual Colossus, an online, browser based simulation of a Colossus computer is available at virtualcolossus.co.uk.

A more detailed description of this operation can be found here."; + this.infoURL = "https://wikipedia.org/wiki/Colossus_computer"; + this.inputType = "string"; + this.outputType = "JSON"; + this.presentType = "html"; + this.args = [ + { + name: "Input", + type: "label" + }, + { + name: "Pattern", + type: "option", + value: ["KH Pattern", "ZMUG Pattern", "BREAM Pattern"] + }, + { + name: "QBusZ", + type: "option", + value: ["", "Z", "ΔZ"] + }, + { + name: "QBusΧ", + type: "option", + value: ["", "Χ", "ΔΧ"] + }, + { + name: "QBusΨ", + type: "option", + value: ["", "Ψ", "ΔΨ"] + }, + { + name: "Limitation", + type: "option", + value: ["None", "Χ2", "Χ2 + P5", "X2 + Ψ1", "X2 + Ψ1 + P5"] + }, + { + name: "K Rack Option", + type: "argSelector", + value: [ + { + name: "Select Program", + on: [7], + off: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40] + }, + { + name: "Top Section - Conditional", + on: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30], + off: [7, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40] + }, + { + name: "Bottom Section - Addition", + on: [31, 32, 33, 34, 35, 36, 37, 38, 39, 40], + off: [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + }, + { + name: "Advanced", + on: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40], + off: [7] + } + ] + }, + { + name: "Program to run", + type: "option", + value: ["", "Letter Count", "1+2=. (1+2 Break In, Find X1,X2)", "4=5=/1=2 (Given X1,X2 find X4,X5)", "/,5,U (Count chars to find X3)"] + }, + { + name: "K Rack: Conditional", + type: "label" + }, + { + name: "R1-Q1", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R1-Q2", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R1-Q3", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R1-Q4", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R1-Q5", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R1-Negate", + type: "boolean", + value: false + }, + { + name: "R1-Counter", + type: "option", + value: ["", "1", "2", "3", "4", "5"] + }, + { + name: "R2-Q1", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R2-Q2", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R2-Q3", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R2-Q4", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R2-Q5", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R2-Negate", + type: "boolean", + value: false + }, + { + name: "R2-Counter", + type: "option", + value: ["", "1", "2", "3", "4", "5"] + }, + { + name: "R3-Q1", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R3-Q2", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R3-Q3", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R3-Q4", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R3-Q5", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R3-Negate", + type: "boolean", + value: false + }, + { + name: "R3-Counter", + type: "option", + value: ["", "1", "2", "3", "4", "5"] + }, + { + name: "Negate All", + type: "boolean", + value: false + }, + { + name: "K Rack: Addition", + type: "label" + }, + { + name: "Add-Q1", + type: "boolean", + value: false + }, + { + name: "Add-Q2", + type: "boolean", + value: false + }, + { + name: "Add-Q3", + type: "boolean", + value: false + }, + { + name: "Add-Q4", + type: "boolean", + value: false + }, + { + name: "Add-Q5", + type: "boolean", + value: false + }, + { + name: "Add-Equals", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "Add-Counter1", + type: "boolean", + value: false + }, + { + name: "Add Negate All", + type: "boolean", + value: false + }, + { + name: "Total Motor", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "Master Control Panel", + type: "label" + }, + { + name: "Set Total", + type: "number", + value: 0 + }, + { + name: "Fast Step", + type: "option", + value: ["", "X1", "X2", "X3", "X4", "X5", "M37", "M61", "S1", "S2", "S3", "S4", "S5"] + }, + { + name: "Slow Step", + type: "option", + value: ["", "X1", "X2", "X3", "X4", "X5", "M37", "M61", "S1", "S2", "S3", "S4", "S5"] + }, + { + name: "Start Χ1", + type: "number", + value: 1 + }, + { + name: "Start Χ2", + type: "number", + value: 1 + }, + { + name: "Start Χ3", + type: "number", + value: 1 + }, + { + name: "Start Χ4", + type: "number", + value: 1 + }, + { + name: "Start Χ5", + type: "number", + value: 1 + }, + { + name: "Start M61", + type: "number", + value: 1 + }, + { + name: "Start M37", + type: "number", + value: 1 + }, + { + name: "Start Ψ1", + type: "number", + value: 1 + }, + { + name: "Start Ψ2", + type: "number", + value: 1 + }, + { + name: "Start Ψ3", + type: "number", + value: 1 + }, + { + name: "Start Ψ4", + type: "number", + value: 1 + }, + { + name: "Start Ψ5", + type: "number", + value: 1 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {Object} + */ + run(input, args) { + input = input.toUpperCase(); + for (const character of input) { + if (VALID_ITA2.indexOf(character) === -1) { + let errltr = character; + if (errltr === "\n") errltr = "Carriage Return"; + if (errltr === " ") errltr = "Space"; + throw new OperationError("Invalid ITA2 character : " + errltr); + } + } + + const pattern = args[1]; + const qbusin = { + "Z": args[2], + "Chi": args[3], + "Psi": args[4], + }; + + const limitation = args[5]; + const lm = [false, false, false]; + if (limitation.includes("Χ2")) lm[0] = true; + if (limitation.includes("Ψ1")) lm[1] = true; + if (limitation.includes("P5")) lm[2] = true; + const limit = { + X2: lm[0], S1: lm[1], P5: lm[2] + }; + + const KRackOpt = args[6]; + const setProgram = args[7]; + + if (KRackOpt === "Select Program" && setProgram !== "") { + args = this.selectProgram(setProgram, args); + } + + const re = new RegExp("^$|^[.x]$"); + for (let qr=0;qr<3;qr++) { + for (let a=0;a<5;a++) { + if (!re.test(args[((qr*7)+(a+9))])) + throw new OperationError("Switch R"+(qr+1)+"-Q"+(a+1)+" can only be set to blank, . or x"); + } + } + + if (!re.test(args[37])) throw new OperationError("Switch Add-Equals can only be set to blank, . or x"); + if (!re.test(args[40])) throw new OperationError("Switch Total Motor can only be set to blank, . or x"); + + // Q1,Q2,Q3,Q4,Q5,negate,counter1 + const qbusswitches = { + condition: [ + {Qswitches: [args[9], args[10], args[11], args[12], args[13]], Negate: args[14], Counter: args[15]}, + {Qswitches: [args[16], args[17], args[18], args[19], args[20]], Negate: args[21], Counter: args[22]}, + {Qswitches: [args[23], args[24], args[25], args[26], args[27]], Negate: args[28], Counter: args[29]} + ], + condNegateAll: args[30], + addition: [ + {Qswitches: [args[32], args[33], args[34], args[35], args[36]], Equals: args[37], C1: args[38]} + ], + addNegateAll: args[39], + totalMotor: args[40] + }; + + const settotal = parseInt(args[42], 10); + if (settotal < 0 || settotal > 9999) + throw new OperationError("Set Total must be between 0000 and 9999"); + + // null|fast|slow for each of S1-5,M1-2,X1-5 + const control = { + fast: args[43], + slow: args[44] + }; + + // Start positions + if (args[52]<1 || args[52]>43) throw new OperationError("Ψ1 start must be between 1 and 43"); + if (args[53]<1 || args[53]>47) throw new OperationError("Ψ2 start must be between 1 and 47"); + if (args[54]<1 || args[54]>51) throw new OperationError("Ψ3 start must be between 1 and 51"); + if (args[55]<1 || args[55]>53) throw new OperationError("Ψ4 start must be between 1 and 53"); + if (args[56]<1 || args[57]>59) throw new OperationError("Ψ5 start must be between 1 and 59"); + if (args[51]<1 || args[51]>37) throw new OperationError("Μ37 start must be between 1 and 37"); + if (args[50]<1 || args[50]>61) throw new OperationError("Μ61 start must be between 1 and 61"); + if (args[45]<1 || args[45]>41) throw new OperationError("Χ1 start must be between 1 and 41"); + if (args[46]<1 || args[46]>31) throw new OperationError("Χ2 start must be between 1 and 31"); + if (args[47]<1 || args[47]>29) throw new OperationError("Χ3 start must be between 1 and 29"); + if (args[48]<1 || args[48]>26) throw new OperationError("Χ4 start must be between 1 and 26"); + if (args[49]<1 || args[49]>23) throw new OperationError("Χ5 start must be between 1 and 23"); + + const starts = { + X1: args[45], X2: args[46], X3: args[47], X4: args[48], X5: args[49], + M61: args[50], M37: args[51], + S1: args[52], S2: args[53], S3: args[54], S4: args[55], S5: args[56] + }; + + const colossus = new ColossusComputer(input, pattern, qbusin, qbusswitches, control, starts, settotal, limit); + const result = colossus.run(); + + return result; + } + + /** + * Select Program + * + * @param {string} progname + * @param {Object[]} args + * @returns {Object[]} + */ + selectProgram(progname, args) { + + // Basic Letter Count + if (progname === "Letter Count") { + // Set Conditional R1 : count every character into counter 1 + args[9] = ""; + args[10] = ""; + args[11] = ""; + args[12] = ""; + args[13] = ""; + args[14] = false; + args[15] = "1"; + // clear Conditional R2 & R3 + args[22] = ""; + args[29] = ""; + // Clear Negate result + args[30] = false; + // Clear Addition row counter + args[38] = false; + } + + // Bill Tutte's 1+2 Break In + if (progname === "1+2=. (1+2 Break In, Find X1,X2)") { + // Clear any other counters + args[15] = ""; // Conditional R1 + args[22] = ""; // Conditional R2 + args[29] = ""; // Conditional R3 + // Set Add Q1+Q2=. into Counter 1 + args[32] = true; + args[33] = true; + args[34] = false; + args[35] = false; + args[36] = false; + args[37] = "."; + args[38] = true; + } + + // 4=3=/1=2 : Find X4 & X5 where X1 & X2 are known + if (progname === "4=5=/1=2 (Given X1,X2 find X4,X5)") { + // Set Conditional R1 : Match NOT ..?.. into counter 1 + args[9] = "."; + args[10] = "."; + args[11] = ""; + args[12] = "."; + args[13] = "."; + args[14] = true; + args[15] = "1"; + // Set Conditional R2 : AND Match NOT xx?xx into counter 1 + args[16] = "x"; + args[17] = "x"; + args[18] = ""; + args[19] = "x"; + args[20] = "x"; + args[21] = true; + args[22] = "1"; + // clear Conditional R3 + args[29] = ""; + // Negate result, giving NOT(NOT Q1 AND NOT Q2) which is equivalent to Q1 OR Q2 + args[30] = true; + // Clear Addition row counter + args[38] = false; + } + + // /,5,U : Count number of matches of /, 5 & U to find X3 + if (progname === "/,5,U (Count chars to find X3)") { + // Set Conditional R1 : Match / char, ITA2 = ..... into counter 1 + args[9] = "."; + args[10] = "."; + args[11] = "."; + args[12] = "."; + args[13] = "."; + args[14] = false; + args[15] = "1"; + // Set Conditional R2 : Match 5 char, ITA2 = xx.xx into counter 2 + args[16] = "x"; + args[17] = "x"; + args[18] = "."; + args[19] = "x"; + args[20] = "x"; + args[21] = false; + args[22] = "2"; + // Set Conditional R3 : Match U char, ITA2 = xxx.. into counter 3 + args[23] = "x"; + args[24] = "x"; + args[25] = "x"; + args[26] = "."; + args[27] = "."; + args[28] = false; + args[29] = "3"; + // Clear Negate result + args[30] = false; + // Clear Addition row counter + args[38] = false; + } + + return args; + } + + /** + * Displays Colossus results in an HTML table + * + * @param {Object} output + * @param {Object[]} output.counters + * @returns {html} + */ + present(output) { + let html = "Colossus Printer\n\n"; + html += output.printout + "\n\n"; + html += "Colossus Counters\n\n"; + html += "\n"; + html += ""; + for (const ct of output.counters) { + html += `\n`; + } + html += ""; + html += "
C1 C2 C3 C4 C5
${ct}
"; + return html; + } + +} + +export default Colossus; diff --git a/src/core/operations/Comment.mjs b/src/core/operations/Comment.mjs new file mode 100644 index 00000000..af74cf48 --- /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.mjs"; + +/** + * 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..91956220 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {HASH_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import ctphjs from "ctph.js"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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://forensics.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..9937d7e6 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {HASH_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import ssdeepjs from "ssdeep.js"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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://forensics.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 d1236560..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"; - -var 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) { - var 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)); - var 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. - var valid = false; - for (var 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) { - var 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)); - var 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) { - var 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; - } - - var 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)); - var 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) { - var 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) { - var options = { - password: Utils.strToByteArray(args[0]), - verify: args[1] - }, - unzip = new Zlib.Unzip(input, options), - filenames = unzip.getFilenames(), - files = []; - - filenames.forEach(function(fileName) { - var bytes = unzip.decompress(fileName); - var contents = Utils.byteArrayToUtf8(bytes); - - var file = { - fileName: fileName, - size: contents.length, - }; - - var 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) { - var 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) { - var Tarball = function() { - this.bytes = new Array(512); - this.position = 0; - }; - - Tarball.prototype.addEmptyBlock = function() { - var filler = new Array(512); - filler.fill(0); - this.bytes = this.bytes.concat(filler); - }; - - Tarball.prototype.writeBytes = function(bytes) { - var 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() { - var numEmptyBlocks = 2; - for (var i = 0; i < numEmptyBlocks; i++) { - this.addEmptyBlock(); - } - }; - - var fileSize = Utils.padLeft(input.length.toString(8), 11, "0"); - var currentUnixTimestamp = Math.floor(Date.now() / 1000); - var lastModTime = Utils.padLeft(currentUnixTimestamp.toString(8), 11, "0"); - - var 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), - }; - - var checksum = 0; - for (var key in file) { - var 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; - - var 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) { - var Stream = function(input) { - this.bytes = input; - this.position = 0; - }; - - Stream.prototype.getBytes = function(bytesToGet) { - var newPosition = this.position + bytesToGet; - var bytes = this.bytes.slice(this.position, newPosition); - this.position = newPosition; - return bytes; - }; - - Stream.prototype.readString = function(numBytes) { - var result = ""; - for (var i = this.position; i < this.position + numBytes; i++) { - var currentByte = this.bytes[i]; - if (currentByte === 0) break; - result += String.fromCharCode(currentByte); - } - this.position += numBytes; - return result; - }; - - Stream.prototype.readInt = function(numBytes, base) { - var string = this.readString(numBytes); - return parseInt(string, base); - }; - - Stream.prototype.hasMore = function() { - return this.position < this.bytes.length; - }; - - var stream = new Stream(input), - files = []; - - while (stream.hasMore()) { - var dataPosition = stream.position + 512; - - var 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); - var 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..74bf1d21 --- /dev/null +++ b/src/core/operations/ConditionalJump.mjs @@ -0,0 +1,87 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Dish from "../Dish.mjs"; +import { getLabelIndex } from "../lib/FlowControl.mjs"; + +/** + * 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) { + state.numJumps = 0; + 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++; + } else { + state.numJumps = 0; + } + } + + return state; + } + +} + +export default ConditionalJump; diff --git a/src/core/operations/ContainImage.mjs b/src/core/operations/ContainImage.mjs new file mode 100644 index 00000000..853021e1 --- /dev/null +++ b/src/core/operations/ContainImage.mjs @@ -0,0 +1,162 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Contain Image operation + */ +class ContainImage extends Operation { + + /** + * ContainImage constructor + */ + constructor() { + super(); + + this.name = "Contain Image"; + this.module = "Image"; + this.description = "Scales an image to the specified width and height, maintaining the aspect ratio. The image may be letterboxed."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Width", + type: "number", + value: 100, + min: 1 + }, + { + name: "Height", + type: "number", + value: 100, + min: 1 + }, + { + name: "Horizontal align", + type: "option", + value: [ + "Left", + "Center", + "Right" + ], + defaultIndex: 1 + }, + { + name: "Vertical align", + type: "option", + value: [ + "Top", + "Middle", + "Bottom" + ], + defaultIndex: 1 + }, + { + name: "Resizing algorithm", + type: "option", + value: [ + "Nearest Neighbour", + "Bilinear", + "Bicubic", + "Hermite", + "Bezier" + ], + defaultIndex: 1 + }, + { + name: "Opaque background", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [width, height, hAlign, vAlign, alg, opaqueBg] = args; + + const resizeMap = { + "Nearest Neighbour": Jimp.RESIZE_NEAREST_NEIGHBOR, + "Bilinear": Jimp.RESIZE_BILINEAR, + "Bicubic": Jimp.RESIZE_BICUBIC, + "Hermite": Jimp.RESIZE_HERMITE, + "Bezier": Jimp.RESIZE_BEZIER + }; + + const alignMap = { + "Left": Jimp.HORIZONTAL_ALIGN_LEFT, + "Center": Jimp.HORIZONTAL_ALIGN_CENTER, + "Right": Jimp.HORIZONTAL_ALIGN_RIGHT, + "Top": Jimp.VERTICAL_ALIGN_TOP, + "Middle": Jimp.VERTICAL_ALIGN_MIDDLE, + "Bottom": Jimp.VERTICAL_ALIGN_BOTTOM + }; + + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("Containing image..."); + image.contain(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]); + + if (opaqueBg) { + const newImage = await Jimp.read(width, height, 0x000000FF); + newImage.blit(image, 0, 0); + image = newImage; + } + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error containing image. (${err})`); + } + } + + /** + * Displays the contained image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default ContainImage; diff --git a/src/core/operations/Convert.js b/src/core/operations/Convert.js deleted file mode 100755 index 972b319f..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) { - var 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) { - var 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) { - var 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) { - var 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) { - var 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..4cce31b1 --- /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.mjs"; + +/** + * 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..f1e1b20f --- /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.mjs"; +import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates.mjs"; + +/** + * 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..0335e852 --- /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.mjs"; + +/** + * 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..1a5fc8af --- /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.mjs"; + +/** + * 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/ConvertImageFormat.mjs b/src/core/operations/ConvertImageFormat.mjs new file mode 100644 index 00000000..7d57f33b --- /dev/null +++ b/src/core/operations/ConvertImageFormat.mjs @@ -0,0 +1,143 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Convert Image Format operation + */ +class ConvertImageFormat extends Operation { + + /** + * ConvertImageFormat constructor + */ + constructor() { + super(); + + this.name = "Convert Image Format"; + this.module = "Image"; + this.description = "Converts an image between different formats. Supported formats:
  • Joint Photographic Experts Group (JPEG)
  • Portable Network Graphics (PNG)
  • Bitmap (BMP)
  • Tagged Image File Format (TIFF)

Note: GIF files are supported for input, but cannot be outputted."; + this.infoURL = "https://wikipedia.org/wiki/Image_file_formats"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Output Format", + type: "option", + value: [ + "JPEG", + "PNG", + "BMP", + "TIFF" + ] + }, + { + name: "JPEG Quality", + type: "number", + value: 80, + min: 1, + max: 100 + }, + { + name: "PNG Filter Type", + type: "option", + value: [ + "Auto", + "None", + "Sub", + "Up", + "Average", + "Paeth" + ] + }, + { + name: "PNG Deflate Level", + type: "number", + value: 9, + min: 0, + max: 9 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [format, jpegQuality, pngFilterType, pngDeflateLevel] = args; + const formatMap = { + "JPEG": Jimp.MIME_JPEG, + "PNG": Jimp.MIME_PNG, + "BMP": Jimp.MIME_BMP, + "TIFF": Jimp.MIME_TIFF + }; + + const pngFilterMap = { + "Auto": Jimp.PNG_FILTER_AUTO, + "None": Jimp.PNG_FILTER_NONE, + "Sub": Jimp.PNG_FILTER_SUB, + "Up": Jimp.PNG_FILTER_UP, + "Average": Jimp.PNG_FILTER_AVERAGE, + "Paeth": Jimp.PNG_FILTER_PATH + }; + + const mime = formatMap[format]; + + if (!isImage(input)) { + throw new OperationError("Invalid file format."); + } + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error opening image file. (${err})`); + } + try { + switch (format) { + case "JPEG": + image.quality(jpegQuality); + break; + case "PNG": + image.filterType(pngFilterMap[pngFilterType]); + image.deflateLevel(pngDeflateLevel); + break; + } + + const imageBuffer = await image.getBufferAsync(mime); + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error converting image format. (${err})`); + } + } + + /** + * Displays the converted image using HTML for web apps + * + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default ConvertImageFormat; diff --git a/src/core/operations/ConvertLeetSpeak.mjs b/src/core/operations/ConvertLeetSpeak.mjs new file mode 100644 index 00000000..1a7cb2fc --- /dev/null +++ b/src/core/operations/ConvertLeetSpeak.mjs @@ -0,0 +1,116 @@ +/** + * @author bartblaze [] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Convert Leet Speak operation + */ +class ConvertLeetSpeak extends Operation { + /** + * ConvertLeetSpeak constructor + */ + constructor() { + super(); + + this.name = "Convert Leet Speak"; + this.module = "Default"; + this.description = "Converts to and from Leet Speak."; + this.infoURL = "https://wikipedia.org/wiki/Leet"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Direction", + type: "option", + value: ["To Leet Speak", "From Leet Speak"], + defaultIndex: 0 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const direction = args[0]; + + if (direction === "To Leet Speak") { + return input.replace(/[a-z]/gi, char => { + const leetChar = toLeetMap[char.toLowerCase()] || char; + return char === char.toUpperCase() ? leetChar.toUpperCase() : leetChar; + }); + } else if (direction === "From Leet Speak") { + return input.replace(/[48cd3f6h1jklmn0pqr57uvwxyz]/gi, char => { + const normalChar = fromLeetMap[char] || char; + return normalChar; + }); + } + } +} + +const toLeetMap = { + "a": "4", + "b": "b", + "c": "c", + "d": "d", + "e": "3", + "f": "f", + "g": "g", + "h": "h", + "i": "1", + "j": "j", + "k": "k", + "l": "l", + "m": "m", + "n": "n", + "o": "0", + "p": "p", + "q": "q", + "r": "r", + "s": "5", + "t": "7", + "u": "u", + "v": "v", + "w": "w", + "x": "x", + "y": "y", + "z": "z" +}; + +const fromLeetMap = { + "4": "a", + "b": "b", + "c": "c", + "d": "d", + "3": "e", + "f": "f", + "g": "g", + "h": "h", + "1": "i", + "j": "j", + "k": "k", + "l": "l", + "m": "m", + "n": "n", + "0": "o", + "p": "p", + "q": "q", + "r": "r", + "5": "s", + "7": "t", + "u": "u", + "v": "v", + "w": "w", + "x": "x", + "y": "y", + "z": "z" +}; + +export default ConvertLeetSpeak; + diff --git a/src/core/operations/ConvertMass.mjs b/src/core/operations/ConvertMass.mjs new file mode 100644 index 00000000..712884ed --- /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.mjs"; + +/** + * 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..7fc6718d --- /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.mjs"; + +/** + * 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/ConvertToNATOAlphabet.mjs b/src/core/operations/ConvertToNATOAlphabet.mjs new file mode 100644 index 00000000..ee3b50c9 --- /dev/null +++ b/src/core/operations/ConvertToNATOAlphabet.mjs @@ -0,0 +1,82 @@ +/** + * @author MarvinJWendt [git@marvinjwendt.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Convert to NATO alphabet operation + */ +class ConvertToNATOAlphabet extends Operation { + /** + * ConvertToNATOAlphabet constructor + */ + constructor() { + super(); + + this.name = "Convert to NATO alphabet"; + this.module = "Default"; + this.description = "Converts characters to their representation in the NATO phonetic alphabet."; + this.infoURL = "https://wikipedia.org/wiki/NATO_phonetic_alphabet"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.replace(/[a-z0-9,/.]/ig, letter => { + return lookup[letter.toUpperCase()]; + }); + } +} + +const lookup = { + "A": "Alfa ", + "B": "Bravo ", + "C": "Charlie ", + "D": "Delta ", + "E": "Echo ", + "F": "Foxtrot ", + "G": "Golf ", + "H": "Hotel ", + "I": "India ", + "J": "Juliett ", + "K": "Kilo ", + "L": "Lima ", + "M": "Mike ", + "N": "November ", + "O": "Oscar ", + "P": "Papa ", + "Q": "Quebec ", + "R": "Romeo ", + "S": "Sierra ", + "T": "Tango ", + "U": "Uniform ", + "V": "Victor ", + "W": "Whiskey ", + "X": "X-ray ", + "Y": "Yankee ", + "Z": "Zulu ", + "0": "Zero ", + "1": "One ", + "2": "Two ", + "3": "Three ", + "4": "Four ", + "5": "Five ", + "6": "Six ", + "7": "Seven ", + "8": "Eight ", + "9": "Nine ", + ",": "Comma ", + "/": "Fraction bar ", + ".": "Full stop ", +}; + +export default ConvertToNATOAlphabet; diff --git a/src/core/operations/CountOccurrences.mjs b/src/core/operations/CountOccurrences.mjs new file mode 100644 index 00000000..36ee33a8 --- /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.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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/CoverImage.mjs b/src/core/operations/CoverImage.mjs new file mode 100644 index 00000000..7d4d07b9 --- /dev/null +++ b/src/core/operations/CoverImage.mjs @@ -0,0 +1,150 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import jimp from "jimp/es/index.js"; + +/** + * Cover Image operation + */ +class CoverImage extends Operation { + + /** + * CoverImage constructor + */ + constructor() { + super(); + + this.name = "Cover Image"; + this.module = "Image"; + this.description = "Scales the image to the given width and height, keeping the aspect ratio. The image may be clipped."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Width", + type: "number", + value: 100, + min: 1 + }, + { + name: "Height", + type: "number", + value: 100, + min: 1 + }, + { + name: "Horizontal align", + type: "option", + value: [ + "Left", + "Center", + "Right" + ], + defaultIndex: 1 + }, + { + name: "Vertical align", + type: "option", + value: [ + "Top", + "Middle", + "Bottom" + ], + defaultIndex: 1 + }, + { + name: "Resizing algorithm", + type: "option", + value: [ + "Nearest Neighbour", + "Bilinear", + "Bicubic", + "Hermite", + "Bezier" + ], + defaultIndex: 1 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [width, height, hAlign, vAlign, alg] = args; + + const resizeMap = { + "Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR, + "Bilinear": jimp.RESIZE_BILINEAR, + "Bicubic": jimp.RESIZE_BICUBIC, + "Hermite": jimp.RESIZE_HERMITE, + "Bezier": jimp.RESIZE_BEZIER + }; + + const alignMap = { + "Left": jimp.HORIZONTAL_ALIGN_LEFT, + "Center": jimp.HORIZONTAL_ALIGN_CENTER, + "Right": jimp.HORIZONTAL_ALIGN_RIGHT, + "Top": jimp.VERTICAL_ALIGN_TOP, + "Middle": jimp.VERTICAL_ALIGN_MIDDLE, + "Bottom": jimp.VERTICAL_ALIGN_BOTTOM + }; + + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("Covering image..."); + image.cover(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]); + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error covering image. (${err})`); + } + } + + /** + * Displays the covered image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default CoverImage; diff --git a/src/core/operations/CropImage.mjs b/src/core/operations/CropImage.mjs new file mode 100644 index 00000000..11a85e37 --- /dev/null +++ b/src/core/operations/CropImage.mjs @@ -0,0 +1,151 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Crop Image operation + */ +class CropImage extends Operation { + + /** + * CropImage constructor + */ + constructor() { + super(); + + this.name = "Crop Image"; + this.module = "Image"; + this.description = "Crops an image to the specified region, or automatically crops edges.

Autocrop
Automatically crops same-colour borders from the image.

Autocrop tolerance
A percentage value for the tolerance of colour difference between pixels.

Only autocrop frames
Only crop real frames (all sides must have the same border)

Symmetric autocrop
Force autocrop to be symmetric (top/bottom and left/right are cropped by the same amount)

Autocrop keep border
The number of pixels of border to leave around the image."; + this.infoURL = "https://wikipedia.org/wiki/Cropping_(image)"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "X Position", + type: "number", + value: 0, + min: 0 + }, + { + name: "Y Position", + type: "number", + value: 0, + min: 0 + }, + { + name: "Width", + type: "number", + value: 10, + min: 1 + }, + { + name: "Height", + type: "number", + value: 10, + min: 1 + }, + { + name: "Autocrop", + type: "boolean", + value: false + }, + { + name: "Autocrop tolerance (%)", + type: "number", + value: 0.02, + min: 0, + max: 100, + step: 0.01 + }, + { + name: "Only autocrop frames", + type: "boolean", + value: true + }, + { + name: "Symmetric autocrop", + type: "boolean", + value: false + }, + { + name: "Autocrop keep border (px)", + type: "number", + value: 0, + min: 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [xPos, yPos, width, height, autocrop, autoTolerance, autoFrames, autoSymmetric, autoBorder] = args; + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("Cropping image..."); + if (autocrop) { + image.autocrop({ + tolerance: (autoTolerance / 100), + cropOnlyFrames: autoFrames, + cropSymmetric: autoSymmetric, + leaveBorder: autoBorder + }); + } else { + image.crop(xPos, yPos, width, height); + } + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error cropping image. (${err})`); + } + } + + /** + * Displays the cropped image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default CropImage; diff --git a/src/core/operations/DESDecrypt.mjs b/src/core/operations/DESDecrypt.mjs new file mode 100644 index 00000000..4b1ab40e --- /dev/null +++ b/src/core/operations/DESDecrypt.mjs @@ -0,0 +1,108 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; + +/** + * 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).

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 as a default."; + 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", "CBC/NoPadding", "ECB/NoPadding"] + }, + { + "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].substring(0, 3), + noPadding = args[2].endsWith("NoPadding"), + [,,, 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).`); + } + if (iv.length !== 8 && mode !== "ECB") { + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +DES uses an IV length of 8 bytes (64 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = forge.cipher.createDecipher("DES-" + mode, key); + + /* Allow for a "no padding" mode */ + if (noPadding) { + decipher.mode.unpad = function(output, options) { + return true; + }; + } + + 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..28d6202a --- /dev/null +++ b/src/core/operations/DESEncrypt.mjs @@ -0,0 +1,94 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; + +/** + * 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).

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).`); + } + if (iv.length !== 8 && mode !== "ECB") { + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +DES uses an IV length of 8 bytes (64 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + } + + 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..87381226 --- /dev/null +++ b/src/core/operations/DNSOverHTTPS.mjs @@ -0,0 +1,142 @@ +/** + * @author h345983745 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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", + "ANAME", + "CERT", + "CNAME", + "DNSKEY", + "HTTPS", + "IPSECKEY", + "LOC", + "MX", + "NS", + "OPENPGPKEY", + "PTR", + "RRSIG", + "SIG", + "SOA", + "SPF", + "SRV", + "SSHFP", + "TA", + "TXT", + "URI", + "ANY" + ] + }, + { + name: "Answer Data Only", + type: "boolean", + value: false + }, + { + name: "Disable DNSSEC validation", + type: "boolean", + value: false + } + ]; + } + + /** + * @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 78f1c0b4..00000000 --- a/src/core/operations/DateTime.js +++ /dev/null @@ -1,453 +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) { - var 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"; - } - }, - - - /** - * To UNIX Timestamp operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {number} - */ - runToUnixTimestamp: function(input, args) { - var units = args[0], - d = 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) { - var 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) { - var 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/DateTimeDelta.mjs b/src/core/operations/DateTimeDelta.mjs new file mode 100644 index 00000000..7f82cf01 --- /dev/null +++ b/src/core/operations/DateTimeDelta.mjs @@ -0,0 +1,107 @@ +/** + * @author tomgond [tom.gonda@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import moment from "moment-timezone"; +import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime.mjs"; + +/** + * DateTime Delta operation + */ +class DateTimeDelta extends Operation { + + /** + * DateTimeDelta constructor + */ + constructor() { + super(); + + this.name = "DateTime Delta"; + this.module = "Default"; + this.description = "Calculates a new DateTime value given an input DateTime value and a time difference (delta) from the input DateTime value."; + 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": "Time Operation", + "type": "option", + "value": ["Add", "Subtract"] + }, + { + "name": "Days", + "type": "number", + "value": 0 + }, + { + "name": "Hours", + "type": "number", + "value": 0 + }, + { + "name": "Minutes", + "type": "number", + "value": 0 + }, + { + "name": "Seconds", + "type": "number", + "value": 0 + } + + ]; + } + + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const inputTimezone = "UTC"; + const inputFormat = args[1]; + const operationType = args[2]; + const daysDelta = args[3]; + const hoursDelta = args[4]; + const minutesDelta = args[5]; + const secondsDelta = args[6]; + 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}`; + } + let newDate; + if (operationType === "Add") { + newDate = date.add(daysDelta, "days") + .add(hoursDelta, "hours") + .add(minutesDelta, "minutes") + .add(secondsDelta, "seconds"); + + } else { + newDate = date.add(-daysDelta, "days") + .add(-hoursDelta, "hours") + .add(-minutesDelta, "minutes") + .add(-secondsDelta, "seconds"); + } + return newDate.tz(inputTimezone).format(inputFormat.replace(/[<>]/g, "")); + } +} + +export default DateTimeDelta; diff --git a/src/core/operations/DechunkHTTPResponse.mjs b/src/core/operations/DechunkHTTPResponse.mjs new file mode 100644 index 00000000..da2eb437 --- /dev/null +++ b/src/core/operations/DechunkHTTPResponse.mjs @@ -0,0 +1,58 @@ +/** + * @author sevzero [sevzero@protonmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * 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 = []; + this.checks = [ + { + pattern: "^[0-9A-F]+\r\n", + flags: "i", + 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..c726f5d8 --- /dev/null +++ b/src/core/operations/DecodeNetBIOSName.mjs @@ -0,0 +1,67 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * 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 + } + ]; + this.checks = [ + { + pattern: "^\\s*\\S{32}$", + flags: "", + args: [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..0fc9d2b5 --- /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.mjs"; +import cptable from "codepage"; +import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs"; + +/** + * 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(CHR_ENC_CODE_PAGES).map(e => `
  • ${e}
  • `).join("\n"), + "
", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Encoding", + "type": "option", + "value": Object.keys(CHR_ENC_CODE_PAGES) + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const format = CHR_ENC_CODE_PAGES[args[0]]; + return cptable.utils.decode(format, new Uint8Array(input)); + } + +} + +export default DecodeText; diff --git a/src/core/operations/DefangIPAddresses.mjs b/src/core/operations/DefangIPAddresses.mjs new file mode 100644 index 00000000..a869f114 --- /dev/null +++ b/src/core/operations/DefangIPAddresses.mjs @@ -0,0 +1,71 @@ +/** + * @author h345983745 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + + +/** + * Defang IP Addresses operation + */ +class DefangIPAddresses extends Operation { + + /** + * DefangIPAddresses constructor + */ + constructor() { + super(); + + this.name = "Defang IP Addresses"; + this.module = "Default"; + this.description = "Takes a IPv4 or IPv6 address and 'Defangs' it, meaning the IP becomes invalid, removing the risk of accidentally utilising it as an IP address."; + this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + pattern: "^\\s*(([0-9]{1,3}\\.){3}[0-9]{1,3}|([0-9a-f]{4}:){7}[0-9a-f]{4})\\s*$", + flags: "i", + args: [], + output: { + pattern: "^\\s*(([0-9]{1,3}\\[\\.\\]){3}[0-9]{1,3}|([0-9a-f]{4}\\[\\:\\]){7}[0-9a-f]{4})\\s*$", + flags: "i" + } + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = input.replace(IPV4_REGEX, x => { + return x.replace(/\./g, "[.]"); + }); + + input = input.replace(IPV6_REGEX, x => { + return x.replace(/:/g, "[:]"); + }); + + return input; + } +} + +export default DefangIPAddresses; + + +/** + * IPV4 regular expression + */ +const IPV4_REGEX = new RegExp("(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?", "g"); + + +/** + * IPV6 regular expression + */ +const IPV6_REGEX = new RegExp("((?=.*::)(?!.*::.+::)(::)?([\\dA-Fa-f]{1,4}:(:|\\b)|){5}|([\\dA-Fa-f]{1,4}:){6})((([\\dA-Fa-f]{1,4}((?!\\3)::|:\\b|(?![\\dA-Fa-f])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})", "g"); diff --git a/src/core/operations/DefangURL.mjs b/src/core/operations/DefangURL.mjs new file mode 100644 index 00000000..aa783c32 --- /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.mjs"; +import {URL_REGEX, DOMAIN_REGEX} from "../lib/Extract.mjs"; + +/** + * 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..3d67aa51 --- /dev/null +++ b/src/core/operations/DeriveEVPKey.mjs @@ -0,0 +1,147 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +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 = CryptoJS.enc.Latin1.parse( + Utils.convertToByteString(args[0].string, args[0].option)), + keySize = args[1] / 32, + iterations = args[2], + hasher = args[3], + salt = CryptoJS.enc.Latin1.parse( + Utils.convertToByteString(args[4].string, args[4].option)), + key = CryptoJS.EvpKDF(passphrase, salt, { // lgtm [js/insufficient-password-hash] + 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/DeriveHKDFKey.mjs b/src/core/operations/DeriveHKDFKey.mjs new file mode 100644 index 00000000..3c594015 --- /dev/null +++ b/src/core/operations/DeriveHKDFKey.mjs @@ -0,0 +1,138 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import CryptoApi from "crypto-api/src/crypto-api.mjs"; + +/** + * Derive HKDF Key operation + */ +class DeriveHKDFKey extends Operation { + + /** + * DeriveHKDFKey constructor + */ + constructor() { + super(); + + this.name = "Derive HKDF key"; + this.module = "Crypto"; + this.description = "A simple Hashed Message Authenticaton Code (HMAC)-based key derivation function (HKDF), defined in RFC5869."; + this.infoURL = "https://wikipedia.org/wiki/HKDF"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Salt", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"] + }, + { + "name": "Info", + "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" + ], + "defaultIndex": 6 + }, + { + "name": "Extract mode", + "type": "argSelector", + "value": [ + { + "name": "with salt", + "on": [0] + }, + { + "name": "no salt", + "off": [0] + }, + { + "name": "skip", + "off": [0] + } + ] + }, + { + "name": "L (number of output octets)", + "type": "number", + "value": 16, + "min": 0 + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const argSalt = Utils.convertToByteString(args[0].string || "", args[0].option), + info = Utils.convertToByteString(args[1].string || "", args[1].option), + hashFunc = args[2].toLowerCase(), + extractMode = args[3], + L = args[4], + IKM = Utils.arrayBufferToStr(input, false), + hasher = CryptoApi.getHasher(hashFunc), + HashLen = hasher.finalize().length; + + if (L < 0) { + throw new OperationError("L must be non-negative"); + } + if (L > 255 * HashLen) { + throw new OperationError("L too large (maximum length for " + args[2] + " is " + (255 * HashLen) + ")"); + } + + const hmacHash = function(key, data) { + hasher.reset(); + const mac = CryptoApi.getHmac(key, hasher); + mac.update(data); + return mac.finalize(); + }; + const salt = extractMode === "with salt" ? argSalt : "\0".repeat(HashLen); + const PRK = extractMode === "skip" ? IKM : hmacHash(salt, IKM); + let T = ""; + let result = ""; + for (let i = 1; i <= 255 && result.length < L; i++) { + const TNext = hmacHash(PRK, T + info + String.fromCharCode(i)); + result += TNext; + T = TNext; + } + return CryptoApi.encoder.toHex(result.substring(0, L)); + } + +} + +export default DeriveHKDFKey; diff --git a/src/core/operations/DerivePBKDF2Key.mjs b/src/core/operations/DerivePBKDF2Key.mjs new file mode 100644 index 00000000..98385dad --- /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.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; + +/** + * 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..58d13c31 --- /dev/null +++ b/src/core/operations/DetectFileType.mjs @@ -0,0 +1,81 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {detectFileType} from "../lib/FileType.mjs"; +import {FILE_SIGNATURES} from "../lib/FileSignatures.mjs"; + +// Concat all supported extensions into a single flat list +const exts = [].concat.apply([], Object.keys(FILE_SIGNATURES).map(cat => + [].concat.apply([], FILE_SIGNATURES[cat].map(sig => + sig.extension.split(",") + )) +)).unique().sort().join(", "); + +/** + * 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: " + + exts + "."; + this.infoURL = "https://wikipedia.org/wiki/List_of_file_signatures"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = Object.keys(FILE_SIGNATURES).map(cat => { + return { + name: cat, + type: "boolean", + value: true + }; + }); + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const data = new Uint8Array(input), + categories = []; + + args.forEach((cat, i) => { + if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]); + }); + + const types = detectFileType(data, categories); + + if (!types.length) { + return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?"; + } else { + const results = types.map(type => { + let output = `File type: ${type.name} +Extension: ${type.extension} +MIME type: ${type.mime}\n`; + + if (type?.description?.length) { + output += `Description: ${type.description}\n`; + } + + return output; + }); + + return results.join("\n"); + } + } + +} + +export default DetectFileType; diff --git a/src/core/operations/Diff.mjs b/src/core/operations/Diff.mjs new file mode 100644 index 00000000..9f180f86 --- /dev/null +++ b/src/core/operations/Diff.mjs @@ -0,0 +1,135 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import * as JsDiff from "diff"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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": "Show subtraction", + "type": "boolean", + "value": false + }, + { + "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, + showSubtraction, + 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 if (!showSubtraction) { + 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..bdaf348a --- /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.mjs"; +import * as disassemble from "../vendor/DisassembleX86-64.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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/DitherImage.mjs b/src/core/operations/DitherImage.mjs new file mode 100644 index 00000000..17051480 --- /dev/null +++ b/src/core/operations/DitherImage.mjs @@ -0,0 +1,87 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Image Dither operation + */ +class DitherImage extends Operation { + + /** + * DitherImage constructor + */ + constructor() { + super(); + + this.name = "Dither Image"; + this.module = "Image"; + this.description = "Apply a dither effect to an image."; + this.infoURL = "https://wikipedia.org/wiki/Dither"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("Applying dither to image..."); + image.dither565(); + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error applying dither to image. (${err})`); + } + } + + /** + * Displays the dithered image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default DitherImage; diff --git a/src/core/operations/Divide.mjs b/src/core/operations/Divide.mjs new file mode 100644 index 00000000..108c175b --- /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.mjs"; +import { div, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + + +/** + * 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..9ea105f8 --- /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.mjs"; + +/** + * 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/DropNthBytes.mjs b/src/core/operations/DropNthBytes.mjs new file mode 100644 index 00000000..e6bac1cd --- /dev/null +++ b/src/core/operations/DropNthBytes.mjs @@ -0,0 +1,79 @@ +/** + * @author Oshawk [oshawk@protonmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Drop nth bytes operation + */ +class DropNthBytes extends Operation { + + /** + * DropNthBytes constructor + */ + constructor() { + super(); + + this.name = "Drop nth bytes"; + this.module = "Default"; + this.description = "Drops every nth byte starting with a given byte."; + this.infoURL = ""; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Drop every", + type: "number", + value: 4 + }, + { + name: "Starting at", + type: "number", + value: 0 + }, + { + name: "Apply to each line", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const n = args[0]; + const start = args[1]; + const eachLine = args[2]; + + if (parseInt(n, 10) !== n || n <= 0) { + throw new OperationError("'Drop every' must be a positive integer."); + } + if (parseInt(start, 10) !== start || start < 0) { + throw new OperationError("'Starting at' must be a positive or zero integer."); + } + + let offset = 0; + const output = []; + for (let i = 0; i < input.length; i++) { + if (eachLine && input[i] === 0x0a) { + output.push(0x0a); + offset = i + 1; + } else if (i - offset < start || (i - (start + offset)) % n !== 0) { + output.push(input[i]); + } + } + + return output; + } + +} + +export default DropNthBytes; diff --git a/src/core/operations/ECDSASign.mjs b/src/core/operations/ECDSASign.mjs new file mode 100644 index 00000000..7b8f57f1 --- /dev/null +++ b/src/core/operations/ECDSASign.mjs @@ -0,0 +1,107 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import r from "jsrsasign"; + +/** + * ECDSA Sign operation + */ +class ECDSASign extends Operation { + + /** + * ECDSASign constructor + */ + constructor() { + super(); + + this.name = "ECDSA Sign"; + this.module = "Ciphers"; + this.description = "Sign a plaintext message with a PEM encoded EC key."; + this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "ECDSA Private Key (PEM)", + type: "text", + value: "-----BEGIN EC PRIVATE KEY-----" + }, + { + name: "Message Digest Algorithm", + type: "option", + value: [ + "SHA-256", + "SHA-384", + "SHA-512", + "SHA-1", + "MD5" + ] + }, + { + name: "Output Format", + type: "option", + value: [ + "ASN.1 HEX", + "P1363 HEX", + "JSON Web Signature", + "Raw JSON" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [keyPem, mdAlgo, outputFormat] = args; + + if (keyPem.replace("-----BEGIN EC PRIVATE KEY-----", "").length === 0) { + throw new OperationError("Please enter a private key."); + } + + const internalAlgorithmName = mdAlgo.replace("-", "") + "withECDSA"; + const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName }); + const key = r.KEYUTIL.getKey(keyPem); + if (key.type !== "EC") { + throw new OperationError("Provided key is not an EC key."); + } + if (!key.isPrivate) { + throw new OperationError("Provided key is not a private key."); + } + sig.init(key); + const signatureASN1Hex = sig.signString(input); + + let result; + switch (outputFormat) { + case "ASN.1 HEX": + result = signatureASN1Hex; + break; + case "P1363 HEX": + result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex); + break; + case "JSON Web Signature": + result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex); + result = toBase64(fromHex(result), "A-Za-z0-9-_"); // base64url + break; + case "Raw JSON": { + const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex); + result = JSON.stringify(signatureRS); + break; + } + } + + return result; + } +} + +export default ECDSASign; diff --git a/src/core/operations/ECDSASignatureConversion.mjs b/src/core/operations/ECDSASignatureConversion.mjs new file mode 100644 index 00000000..3f6c6bfb --- /dev/null +++ b/src/core/operations/ECDSASignatureConversion.mjs @@ -0,0 +1,146 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { fromBase64, toBase64 } from "../lib/Base64.mjs"; +import { fromHex, toHexFast } from "../lib/Hex.mjs"; +import r from "jsrsasign"; + +/** + * ECDSA Sign operation + */ +class ECDSASignatureConversion extends Operation { + + /** + * ECDSASignatureConversion constructor + */ + constructor() { + super(); + + this.name = "ECDSA Signature Conversion"; + this.module = "Ciphers"; + this.description = "Convert an ECDSA signature between hex, asn1 and json."; + this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input Format", + type: "option", + value: [ + "Auto", + "ASN.1 HEX", + "P1363 HEX", + "JSON Web Signature", + "Raw JSON" + ] + }, + { + name: "Output Format", + type: "option", + value: [ + "ASN.1 HEX", + "P1363 HEX", + "JSON Web Signature", + "Raw JSON" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let inputFormat = args[0]; + const outputFormat = args[1]; + + // detect input format + let inputJson; + if (inputFormat === "Auto") { + try { + inputJson = JSON.parse(input); + if (typeof(inputJson) === "object") { + inputFormat = "Raw JSON"; + } + } catch {} + } + + if (inputFormat === "Auto") { + const hexRegex = /^[a-f\d]{2,}$/gi; + if (hexRegex.test(input)) { + if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) { + inputFormat = "ASN.1 HEX"; + } else { + inputFormat = "P1363 HEX"; + } + } + } + + let inputBase64; + if (inputFormat === "Auto") { + try { + inputBase64 = fromBase64(input, "A-Za-z0-9-_", false); + inputFormat = "JSON Web Signature"; + } catch {} + } + + // convert input to ASN.1 hex + let signatureASN1Hex; + switch (inputFormat) { + case "Auto": + throw new OperationError("Signature format could not be detected"); + case "ASN.1 HEX": + signatureASN1Hex = input; + break; + case "P1363 HEX": + signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input); + break; + case "JSON Web Signature": + if (!inputBase64) inputBase64 = fromBase64(input, "A-Za-z0-9-_"); + signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64)); + break; + case "Raw JSON": { + if (!inputJson) inputJson = JSON.parse(input); + if (!inputJson.r) { + throw new OperationError('No "r" value in the signature JSON'); + } + if (!inputJson.s) { + throw new OperationError('No "s" value in the signature JSON'); + } + signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s); + break; + } + } + + // convert ASN.1 hex to output format + let result; + switch (outputFormat) { + case "ASN.1 HEX": + result = signatureASN1Hex; + break; + case "P1363 HEX": + result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex); + break; + case "JSON Web Signature": + result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex); + result = toBase64(fromHex(result), "A-Za-z0-9-_"); // base64url + break; + case "Raw JSON": { + const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex); + result = JSON.stringify(signatureRS); + break; + } + } + + return result; + } +} + +export default ECDSASignatureConversion; diff --git a/src/core/operations/ECDSAVerify.mjs b/src/core/operations/ECDSAVerify.mjs new file mode 100644 index 00000000..7e46e867 --- /dev/null +++ b/src/core/operations/ECDSAVerify.mjs @@ -0,0 +1,154 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { fromBase64 } from "../lib/Base64.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import r from "jsrsasign"; + +/** + * ECDSA Verify operation + */ +class ECDSAVerify extends Operation { + + /** + * ECDSAVerify constructor + */ + constructor() { + super(); + + this.name = "ECDSA Verify"; + this.module = "Ciphers"; + this.description = "Verify a message against a signature and a public PEM encoded EC key."; + this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input Format", + type: "option", + value: [ + "Auto", + "ASN.1 HEX", + "P1363 HEX", + "JSON Web Signature", + "Raw JSON" + ] + }, + { + name: "Message Digest Algorithm", + type: "option", + value: [ + "SHA-256", + "SHA-384", + "SHA-512", + "SHA-1", + "MD5" + ] + }, + { + name: "ECDSA Public Key (PEM)", + type: "text", + value: "-----BEGIN PUBLIC KEY-----" + }, + { + name: "Message", + type: "text", + value: "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let inputFormat = args[0]; + const [, mdAlgo, keyPem, msg] = args; + + if (keyPem.replace("-----BEGIN PUBLIC KEY-----", "").length === 0) { + throw new OperationError("Please enter a public key."); + } + + // detect input format + let inputJson; + if (inputFormat === "Auto") { + try { + inputJson = JSON.parse(input); + if (typeof(inputJson) === "object") { + inputFormat = "Raw JSON"; + } + } catch {} + } + + if (inputFormat === "Auto") { + const hexRegex = /^[a-f\d]{2,}$/gi; + if (hexRegex.test(input)) { + if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) { + inputFormat = "ASN.1 HEX"; + } else { + inputFormat = "P1363 HEX"; + } + } + } + + let inputBase64; + if (inputFormat === "Auto") { + try { + inputBase64 = fromBase64(input, "A-Za-z0-9-_", false); + inputFormat = "JSON Web Signature"; + } catch {} + } + + // convert to ASN.1 signature + let signatureASN1Hex; + switch (inputFormat) { + case "Auto": + throw new OperationError("Signature format could not be detected"); + case "ASN.1 HEX": + signatureASN1Hex = input; + break; + case "P1363 HEX": + signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input); + break; + case "JSON Web Signature": + if (!inputBase64) inputBase64 = fromBase64(input, "A-Za-z0-9-_"); + signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64)); + break; + case "Raw JSON": { + if (!inputJson) inputJson = JSON.parse(input); + if (!inputJson.r) { + throw new OperationError('No "r" value in the signature JSON'); + } + if (!inputJson.s) { + throw new OperationError('No "s" value in the signature JSON'); + } + signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s); + break; + } + } + + // verify signature + const internalAlgorithmName = mdAlgo.replace("-", "") + "withECDSA"; + const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName }); + const key = r.KEYUTIL.getKey(keyPem); + if (key.type !== "EC") { + throw new OperationError("Provided key is not an EC key."); + } + if (!key.isPublic) { + throw new OperationError("Provided key is not a public key."); + } + sig.init(key); + sig.updateString(msg); + const result = sig.verify(signatureASN1Hex); + return result ? "Verified OK" : "Verification Failure"; + } +} + +export default ECDSAVerify; diff --git a/src/core/operations/ELFInfo.mjs b/src/core/operations/ELFInfo.mjs new file mode 100644 index 00000000..815d3cf1 --- /dev/null +++ b/src/core/operations/ELFInfo.mjs @@ -0,0 +1,913 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Stream from "../lib/Stream.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * ELF Info operation + */ +class ELFInfo extends Operation { + + /** + * ELFInfo constructor + */ + constructor() { + super(); + + this.name = "ELF Info"; + this.module = "Default"; + this.description = "Implements readelf-like functionality. This operation will extract the ELF Header, Program Headers, Section Headers and Symbol Table for an ELF file."; + this.infoURL = "https://www.wikipedia.org/wiki/Executable_and_Linkable_Format"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let phoff = 0; + let phEntries = 0; + let shoff = 0; + let shEntries = 0; + let shentSize = 0; + let entry = 0; + let format = 0; + let endianness = ""; + let shstrtab = 0; + + let namesOffset = 0; + + let symtabOffset = 0; + let symtabSize = 0; + let symtabEntSize = 0; + + let strtabOffset = 0; + const align = 30; + + /** + * This function reads characters until it hits a null terminator. + * + * @param {stream} stream + * @param {integer} namesOffset + * @param {integer} nameOffset + * @returns {string} + */ + function readString(stream, namesOffset, nameOffset) { + const preMove = stream.position; + stream.moveTo(namesOffset + nameOffset); + + const nameResult = stream.readString(); + stream.moveTo(preMove); + return nameResult; + } + + /** + * This function parses and extracts relevant information from the ELF Header. + * + * @param {stream} stream + * @returns {string} + */ + function elfHeader(stream) { + /** + * The ELF Header is comprised of the following structures depending on the binary's format. + * + * e_ident - The Magic Number 0x7F,0x45,0x4c,0x46 + * - Byte set to 1 or 2 to signify 32-bit or 64-bit format, respectively. + * - Byte set to 1 or 2 to signify little of big endianness, respectively. + * - Byte set to 1 for the version of ELF. + * - Byte identifying the target OS ABI. + * - Byte further identifying the OS ABI Version. + * - 7 Padding Bytes. + * e_type - 2 bytes identifying the object file type. + * e_machine - 2 bytes identifying the instruction set architecture. + * e_version - Byte set to 1 for the version of ELF. + * + * 32-bit: + * e_entry - 4 Bytes specifying the entry point. + * e_phoff - 4 Bytes specifying the offset of the Program Header Table. + * e_shoff - 4 Bytes specifying the offset of the Section Header Table. + * + * 64-bit: + * e_entry - 8 Bytes specifying the entry point. + * e_phoff - 8 Bytes specifying the offset of the Program Header Table. + * e_shoff - 8 Bytes specifying the offset of the Section Header Table. + * + * e_flags - 4 Bytes specifying processor specific flags. + * e_ehsize - 2 Bytes specifying the size of the ELF Header. + * e_phentsize - 2 Bytes specifying the size of a Program Header Table Entry. + * e_phnum - 2 Bytes specifying the number of entries in the Program Header Table. + * e_shentsize - 2 Bytes specifying the size of a Section Header Table Entry. + * e_shnum - 2 Bytes specifying the number of entries in the Section Header Table. + * e_shstrndx - 2 Bytes specifying the index of the section containing the section names in the Section Header Table. + */ + const ehResult = []; + + const magic = stream.getBytes(4); + if (magic.join("") !== [0x7f, 0x45, 0x4c, 0x46].join("")) + throw new OperationError("Invalid ELF"); + + ehResult.push("Magic:".padEnd(align) + `${Utils.byteArrayToChars(magic)}`); + + format = stream.readInt(1); + ehResult.push("Format:".padEnd(align) + `${format === 1 ? "32-bit" : "64-bit"}`); + + endianness = stream.readInt(1) === 1 ? "le" : "be"; + ehResult.push("Endianness:".padEnd(align) + `${endianness === "le" ? "Little" : "Big"}`); + + ehResult.push("Version:".padEnd(align) + `${stream.readInt(1).toString()}`); + + let ABI = ""; + switch (stream.readInt(1)) { + case 0x00: + ABI = "System V"; + break; + case 0x01: + ABI = "HP-UX"; + break; + case 0x02: + ABI = "NetBSD"; + break; + case 0x03: + ABI = "Linux"; + break; + case 0x04: + ABI = "GNU Hurd"; + break; + case 0x06: + ABI = "Solaris"; + break; + case 0x07: + ABI = "AIX"; + break; + case 0x08: + ABI = "IRIX"; + break; + case 0x09: + ABI = "FreeBSD"; + break; + case 0x0A: + ABI = "Tru64"; + break; + case 0x0B: + ABI = "Novell Modesto"; + break; + case 0x0C: + ABI = "OpenBSD"; + break; + case 0x0D: + ABI = "OpenVMS"; + break; + case 0x0E: + ABI = "NonStop Kernel"; + break; + case 0x0F: + ABI = "AROS"; + break; + case 0x10: + ABI = "Fenix OS"; + break; + case 0x11: + ABI = "CloudABI"; + break; + case 0x12: + ABI = "Stratus Technologies OpenVOS"; + break; + default: + break; + } + ehResult.push("ABI:".padEnd(align) + ABI); + + // Linux Kernel does not use ABI Version. + const abiVersion = stream.readInt(1).toString(); + if (ABI !== "Linux") + ehResult.push("ABI Version:".padEnd(align) + abiVersion); + + stream.moveForwardsBy(7); + + let eType = ""; + switch (stream.readInt(2, endianness)) { + case 0x0000: + eType = "Unknown"; + break; + case 0x0001: + eType = "Relocatable File"; + break; + case 0x0002: + eType = "Executable File"; + break; + case 0x0003: + eType = "Shared Object"; + break; + case 0x0004: + eType = "Core File"; + break; + case 0xFE00: + eType = "LOOS"; + break; + case 0xFEFF: + eType = "HIOS"; + break; + case 0xFF00: + eType = "LOPROC"; + break; + case 0xFFFF: + eType = "HIPROC"; + break; + default: + break; + } + ehResult.push("Type:".padEnd(align) + eType); + + let ISA = ""; + switch (stream.readInt(2, endianness)) { + case 0x0000: + ISA = "No specific instruction set"; + break; + case 0x0001: + ISA = "AT&T WE 32100"; + break; + case 0x0002: + ISA = "SPARC"; + break; + case 0x0003: + ISA = "x86"; + break; + case 0x0004: + ISA = "Motorola 68000 (M68k)"; + break; + case 0x0005: + ISA = "Motorola 88000 (M88k)"; + break; + case 0x0006: + ISA = "Intel MCU"; + break; + case 0x0007: + ISA = "Intel 80860"; + break; + case 0x0008: + ISA = "MIPS"; + break; + case 0x0009: + ISA = "IBM System/370"; + break; + case 0x000A: + ISA = "MIPS RS3000 Little-endian"; + break; + case 0x000B: + case 0x000C: + case 0x000D: + case 0x000E: + case 0x0018: + case 0x0019: + case 0x001A: + case 0x001B: + case 0x001C: + case 0x001D: + case 0x001E: + case 0x001F: + case 0x0020: + case 0x0021: + case 0x0022: + case 0x0023: + ISA = "Reserved for future use"; + break; + case 0x000F: + ISA = "Hewlett-Packard PA-RISC"; + break; + case 0x0011: + ISA = "Fujitsu VPP500"; + break; + case 0x0012: + ISA = "Enhanced instruction set SPARC"; + break; + case 0x0013: + ISA = "Intel 80960"; + break; + case 0x0014: + ISA = "PowerPC"; + break; + case 0x0015: + ISA = "PowerPC (64-bit)"; + break; + case 0x0016: + ISA = "S390, including S390"; + break; + case 0x0017: + ISA = "IBM SPU/SPC"; + break; + case 0x0024: + ISA = "NEC V800"; + break; + case 0x0025: + ISA = "Fujitsu FR20"; + break; + case 0x0026: + ISA = "TRW RH-32"; + break; + case 0x0027: + ISA = "Motorola RCE"; + break; + case 0x0028: + ISA = "ARM (up to ARMv7/Aarch32)"; + break; + case 0x0029: + ISA = "Digital Alpha"; + break; + case 0x002A: + ISA = "SuperH"; + break; + case 0x002B: + ISA = "SPARC Version 9"; + break; + case 0x002C: + ISA = "Siemens TriCore embedded processor"; + break; + case 0x002D: + ISA = "Argonaut RISC Core"; + break; + case 0x002E: + ISA = "Hitachi H8/300"; + break; + case 0x002F: + ISA = "Hitachi H8/300H"; + break; + case 0x0030: + ISA = "Hitachi H8S"; + break; + case 0x0031: + ISA = "Hitachi H8/500"; + break; + case 0x0032: + ISA = "IA-64"; + break; + case 0x0033: + ISA = "Standford MIPS-X"; + break; + case 0x0034: + ISA = "Motorola ColdFire"; + break; + case 0x0035: + ISA = "Motorola M68HC12"; + break; + case 0x0036: + ISA = "Fujitsu MMA Multimedia Accelerator"; + break; + case 0x0037: + ISA = "Siemens PCP"; + break; + case 0x0038: + ISA = "Sony nCPU embedded RISC processor"; + break; + case 0x0039: + ISA = "Denso NDR1 microprocessor"; + break; + case 0x003A: + ISA = "Motorola Star*Core processor"; + break; + case 0x003B: + ISA = "Toyota ME16 processor"; + break; + case 0x003C: + ISA = "STMicroelectronics ST100 processor"; + break; + case 0x003D: + ISA = "Advanced Logic Corp. TinyJ embedded processor family"; + break; + case 0x003E: + ISA = "AMD x86-64"; + break; + case 0x003F: + ISA = "Sony DSP Processor"; + break; + case 0x0040: + ISA = "Digital Equipment Corp. PDP-10"; + break; + case 0x0041: + ISA = "Digital Equipment Corp. PDP-11"; + break; + case 0x0042: + ISA = "Siemens FX66 microcontroller"; + break; + case 0x0043: + ISA = "STMicroelectronics ST9+ 8/16 bit microcontroller"; + break; + case 0x0044: + ISA = "STMicroelectronics ST7 8-bit microcontroller"; + break; + case 0x0045: + ISA = "Motorola MC68HC16 Microcontroller"; + break; + case 0x0046: + ISA = "Motorola MC68HC11 Microcontroller"; + break; + case 0x0047: + ISA = "Motorola MC68HC08 Microcontroller"; + break; + case 0x0048: + ISA = "Motorola MC68HC05 Microcontroller"; + break; + case 0x0049: + ISA = "Silicon Graphics SVx"; + break; + case 0x004A: + ISA = "STMicroelectronics ST19 8-bit microcontroller"; + break; + case 0x004B: + ISA = "Digital VAX"; + break; + case 0x004C: + ISA = "Axis Communications 32-bit embedded processor"; + break; + case 0x004D: + ISA = "Infineon Technologies 32-bit embedded processor"; + break; + case 0x004E: + ISA = "Element 14 64-bit DSP Processor"; + break; + case 0x004F: + ISA = "LSI Logic 16-bit DSP Processor"; + break; + case 0x0050: + ISA = "Donald Knuth's educational 64-bit processor"; + break; + case 0x0051: + ISA = "Harvard University machine-independent object files"; + break; + case 0x0052: + ISA = "SiTera Prism"; + break; + case 0x0053: + ISA = "Atmel AVR 8-bit microcontroller"; + break; + case 0x0054: + ISA = "Fujitsu FR30"; + break; + case 0x0055: + ISA = "Mitsubishi D10V"; + break; + case 0x0056: + ISA = "Mitsubishi D30V"; + break; + case 0x0057: + ISA = "NEC v850"; + break; + case 0x0058: + ISA = "Mitsubishi M32R"; + break; + case 0x0059: + ISA = "Matsushita MN10300"; + break; + case 0x005A: + ISA = "Matsushita MN10200"; + break; + case 0x005B: + ISA = "picoJava"; + break; + case 0x005C: + ISA = "OpenRISC 32-bit embedded processor"; + break; + case 0x005D: + ISA = "ARC Cores Tangent-A5"; + break; + case 0x005E: + ISA = "Tensilica Xtensa Architecture"; + break; + case 0x005F: + ISA = "Alphamosaic VideoCore processor"; + break; + case 0x0060: + ISA = "Thompson Multimedia General Purpose Processor"; + break; + case 0x0061: + ISA = "National Semiconductor 32000 series"; + break; + case 0x0062: + ISA = "Tenor Network TPC processor"; + break; + case 0x0063: + ISA = "Trebia SNP 1000 processor"; + break; + case 0x0064: + ISA = "STMicroelectronics (www.st.com) ST200 microcontroller"; + break; + case 0x008C: + ISA = "TMS320C6000 Family"; + break; + case 0x00AF: + ISA = "MCST Elbrus e2k"; + break; + case 0x00B7: + ISA = "ARM 64-bits (ARMv8/Aarch64)"; + break; + case 0x00F3: + ISA = "RISC-V"; + break; + case 0x00F7: + ISA = "Berkeley Packet Filter"; + break; + case 0x0101: + ISA = "WDC 65C816"; + break; + default: + ISA = "Unimplemented"; + break; + } + ehResult.push("Instruction Set Architecture:".padEnd(align) + ISA); + + ehResult.push("ELF Version:".padEnd(align) + `${stream.readInt(4, endianness)}`); + + const readSize = format === 1 ? 4 : 8; + entry = stream.readInt(readSize, endianness); + phoff = stream.readInt(readSize, endianness); + shoff = stream.readInt(readSize, endianness); + ehResult.push("Entry Point:".padEnd(align) + `0x${Utils.hex(entry)}`); + ehResult.push("Entry PHOFF:".padEnd(align) + `0x${Utils.hex(phoff)}`); + ehResult.push("Entry SHOFF:".padEnd(align) + `0x${Utils.hex(shoff)}`); + + const flags = stream.readInt(4, endianness); + ehResult.push("Flags:".padEnd(align) + `${Utils.bin(flags)}`); + + ehResult.push("ELF Header Size:".padEnd(align) + `${stream.readInt(2, endianness)} bytes`); + ehResult.push("Program Header Size:".padEnd(align) + `${stream.readInt(2, endianness)} bytes`); + phEntries = stream.readInt(2, endianness); + ehResult.push("Program Header Entries:".padEnd(align) + phEntries); + shentSize = stream.readInt(2, endianness); + ehResult.push("Section Header Size:".padEnd(align) + shentSize + " bytes"); + shEntries = stream.readInt(2, endianness); + ehResult.push("Section Header Entries:".padEnd(align) + shEntries); + shstrtab = stream.readInt(2, endianness); + ehResult.push("Section Header Names:".padEnd(align) + shstrtab); + + return ehResult.join("\n"); + } + + /** + * This function parses and extracts relevant information from a Program Header. + * + * @param {stream} stream + * @returns {string} + */ + function programHeader(stream) { + /** + * A Program Header is comprised of the following structures depending on the binary's format. + * + * p_type - 4 Bytes identifying the type of the segment. + * + * 32-bit: + * p_offset - 4 Bytes specifying the offset of the segment. + * p_vaddr - 4 Bytes specifying the virtual address of the segment in memory. + * p_paddr - 4 Bytes specifying the physical address of the segment in memory. + * p_filesz - 4 Bytes specifying the size in bytes of the segment in the file image. + * p_memsz - 4 Bytes specifying the size in bytes of the segment in memory. + * p_flags - 4 Bytes identifying the segment dependent flags. + * p_align - 4 Bytes set to 0 or 1 for alignment or no alignment, respectively. + * + * 64-bit: + * p_flags - 4 Bytes identifying segment dependent flags. + * p_offset - 8 Bytes specifying the offset of the segment. + * p_vaddr - 8 Bytes specifying the virtual address of the segment in memory. + * p_paddr - 8 Bytes specifying the physical address of the segment in memory. + * p_filesz - 8 Bytes specifying the size in bytes of the segment in the file image. + * p_memsz - 8 Bytes specifying the size in bytes of the segment in memory. + * p_align - 8 Bytes set to 0 or 1 for alignment or no alignment, respectively. + */ + + /** + * This function decodes the flags bitmask for the Program Header. + * + * @param {integer} flags + * @returns {string} + */ + function readFlags(flags) { + const result = []; + if (flags & 0x1) + result.push("Execute"); + if (flags & 0x2) + result.push("Write"); + if (flags & 0x4) + result.push("Read"); + if (flags & 0xf0000000) + result.push("Unspecified"); + return result.join(","); + } + + const phResult = []; + + let pType = ""; + const programHeaderType = stream.readInt(4, endianness); + switch (true) { + case (programHeaderType === 0x00000000): + pType = "Unused"; + break; + case (programHeaderType === 0x00000001): + pType = "Loadable Segment"; + break; + case (programHeaderType === 0x00000002): + pType = "Dynamic linking information"; + break; + case (programHeaderType === 0x00000003): + pType = "Interpreter Information"; + break; + case (programHeaderType === 0x00000004): + pType = "Auxiliary Information"; + break; + case (programHeaderType === 0x00000005): + pType = "Reserved"; + break; + case (programHeaderType === 0x00000006): + pType = "Program Header Table"; + break; + case (programHeaderType === 0x00000007): + pType = "Thread-Local Storage Template"; + break; + case (programHeaderType >= 0x60000000 && programHeaderType <= 0x6FFFFFFF): + pType = "Reserved Inclusive Range. OS Specific"; + break; + case (programHeaderType >= 0x70000000 && programHeaderType <= 0x7FFFFFFF): + pType = "Reserved Inclusive Range. Processor Specific"; + break; + default: + break; + + } + phResult.push("Program Header Type:".padEnd(align) + pType); + + if (format === 2) + phResult.push("Flags:".padEnd(align) + readFlags(stream.readInt(4, endianness))); + + const readSize = format === 1? 4 : 8; + phResult.push("Offset Of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`); + phResult.push("Virtual Address of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`); + phResult.push("Physical Address of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`); + phResult.push("Size of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)} bytes`); + phResult.push("Size of Segment in Memory:".padEnd(align) + `${stream.readInt(readSize, endianness)} bytes`); + + if (format === 1) + phResult.push("Flags:".padEnd(align) + readFlags(stream.readInt(4, endianness))); + + stream.moveForwardsBy(readSize); + + return phResult.join("\n"); + } + + /** + * This function parses and extracts relevant information from a Section Header. + * + * @param {stream} stream + * @returns {string} + */ + function sectionHeader(stream) { + /** + * A Section Header is comprised of the following structures depending on the binary's format. + * + * sh_name - 4 Bytes identifying the offset into the .shstrtab for the name of this section. + * sh_type - 4 Bytes identifying the type of this header. + * + * 32-bit: + * sh_flags - 4 Bytes identifying section specific flags. + * sh_addr - 4 Bytes identifying the virtual address of the section in memory. + * sh_offset - 4 Bytes identifying the offset of the section in the file. + * sh_size - 4 Bytes specifying the size in bytes of the section in the file image. + * sh_link - 4 Bytes identifying the index of an associated section. + * sh_info - 4 Bytes specifying extra information about the section. + * sh_addralign - 4 Bytes containing the alignment for the section. + * sh_entsize - 4 Bytes specifying the size, in bytes, of each entry in the section. + * + * 64-bit: + * sh_flags - 8 Bytes identifying section specific flags. + * sh_addr - 8 Bytes identifying the virtual address of the section in memory. + * sh_offset - 8 Bytes identifying the offset of the section in the file. + * sh_size - 8 Bytes specifying the size in bytes of the section in the file image. + * sh_link - 4 Bytes identifying the index of an associated section. + * sh_info - 4 Bytes specifying extra information about the section. + * sh_addralign - 8 Bytes containing the alignment for the section. + * sh_entsize - 8 Bytes specifying the size, in bytes, of each entry in the section. + */ + const shResult = []; + + const nameOffset = stream.readInt(4, endianness); + let type = ""; + const shType = stream.readInt(4, endianness); + switch (true) { + case (shType === 0x00000001): + type = "Program Data"; + break; + case (shType === 0x00000002): + type = "Symbol Table"; + break; + case (shType === 0x00000003): + type = "String Table"; + break; + case (shType === 0x00000004): + type = "Relocation Entries with Addens"; + break; + case (shType === 0x00000005): + type = "Symbol Hash Table"; + break; + case (shType === 0x00000006): + type = "Dynamic Linking Information"; + break; + case (shType === 0x00000007): + type = "Notes"; + break; + case (shType === 0x00000008): + type = "Program Space with No Data"; + break; + case (shType === 0x00000009): + type = "Relocation Entries with no Addens"; + break; + case (shType === 0x0000000A): + type = "Reserved"; + break; + case (shType === 0x0000000B): + type = "Dynamic Linker Symbol Table"; + break; + case (shType === 0x0000000E): + type = "Array of Constructors"; + break; + case (shType === 0x0000000F): + type = "Array of Destructors"; + break; + case (shType === 0x00000010): + type = "Array of pre-constructors"; + break; + case (shType === 0x00000011): + type = "Section group"; + break; + case (shType === 0x00000012): + type = "Extended section indices"; + break; + case (shType === 0x00000013): + type = "Number of defined types"; + break; + case (shType >= 0x60000000 && shType <= 0x6fffffff): + type = "OS-specific"; + break; + case (shType >= 0x70000000 && shType <= 0x7fffffff): + type = "Processor-specific"; + break; + case (shType >= 0x80000000 && shType <= 0x8fffffff): + type = "Application-specific"; + break; + default: + type = "Unused"; + break; + } + + shResult.push("Type:".padEnd(align) + type); + + let nameResult = ""; + if (type !== "Unused") { + nameResult = readString(stream, namesOffset, nameOffset); + shResult.push("Section Name: ".padEnd(align) + nameResult); + } + + const readSize = (format === 1) ? 4 : 8; + + const flags = stream.readInt(readSize, endianness); + const shFlags = []; + const bitMasks = [ + [0x00000001, "Writable"], + [0x00000002, "Alloc"], + [0x00000004, "Executable"], + [0x00000010, "Merge"], + [0x00000020, "Strings"], + [0x00000040, "SHT Info Link"], + [0x00000080, "Link Order"], + [0x00000100, "OS Specific Handling"], + [0x00000200, "Group"], + [0x00000400, "Thread Local Data"], + [0x0FF00000, "OS-Specific"], + [0xF0000000, "Processor Specific"], + [0x04000000, "Special Ordering (Solaris)"], + [0x08000000, "Excluded (Solaris)"] + ]; + bitMasks.forEach(elem => { + if (flags & elem[0]) + shFlags.push(elem[1]); + }); + shResult.push("Flags:".padEnd(align) + shFlags); + + const vaddr = stream.readInt(readSize, endianness); + shResult.push("Section Vaddr in memory:".padEnd(align) + vaddr); + + const shoffset = stream.readInt(readSize, endianness); + shResult.push("Offset of the section:".padEnd(align) + shoffset); + + const secSize = stream.readInt(readSize, endianness); + shResult.push("Section Size:".padEnd(align) + secSize); + + const associatedSection = stream.readInt(4, endianness); + shResult.push("Associated Section:".padEnd(align) + associatedSection); + + const extraInfo = stream.readInt(4, endianness); + shResult.push("Section Extra Information:".padEnd(align) + extraInfo); + + // Jump over alignment field. + stream.moveForwardsBy(readSize); + const entSize = stream.readInt(readSize, endianness); + switch (nameResult) { + case ".strtab": + strtabOffset = shoffset; + break; + case ".symtab": + symtabOffset = shoffset; + symtabSize = secSize; + symtabEntSize = entSize; + break; + default: + break; + } + return shResult.join("\n"); + } + + /** + * This function returns the offset of the Section Header Names Section. + * + * @param {stream} stream + */ + function getNamesOffset(stream) { + const preMove = stream.position; + stream.moveTo(shoff + (shentSize * shstrtab)); + if (format === 1) { + stream.moveForwardsBy(0x10); + namesOffset = stream.readInt(4, endianness); + } else { + stream.moveForwardsBy(0x18); + namesOffset = stream.readInt(8, endianness); + } + stream.position = preMove; + } + + /** + * This function returns a symbol's name from the string table. + * + * @param {stream} stream + * @returns {string} + */ + function getSymbols(stream) { + /** + * The Symbol Table is comprised of Symbol Table Entries whose structure depends on the binary's format. + * + * 32-bit: + * st_name - 4 Bytes specifying an index in the files symbol string table. + * st_value - 4 Bytes identifying the value associated with the symbol. + * st_size - 4 Bytes specifying the size associated with the symbol (this is not the size of the symbol). + * st_info - A byte specifying the type and binding of the symbol. + * st_other - A byte specifying the symbol's visibility. + * st_shndx - 2 Bytes identifying the section that this symbol is related to. + * + * 64-bit: + * st_name - 4 Bytes specifying an index in the files symbol string table. + * st_info - A byte specifying the type and binding of the symbol. + * st_other - A byte specifying the symbol's visibility. + * st_shndx - 2 Bytes identifying the section that this symbol is related to. + * st_value - 8 Bytes identifying the value associated with the symbol. + * st_size - 8 Bytes specifying the size associated with the symbol (this is not the size of the symbol). + */ + const nameOffset = stream.readInt(4, endianness); + stream.moveForwardsBy(format === 2 ? 20 : 12); + return readString(stream, strtabOffset, nameOffset); + } + + input = new Uint8Array(input); + const stream = new Stream(input); + const result = ["=".repeat(align) + " ELF Header " + "=".repeat(align)]; + result.push(elfHeader(stream) + "\n"); + + getNamesOffset(stream); + + result.push("=".repeat(align) + " Program Header " + "=".repeat(align)); + stream.moveTo(phoff); + for (let i = 0; i < phEntries; i++) + result.push(programHeader(stream) + "\n"); + + result.push("=".repeat(align) + " Section Header " + "=".repeat(align)); + stream.moveTo(shoff); + for (let i = 0; i < shEntries; i++) + result.push(sectionHeader(stream) + "\n"); + + result.push("=".repeat(align) + " Symbol Table " + "=".repeat(align)); + + stream.moveTo(symtabOffset); + let elem = ""; + for (let i = 0; i < (symtabSize / symtabEntSize); i++) + if ((elem = getSymbols(stream)) !== "") + result.push("Symbol Name:".padEnd(align) + elem); + + return result.join("\n"); + } + +} + +export default ELFInfo; diff --git a/src/core/operations/EncodeNetBIOSName.mjs b/src/core/operations/EncodeNetBIOSName.mjs new file mode 100644 index 00000000..bcc9a11d --- /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.mjs"; + +/** + * 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..8cc1450f --- /dev/null +++ b/src/core/operations/EncodeText.mjs @@ -0,0 +1,58 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import cptable from "codepage"; +import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs"; + +/** + * 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(CHR_ENC_CODE_PAGES).map(e => `
  • ${e}
  • `).join("\n"), + "
", + ].join("\n"); + this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; + this.inputType = "string"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Encoding", + "type": "option", + "value": Object.keys(CHR_ENC_CODE_PAGES) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const format = CHR_ENC_CODE_PAGES[args[0]]; + const encoded = cptable.utils.encode(format, input); + return new Uint8Array(encoded).buffer; + } + +} + + +export default EncodeText; diff --git a/src/core/operations/Endian.js b/src/core/operations/Endian.js deleted file mode 100755 index 219ef347..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) { - var 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) { - var 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/Enigma.mjs b/src/core/operations/Enigma.mjs new file mode 100644 index 00000000..c0a75841 --- /dev/null +++ b/src/core/operations/Enigma.mjs @@ -0,0 +1,217 @@ +/** + * Emulation of the Enigma machine. + * + * Tested against various genuine Enigma machines using a variety of inputs + * and settings to confirm correctness. + * + * @author s2224834 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ROTORS, LETTERS, ROTORS_FOURTH, REFLECTORS, Rotor, Reflector, Plugboard, EnigmaMachine} from "../lib/Enigma.mjs"; + +/** + * Enigma operation + */ +class Enigma extends Operation { + /** + * Enigma constructor + */ + constructor() { + super(); + + this.name = "Enigma"; + this.module = "Bletchley"; + this.description = "Encipher/decipher with the WW2 Enigma machine.

Enigma was used by the German military, among others, around the WW2 era as a portable cipher machine to protect sensitive military, diplomatic and commercial communications.

The standard set of German military rotors and reflectors are provided. To configure the plugboard, enter a string of connected pairs of letters, e.g. AB CD EF connects A to B, C to D, and E to F. This is also used to create your own reflectors. To create your own rotor, enter the letters that the rotor maps A to Z to, in order, optionally followed by < then a list of stepping points.
This is deliberately fairly permissive with rotor placements etc compared to a real Enigma (on which, for example, a four-rotor Enigma uses only the thin reflectors and the beta or gamma rotor in the 4th slot).

More detailed descriptions of the Enigma, Typex and Bombe operations can be found here."; + this.infoURL = "https://wikipedia.org/wiki/Enigma_machine"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Model", + type: "argSelector", + value: [ + { + name: "3-rotor", + off: [1, 2, 3] + }, + { + name: "4-rotor", + on: [1, 2, 3] + } + ] + }, + { + name: "Left-most (4th) rotor", + type: "editableOption", + value: ROTORS_FOURTH, + defaultIndex: 0 + }, + { + name: "Left-most rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "Left-most rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "Left-hand rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 0 + }, + { + name: "Left-hand rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "Left-hand rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "Middle rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 1 + }, + { + name: "Middle rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "Middle rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "Right-hand rotor", + type: "editableOption", + value: ROTORS, + // Default config is the rotors I-III *left to right* + defaultIndex: 2 + }, + { + name: "Right-hand rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "Right-hand rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "Reflector", + type: "editableOption", + value: REFLECTORS + }, + { + name: "Plugboard", + type: "string", + value: "" + }, + { + name: "Strict output", + hint: "Remove non-alphabet letters and group output", + type: "boolean", + value: true + }, + ]; + } + + /** + * Helper - for ease of use rotors are specified as a single string; this + * method breaks the spec string into wiring and steps parts. + * + * @param {string} rotor - Rotor specification string. + * @param {number} i - For error messages, the number of this rotor. + * @returns {string[]} + */ + parseRotorStr(rotor, i) { + if (rotor === "") { + throw new OperationError(`Rotor ${i} must be provided.`); + } + if (!rotor.includes("<")) { + return [rotor, ""]; + } + return rotor.split("<", 2); + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const model = args[0]; + const reflectorstr = args[13]; + const plugboardstr = args[14]; + const removeOther = args[15]; + const rotors = []; + for (let i=0; i<4; i++) { + if (i === 0 && model === "3-rotor") { + // Skip the 4th rotor settings + continue; + } + const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*3 + 1], 1); + rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*3 + 2], args[i*3 + 3])); + } + // Rotors are handled in reverse + rotors.reverse(); + const reflector = new Reflector(reflectorstr); + const plugboard = new Plugboard(plugboardstr); + if (removeOther) { + input = input.replace(/[^A-Za-z]/g, ""); + } + const enigma = new EnigmaMachine(rotors, reflector, plugboard); + let result = enigma.crypt(input); + if (removeOther) { + // Five character cipher groups is traditional + result = result.replace(/([A-Z]{5})(?!$)/g, "$1 "); + } + return result; + } + + /** + * Highlight Enigma + * This is only possible if we're passing through non-alphabet characters. + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + if (args[13] === false) { + return pos; + } + } + + /** + * Highlight Enigma in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + if (args[13] === false) { + return pos; + } + } + +} + +export default Enigma; diff --git a/src/core/operations/Entropy.js b/src/core/operations/Entropy.js deleted file mode 100755 index e3237c25..00000000 --- a/src/core/operations/Entropy.js +++ /dev/null @@ -1,171 +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) { - var 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" + - "
"; - - var chunkEntropy = 0; - if (chunkSize !== 0) { - for (var 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"; - - var distrib = new Array(256), - percentages = new Array(256), - len = input.length, - showZeroes = args[0]; - - // Initialise distrib to 0 - for (var i = 0; i < 256; i++) { - distrib[i] = 0; - } - - // Count bytes - for (i = 0; i < len; i++) { - distrib[input[i]]++; - } - - // Calculate percentages - var repr = 0; - for (i = 0; i < 256; i++) { - if (distrib[i] > 0) repr++; - percentages[i] = distrib[i] / len * 100; - } - - // Print - var 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) { - var prob = [], - uniques = data.unique(), - str = Utils.byteArrayToChars(data); - - for (var i = 0; i < uniques.length; i++) { - prob.push(str.count(Utils.chr(uniques[i])) / data.length); - } - - var 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..296d5ee8 --- /dev/null +++ b/src/core/operations/Entropy.mjs @@ -0,0 +1,430 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import * as d3temp from "d3"; +import * as nodomtemp from "nodom"; + +import Operation from "../Operation.mjs"; + +const d3 = d3temp.default ? d3temp.default : d3temp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + +/** + * Entropy operation + */ +class Entropy extends Operation { + + /** + * Entropy constructor + */ + constructor() { + super(); + + this.name = "Entropy"; + this.module = "Charts"; + 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 = "ArrayBuffer"; + this.outputType = "json"; + this.presentType = "html"; + this.args = [ + { + "name": "Visualisation", + "type": "option", + "value": ["Shannon scale", "Histogram (Bar)", "Histogram (Line)", "Curve", "Image"] + } + ]; + } + + /** + * Calculates the frequency of bytes in the input. + * + * @param {Uint8Array} input + * @returns {number} + */ + calculateShannonEntropy(input) { + const prob = [], + occurrences = new Array(256).fill(0); + + // Count occurrences of each byte in the input + let i; + for (i = 0; i < input.length; i++) { + occurrences[input[i]]++; + } + + // Store probability list + for (i = 0; i < occurrences.length; i++) { + if (occurrences[i] > 0) { + prob.push(occurrences[i] / input.length); + } + } + + // Calculate Shannon entropy + let entropy = 0, + p; + + for (i = 0; i < prob.length; i++) { + p = prob[i]; + entropy += p * Math.log(p) / Math.log(2); + } + + return -entropy; + } + + /** + * Calculates the scanning entropy of the input + * + * @param {Uint8Array} inputBytes + * @returns {Object} + */ + calculateScanningEntropy(inputBytes) { + const entropyData = []; + const binWidth = inputBytes.length < 256 ? 8 : 256; + + for (let bytePos = 0; bytePos < inputBytes.length; bytePos += binWidth) { + const block = inputBytes.slice(bytePos, bytePos+binWidth); + entropyData.push(this.calculateShannonEntropy(block)); + } + + return { entropyData, binWidth }; + } + + /** + * Calculates the frequency of bytes in the input. + * + * @param {object} svg + * @param {function} xScale + * @param {function} yScale + * @param {integer} svgHeight + * @param {integer} svgWidth + * @param {object} margins + * @param {string} xTitle + * @param {string} yTitle + */ + createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, title, xTitle, yTitle) { + // Axes + const yAxis = d3.axisLeft() + .scale(yScale); + + const xAxis = d3.axisBottom() + .scale(xScale); + + svg.append("g") + .attr("transform", `translate(0, ${svgHeight - margins.bottom})`) + .call(xAxis); + + svg.append("g") + .attr("transform", `translate(${margins.left},0)`) + .call(yAxis); + + // Axes labels + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", 0 - margins.left) + .attr("x", 0 - (svgHeight / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yTitle); + + svg.append("text") + .attr("transform", `translate(${svgWidth / 2}, ${svgHeight - margins.bottom + 40})`) + .style("text-anchor", "middle") + .text(xTitle); + + // Add title + svg.append("text") + .attr("transform", `translate(${svgWidth / 2}, ${margins.top - 10})`) + .style("text-anchor", "middle") + .text(title); + } + + /** + * Calculates the frequency of bytes in the input. + * + * @param {Uint8Array} inputBytes + * @returns {number[]} + */ + calculateByteFrequency(inputBytes) { + const freq = new Array(256).fill(0); + if (inputBytes.length === 0) return freq; + + // Count occurrences of each byte in the input + let i; + for (i = 0; i < inputBytes.length; i++) { + freq[inputBytes[i]]++; + } + + for (i = 0; i < freq.length; i++) { + freq[i] = freq[i] / inputBytes.length; + } + + return freq; + } + + /** + * Calculates the frequency of bytes in the input. + * + * @param {number[]} byteFrequency + * @returns {HTML} + */ + createByteFrequencyLineHistogram(byteFrequency) { + const margins = { top: 30, right: 20, bottom: 50, left: 30 }; + + const svgWidth = 500, + svgHeight = 500; + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); + + const yScale = d3.scaleLinear() + .domain([0, d3.max(byteFrequency, d => d)]) + .range([svgHeight - margins.bottom, margins.top]); + + const xScale = d3.scaleLinear() + .domain([0, byteFrequency.length - 1]) + .range([margins.left, svgWidth - margins.right]); + + const line = d3.line() + .x((_, i) => xScale(i)) + .y(d => yScale(d)) + .curve(d3.curveMonotoneX); + + svg.append("path") + .datum(byteFrequency) + .attr("fill", "none") + .attr("stroke", "steelblue") + .attr("d", line); + + this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, "", "Byte", "Byte Frequency"); + + return svg._groups[0][0].outerHTML; + } + + /** + * Creates a byte frequency histogram + * + * @param {number[]} byteFrequency + * @returns {HTML} + */ + createByteFrequencyBarHistogram(byteFrequency) { + const margins = { top: 30, right: 20, bottom: 50, left: 30 }; + + const svgWidth = 500, + svgHeight = 500, + binWidth = 1; + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); + + const yExtent = d3.extent(byteFrequency, d => d); + const yScale = d3.scaleLinear() + .domain(yExtent) + .range([svgHeight - margins.bottom, margins.top]); + + const xScale = d3.scaleLinear() + .domain([0, byteFrequency.length - 1]) + .range([margins.left - binWidth, svgWidth - margins.right]); + + svg.selectAll("rect") + .data(byteFrequency) + .enter().append("rect") + .attr("x", (_, i) => xScale(i) + binWidth) + .attr("y", dataPoint => yScale(dataPoint)) + .attr("width", binWidth) + .attr("height", dataPoint => yScale(yExtent[0]) - yScale(dataPoint)) + .attr("fill", "blue"); + + this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, "", "Byte", "Byte Frequency"); + + return svg._groups[0][0].outerHTML; + } + + /** + * Creates a byte frequency histogram + * + * @param {number[]} entropyData + * @returns {HTML} + */ + createEntropyCurve(entropyData) { + const margins = { top: 30, right: 20, bottom: 50, left: 30 }; + + const svgWidth = 500, + svgHeight = 500; + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); + + const yScale = d3.scaleLinear() + .domain([0, d3.max(entropyData, d => d)]) + .range([svgHeight - margins.bottom, margins.top]); + + const xScale = d3.scaleLinear() + .domain([0, entropyData.length]) + .range([margins.left, svgWidth - margins.right]); + + const line = d3.line() + .x((_, i) => xScale(i)) + .y(d => yScale(d)) + .curve(d3.curveMonotoneX); + + if (entropyData.length > 0) { + svg.append("path") + .datum(entropyData) + .attr("d", line); + + svg.selectAll("path").attr("fill", "none").attr("stroke", "steelblue"); + } + + this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, "Scanning Entropy", "Block", "Entropy"); + + return svg._groups[0][0].outerHTML; + } + + /** + * Creates an image representation of the entropy + * + * @param {number[]} entropyData + * @returns {HTML} + */ + createEntropyImage(entropyData) { + const svgHeight = 100, + svgWidth = 100, + cellSize = 1, + nodes = []; + + for (let i = 0; i < entropyData.length; i++) { + nodes.push({ + x: i % svgWidth, + y: Math.floor(i / svgWidth), + entropy: entropyData[i] + }); + } + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); + + const greyScale = d3.scaleLinear() + .domain([0, d3.max(entropyData, d => d)]) + .range(["#000000", "#FFFFFF"]) + .interpolate(d3.interpolateRgb); + + svg + .selectAll("rect") + .data(nodes) + .enter().append("rect") + .attr("x", d => d.x * cellSize) + .attr("y", d => d.y * cellSize) + .attr("width", cellSize) + .attr("height", cellSize) + .style("fill", d => greyScale(d.entropy)); + + return svg._groups[0][0].outerHTML; + } + + /** + * Displays the entropy as a scale bar for web apps. + * + * @param {number} entropy + * @returns {HTML} + */ + createShannonEntropyVisualization(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. + +
`; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {json} + */ + run(input, args) { + const visualizationType = args[0]; + input = new Uint8Array(input); + + switch (visualizationType) { + case "Histogram (Bar)": + case "Histogram (Line)": + return this.calculateByteFrequency(input); + case "Curve": + case "Image": + return this.calculateScanningEntropy(input).entropyData; + case "Shannon scale": + default: + return this.calculateShannonEntropy(input); + } + } + + /** + * Displays the entropy in a visualisation for web apps. + * + * @param {json} entropyData + * @param {Object[]} args + * @returns {html} + */ + present(entropyData, args) { + const visualizationType = args[0]; + + switch (visualizationType) { + case "Histogram (Bar)": + return this.createByteFrequencyBarHistogram(entropyData); + case "Histogram (Line)": + return this.createByteFrequencyLineHistogram(entropyData); + case "Curve": + return this.createEntropyCurve(entropyData); + case "Image": + return this.createEntropyImage(entropyData); + case "Shannon scale": + default: + return this.createShannonEntropyVisualization(entropyData); + } + } +} + +export default Entropy; diff --git a/src/core/operations/EscapeString.mjs b/src/core/operations/EscapeString.mjs new file mode 100644 index 00000000..3ddea181 --- /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.mjs"; +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..db2680c0 --- /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.mjs"; + +/** + * 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.checks = [ + { + pattern: "\\\\u(?:[\\da-f]{4,6})", + flags: "i", + args: ["\\u"] + }, + { + pattern: "%u(?:[\\da-f]{4,6})", + flags: "i", + args: ["%u"] + }, + { + pattern: "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..62afeb65 --- /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.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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 52bfaf7d..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) { - var 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) { - var 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) { - var 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) { - var regex = new RegExp(ips, "ig"); - - if (removeLocal) { - var 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) { - var 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) { - var 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) { - var displayTotal = args[0], - protocol = "[A-Z]+://", - hostname = "[-\\w]+(?:\\.\\w[-\\w]*)+", - port = ":\\d+", - path = "/[^.!,?;\"'<>()\\[\\]{}\\s\\x7F-\\xFF]*"; - - path += "(?:[.!,?]+[^.!,?;\"'<>()\\[\\]{}\\s\\x7F-\\xFF]+)*"; - var 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) { - var 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) { - var 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) { - var 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) { - var 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) { - var 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..74c177d3 --- /dev/null +++ b/src/core/operations/ExtractDates.mjs @@ -0,0 +1,58 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; + +/** + * 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"); + + const results = search(input, regex); + + if (displayTotal) { + return `Total found: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractDates; diff --git a/src/core/operations/ExtractDomains.mjs b/src/core/operations/ExtractDomains.mjs new file mode 100644 index 00000000..9e78bf3a --- /dev/null +++ b/src/core/operations/ExtractDomains.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { search, DOMAIN_REGEX, DMARC_DOMAIN_REGEX } from "../lib/Extract.mjs"; +import { caseInsensitiveSort } from "../lib/Sort.mjs"; + +/** + * Extract domains operation + */ +class ExtractDomains extends Operation { + + /** + * ExtractDomains constructor + */ + constructor() { + super(); + + this.name = "Extract domains"; + this.module = "Regex"; + this.description = "Extracts fully qualified 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: false + }, + { + name: "Sort", + type: "boolean", + value: false + }, + { + name: "Unique", + type: "boolean", + value: false + }, + { + name: "Underscore (DMARC, DKIM, etc)", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [displayTotal, sort, unique, dmarc] = args; + + const results = search( + input, + dmarc ? DMARC_DOMAIN_REGEX : DOMAIN_REGEX, + null, + sort ? caseInsensitiveSort : null, + unique + ); + + if (displayTotal) { + return `Total found: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractDomains; diff --git a/src/core/operations/ExtractEXIF.mjs b/src/core/operations/ExtractEXIF.mjs new file mode 100644 index 00000000..7edc8b80 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..f50e1aaf --- /dev/null +++ b/src/core/operations/ExtractEmailAddresses.mjs @@ -0,0 +1,73 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; +import { caseInsensitiveSort } from "../lib/Sort.mjs"; + +/** + * 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 + }, + { + name: "Sort", + type: "boolean", + value: false + }, + { + name: "Unique", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [displayTotal, sort, unique] = args, + // email regex from: https://www.regextester.com/98066 + regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}\])/ig; + + const results = search( + input, + regex, + null, + sort ? caseInsensitiveSort : null, + unique + ); + + if (displayTotal) { + return `Total found: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractEmailAddresses; diff --git a/src/core/operations/ExtractFilePaths.mjs b/src/core/operations/ExtractFilePaths.mjs new file mode 100644 index 00000000..5de76fe5 --- /dev/null +++ b/src/core/operations/ExtractFilePaths.mjs @@ -0,0 +1,102 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; +import { caseInsensitiveSort } from "../lib/Sort.mjs"; + +/** + * 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 + }, + { + name: "Sort", + type: "boolean", + value: false + }, + { + name: "Unique", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [includeWinPath, includeUnixPath, displayTotal, sort, unique] = 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) { + return ""; + } + + const regex = new RegExp(filePaths, "ig"); + const results = search( + input, + regex, + null, + sort ? caseInsensitiveSort : null, + unique + ); + + if (displayTotal) { + return `Total found: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + + } + +} + +export default ExtractFilePaths; diff --git a/src/core/operations/ExtractFiles.mjs b/src/core/operations/ExtractFiles.mjs new file mode 100644 index 00000000..3ff8ffb6 --- /dev/null +++ b/src/core/operations/ExtractFiles.mjs @@ -0,0 +1,123 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {scanForFileTypes, extractFile} from "../lib/FileType.mjs"; +import {FILE_SIGNATURES} from "../lib/FileSignatures.mjs"; + +/** + * Extract Files operation + */ +class ExtractFiles extends Operation { + + /** + * ExtractFiles constructor + */ + constructor() { + super(); + + // Get the first extension for each signature that can be extracted + let supportedExts = Object.keys(FILE_SIGNATURES).map(cat => { + return FILE_SIGNATURES[cat] + .filter(sig => sig.extractor) + .map(sig => sig.extension.toUpperCase()); + }); + + // Flatten categories and remove duplicates + supportedExts = [].concat(...supportedExts).unique(); + + this.name = "Extract Files"; + this.module = "Default"; + this.description = `Performs file carving to attempt to extract files from the input.

This operation is currently capable of carving out the following formats: +
    +
  • + ${supportedExts.join("
  • ")} +
  • +
Minimum File Size can be used to prune small false positives.`; + this.infoURL = "https://forensics.wiki/file_carving"; + this.inputType = "ArrayBuffer"; + this.outputType = "List"; + this.presentType = "html"; + this.args = Object.keys(FILE_SIGNATURES).map(cat => { + return { + name: cat, + type: "boolean", + value: cat === "Miscellaneous" ? false : true + }; + }).concat([ + { + name: "Ignore failed extractions", + type: "boolean", + value: true + }, + { + name: "Minimum File Size", + type: "number", + value: 100 + } + ]); + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {List} + */ + run(input, args) { + const bytes = new Uint8Array(input), + categories = [], + minSize = args.pop(1), + ignoreFailedExtractions = args.pop(1); + + args.forEach((cat, i) => { + if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]); + }); + + // Scan for embedded files + const detectedFiles = scanForFileTypes(bytes, categories); + + // Extract each file that we support + const files = []; + const errors = []; + detectedFiles.forEach(detectedFile => { + try { + const file = extractFile(bytes, detectedFile.fileDetails, detectedFile.offset); + if (file.size >= minSize) + files.push(file); + } catch (err) { + if (!ignoreFailedExtractions && err.message.indexOf("No extraction algorithm available") < 0) { + errors.push( + `Error while attempting to extract ${detectedFile.fileDetails.name} ` + + `at offset ${detectedFile.offset}:\n` + + `${err.message}` + ); + } + } + }); + + if (errors.length) { + throw new OperationError(errors.join("\n\n")); + } + + 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 ExtractFiles; diff --git a/src/core/operations/ExtractHashes.mjs b/src/core/operations/ExtractHashes.mjs new file mode 100644 index 00000000..fd50089f --- /dev/null +++ b/src/core/operations/ExtractHashes.mjs @@ -0,0 +1,84 @@ +/** + * @author mshwed [m@ttshwed.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; + +/** + * Extract Hash Values operation + */ +class ExtractHashes extends Operation { + + /** + * ExtractHashValues constructor + */ + constructor() { + super(); + + this.name = "Extract hashes"; + this.module = "Regex"; + this.description = "Extracts potential hashes based on hash character length"; + this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Hash character length", + type: "number", + value: 40 + }, + { + name: "All hashes", + type: "boolean", + value: false + }, + { + name: "Display Total", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const results = []; + let hashCount = 0; + + const [hashLength, searchAllHashes, showDisplayTotal] = args; + + // Convert character length to bit length + let hashBitLengths = [(hashLength / 2) * 8]; + + if (searchAllHashes) hashBitLengths = [4, 8, 16, 32, 64, 128, 160, 192, 224, 256, 320, 384, 512, 1024]; + + for (const hashBitLength of hashBitLengths) { + // Convert bit length to character length + const hashCharacterLength = (hashBitLength / 8) * 2; + + const regex = new RegExp(`(\\b|^)[a-f0-9]{${hashCharacterLength}}(\\b|$)`, "g"); + const searchResults = search(input, regex, null, false); + + hashCount += searchResults.length; + results.push(...searchResults); + } + + let output = ""; + if (showDisplayTotal) { + output = `Total Results: ${hashCount}\n\n`; + } + + output = output + results.join("\n"); + return output; + } + +} + +export default ExtractHashes; diff --git a/src/core/operations/ExtractID3.mjs b/src/core/operations/ExtractID3.mjs new file mode 100644 index 00000000..b06bdcfe --- /dev/null +++ b/src/core/operations/ExtractID3.mjs @@ -0,0 +1,324 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Extract ID3 operation + */ +class ExtractID3 extends Operation { + + /** + * ExtractID3 constructor + */ + constructor() { + super(); + + this.name = "Extract ID3"; + this.module = "Default"; + this.description = "This operation extracts ID3 metadata from an MP3 file.

ID3 is a metadata container most often used in conjunction with the MP3 audio file format. It allows information such as the title, artist, album, track number, and other information about the file to be stored in the file itself."; + this.infoURL = "https://wikipedia.org/wiki/ID3"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + input = new Uint8Array(input); + + /** + * Extracts the ID3 header fields. + */ + function extractHeader() { + if (!Array.from(input.slice(0, 3)).equals([0x49, 0x44, 0x33])) + throw new OperationError("No valid ID3 header."); + + const header = { + "Type": "ID3", + // Tag version + "Version": input[3].toString() + "." + input[4].toString(), + // Header version + "Flags": input[5].toString() + }; + + input = input.slice(6); + return header; + } + + /** + * Converts the size fields to a single integer. + * + * @param {number} num + * @returns {string} + */ + function readSize(num) { + let result = 0; + + // The sizes are 7 bit numbers stored in 8 bit locations + for (let i = (num) * 7; i; i -= 7) { + result = (result << i) | input[0]; + input = input.slice(1); + } + return result; + } + + /** + * Reads frame header based on ID. + * + * @param {string} id + * @returns {number} + */ + function readFrame(id) { + const frame = {}; + + // Size of frame + const size = readSize(4); + frame.Size = size.toString(); + frame.Description = FRAME_DESCRIPTIONS[id]; + input = input.slice(2); + + // Read data from frame + let data = ""; + for (let i = 1; i < size; i++) + data += String.fromCharCode(input[i]); + frame.Data = data; + + // Move to next Frame + input = input.slice(size); + + return [frame, size]; + } + + const result = extractHeader(); + + const headerTagSize = readSize(4); + result.Size = headerTagSize.toString(); + + const tags = {}; + let pos = 10; + + // While the current element is in the header + while (pos < headerTagSize) { + + // Frame Identifier of frame + let id = String.fromCharCode(input[0]) + String.fromCharCode(input[1]) + String.fromCharCode(input[2]); + input = input.slice(3); + + // If the next character is non-zero it is an identifier + if (input[0] !== 0) { + id += String.fromCharCode(input[0]); + } + input = input.slice(1); + + if (id in FRAME_DESCRIPTIONS) { + const [frame, size] = readFrame(id); + tags[id] = frame; + pos += 10 + size; + } else if (id === "\x00\x00\x00") { // end of header + break; + } else { + throw new OperationError("Unknown Frame Identifier: " + id); + } + } + + result.Tags = tags; + + return result; + } + + /** + * Displays the extracted data in a more accessible format for web apps. + * @param {JSON} data + * @returns {html} + */ + present(data) { + if (!data || !Object.prototype.hasOwnProperty.call(data, "Tags")) + return JSON.stringify(data, null, 4); + + let output = ` + `; + + for (const tagID in data.Tags) { + const description = data.Tags[tagID].Description, + contents = data.Tags[tagID].Data; + output += ``; + } + output += "
TagDescriptionData
${tagID}${Utils.escapeHtml(description)}${Utils.escapeHtml(contents)}
"; + return output; + } + +} + +// Borrowed from https://github.com/aadsm/jsmediatags +const FRAME_DESCRIPTIONS = { + // v2.2 + "BUF": "Recommended buffer size", + "CNT": "Play counter", + "COM": "Comments", + "CRA": "Audio encryption", + "CRM": "Encrypted meta frame", + "ETC": "Event timing codes", + "EQU": "Equalization", + "GEO": "General encapsulated object", + "IPL": "Involved people list", + "LNK": "Linked information", + "MCI": "Music CD Identifier", + "MLL": "MPEG location lookup table", + "PIC": "Attached picture", + "POP": "Popularimeter", + "REV": "Reverb", + "RVA": "Relative volume adjustment", + "SLT": "Synchronized lyric/text", + "STC": "Synced tempo codes", + "TAL": "Album/Movie/Show title", + "TBP": "BPM (Beats Per Minute)", + "TCM": "Composer", + "TCO": "Content type", + "TCR": "Copyright message", + "TDA": "Date", + "TDY": "Playlist delay", + "TEN": "Encoded by", + "TFT": "File type", + "TIM": "Time", + "TKE": "Initial key", + "TLA": "Language(s)", + "TLE": "Length", + "TMT": "Media type", + "TOA": "Original artist(s)/performer(s)", + "TOF": "Original filename", + "TOL": "Original Lyricist(s)/text writer(s)", + "TOR": "Original release year", + "TOT": "Original album/Movie/Show title", + "TP1": "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group", + "TP2": "Band/Orchestra/Accompaniment", + "TP3": "Conductor/Performer refinement", + "TP4": "Interpreted, remixed, or otherwise modified by", + "TPA": "Part of a set", + "TPB": "Publisher", + "TRC": "ISRC (International Standard Recording Code)", + "TRD": "Recording dates", + "TRK": "Track number/Position in set", + "TSI": "Size", + "TSS": "Software/hardware and settings used for encoding", + "TT1": "Content group description", + "TT2": "Title/Songname/Content description", + "TT3": "Subtitle/Description refinement", + "TXT": "Lyricist/text writer", + "TXX": "User defined text information frame", + "TYE": "Year", + "UFI": "Unique file identifier", + "ULT": "Unsychronized lyric/text transcription", + "WAF": "Official audio file webpage", + "WAR": "Official artist/performer webpage", + "WAS": "Official audio source webpage", + "WCM": "Commercial information", + "WCP": "Copyright/Legal information", + "WPB": "Publishers official webpage", + "WXX": "User defined URL link frame", + // v2.3 + "AENC": "Audio encryption", + "APIC": "Attached picture", + "ASPI": "Audio seek point index", + "CHAP": "Chapter", + "CTOC": "Table of contents", + "COMM": "Comments", + "COMR": "Commercial frame", + "ENCR": "Encryption method registration", + "EQU2": "Equalisation (2)", + "EQUA": "Equalization", + "ETCO": "Event timing codes", + "GEOB": "General encapsulated object", + "GRID": "Group identification registration", + "IPLS": "Involved people list", + "LINK": "Linked information", + "MCDI": "Music CD identifier", + "MLLT": "MPEG location lookup table", + "OWNE": "Ownership frame", + "PRIV": "Private frame", + "PCNT": "Play counter", + "POPM": "Popularimeter", + "POSS": "Position synchronisation frame", + "RBUF": "Recommended buffer size", + "RVA2": "Relative volume adjustment (2)", + "RVAD": "Relative volume adjustment", + "RVRB": "Reverb", + "SEEK": "Seek frame", + "SYLT": "Synchronized lyric/text", + "SYTC": "Synchronized tempo codes", + "TALB": "Album/Movie/Show title", + "TBPM": "BPM (beats per minute)", + "TCOM": "Composer", + "TCON": "Content type", + "TCOP": "Copyright message", + "TDAT": "Date", + "TDLY": "Playlist delay", + "TDRC": "Recording time", + "TDRL": "Release time", + "TDTG": "Tagging time", + "TENC": "Encoded by", + "TEXT": "Lyricist/Text writer", + "TFLT": "File type", + "TIME": "Time", + "TIPL": "Involved people list", + "TIT1": "Content group description", + "TIT2": "Title/songname/content description", + "TIT3": "Subtitle/Description refinement", + "TKEY": "Initial key", + "TLAN": "Language(s)", + "TLEN": "Length", + "TMCL": "Musician credits list", + "TMED": "Media type", + "TMOO": "Mood", + "TOAL": "Original album/movie/show title", + "TOFN": "Original filename", + "TOLY": "Original lyricist(s)/text writer(s)", + "TOPE": "Original artist(s)/performer(s)", + "TORY": "Original release year", + "TOWN": "File owner/licensee", + "TPE1": "Lead performer(s)/Soloist(s)", + "TPE2": "Band/orchestra/accompaniment", + "TPE3": "Conductor/performer refinement", + "TPE4": "Interpreted, remixed, or otherwise modified by", + "TPOS": "Part of a set", + "TPRO": "Produced notice", + "TPUB": "Publisher", + "TRCK": "Track number/Position in set", + "TRDA": "Recording dates", + "TRSN": "Internet radio station name", + "TRSO": "Internet radio station owner", + "TSOA": "Album sort order", + "TSOP": "Performer sort order", + "TSOT": "Title sort order", + "TSIZ": "Size", + "TSRC": "ISRC (international standard recording code)", + "TSSE": "Software/Hardware and settings used for encoding", + "TSST": "Set subtitle", + "TYER": "Year", + "TXXX": "User defined text information frame", + "UFID": "Unique file identifier", + "USER": "Terms of use", + "USLT": "Unsychronized lyric/text transcription", + "WCOM": "Commercial information", + "WCOP": "Copyright/Legal information", + "WOAF": "Official audio file webpage", + "WOAR": "Official artist/performer webpage", + "WOAS": "Official audio source webpage", + "WORS": "Official internet radio station homepage", + "WPAY": "Payment", + "WPUB": "Publishers official webpage", + "WXXX": "User defined URL link frame" +}; + +export default ExtractID3; diff --git a/src/core/operations/ExtractIPAddresses.mjs b/src/core/operations/ExtractIPAddresses.mjs new file mode 100644 index 00000000..97b52478 --- /dev/null +++ b/src/core/operations/ExtractIPAddresses.mjs @@ -0,0 +1,108 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; +import { ipSort } from "../lib/Sort.mjs"; + +/** + * 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 + }, + { + name: "Sort", + type: "boolean", + value: false + }, + { + name: "Unique", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = 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}"; + let ips = ""; + + if (includeIpv4 && includeIpv6) { + ips = ipv4 + "|" + ipv6; + } else if (includeIpv4) { + ips = ipv4; + } else if (includeIpv6) { + ips = ipv6; + } + + if (!ips) return ""; + + const regex = new RegExp(ips, "ig"); + + const ten = "10\\..+", + oneninetwo = "192\\.168\\..+", + oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+", + onetwoseven = "127\\..+", + removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo + + "|" + oneseventwo + "|" + onetwoseven + ")"); + + const results = search( + input, + regex, + removeLocal ? removeRegex : null, + sort ? ipSort : null, + unique + ); + + if (displayTotal) { + return `Total found: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractIPAddresses; diff --git a/src/core/operations/ExtractLSB.mjs b/src/core/operations/ExtractLSB.mjs new file mode 100644 index 00000000..d5c80406 --- /dev/null +++ b/src/core/operations/ExtractLSB.mjs @@ -0,0 +1,114 @@ +/** + * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { fromBinary } from "../lib/Binary.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Extract LSB operation + */ +class ExtractLSB extends Operation { + + /** + * ExtractLSB constructor + */ + constructor() { + super(); + + this.name = "Extract LSB"; + this.module = "Image"; + this.description = "Extracts the Least Significant Bit data from each pixel in an image. This is a common way to hide data in Steganography."; + this.infoURL = "https://wikipedia.org/wiki/Bit_numbering#Least_significant_bit_in_digital_steganography"; + this.inputType = "ArrayBuffer"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Colour Pattern #1", + type: "option", + value: COLOUR_OPTIONS, + }, + { + name: "Colour Pattern #2", + type: "option", + value: ["", ...COLOUR_OPTIONS], + }, + { + name: "Colour Pattern #3", + type: "option", + value: ["", ...COLOUR_OPTIONS], + }, + { + name: "Colour Pattern #4", + type: "option", + value: ["", ...COLOUR_OPTIONS], + }, + { + name: "Pixel Order", + type: "option", + value: ["Row", "Column"], + }, + { + name: "Bit", + type: "number", + value: 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + if (!isImage(input)) throw new OperationError("Please enter a valid image file."); + + const bit = 7 - args.pop(), + pixelOrder = args.pop(), + colours = args.filter(option => option !== "").map(option => COLOUR_OPTIONS.indexOf(option)), + parsedImage = await Jimp.read(input), + width = parsedImage.bitmap.width, + height = parsedImage.bitmap.height, + rgba = parsedImage.bitmap.data; + + if (bit < 0 || bit > 7) { + throw new OperationError("Error: Bit argument must be between 0 and 7"); + } + + let i, combinedBinary = ""; + + if (pixelOrder === "Row") { + for (i = 0; i < rgba.length; i += 4) { + for (const colour of colours) { + combinedBinary += Utils.bin(rgba[i + colour])[bit]; + } + } + } else { + let rowWidth; + const pixelWidth = width * 4; + for (let col = 0; col < width; col++) { + for (let row = 0; row < height; row++) { + rowWidth = row * pixelWidth; + for (const colour of colours) { + i = rowWidth + (col + colour * 4); + combinedBinary += Utils.bin(rgba[i])[bit]; + } + } + } + } + + return fromBinary(combinedBinary); + } + +} + +const COLOUR_OPTIONS = ["R", "G", "B", "A"]; + +export default ExtractLSB; diff --git a/src/core/operations/ExtractMACAddresses.mjs b/src/core/operations/ExtractMACAddresses.mjs new file mode 100644 index 00000000..1689d18f --- /dev/null +++ b/src/core/operations/ExtractMACAddresses.mjs @@ -0,0 +1,71 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; +import { hexadecimalSort } from "../lib/Sort.mjs"; + +/** + * 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 + }, + { + name: "Sort", + type: "boolean", + value: false + }, + { + name: "Unique", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [displayTotal, sort, unique] = args, + regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig, + results = search( + input, + regex, + null, + sort ? hexadecimalSort : null, + unique + ); + + if (displayTotal) { + return `Total found: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractMACAddresses; diff --git a/src/core/operations/ExtractRGBA.mjs b/src/core/operations/ExtractRGBA.mjs new file mode 100644 index 00000000..3339a2a7 --- /dev/null +++ b/src/core/operations/ExtractRGBA.mjs @@ -0,0 +1,65 @@ +/** + * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import Jimp from "jimp/es/index.js"; + +import {RGBA_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * Extract RGBA operation + */ +class ExtractRGBA extends Operation { + + /** + * ExtractRGBA constructor + */ + constructor() { + super(); + + this.name = "Extract RGBA"; + this.module = "Image"; + this.description = "Extracts each pixel's RGBA value in an image. These are sometimes used in Steganography to hide text or data."; + this.infoURL = "https://wikipedia.org/wiki/RGBA_color_space"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Delimiter", + type: "editableOption", + value: RGBA_DELIM_OPTIONS + }, + { + name: "Include Alpha", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + if (!isImage(input)) throw new OperationError("Please enter a valid image file."); + + const delimiter = args[0], + includeAlpha = args[1], + parsedImage = await Jimp.read(input); + + let bitmap = parsedImage.bitmap.data; + bitmap = includeAlpha ? bitmap : bitmap.filter((val, idx) => idx % 4 !== 3); + + return bitmap.join(delimiter); + } + +} + +export default ExtractRGBA; diff --git a/src/core/operations/ExtractURLs.mjs b/src/core/operations/ExtractURLs.mjs new file mode 100644 index 00000000..32cdb3a7 --- /dev/null +++ b/src/core/operations/ExtractURLs.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { search, URL_REGEX } from "../lib/Extract.mjs"; +import { caseInsensitiveSort } from "../lib/Sort.mjs"; + +/** + * 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 + }, + { + name: "Sort", + type: "boolean", + value: false + }, + { + name: "Unique", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [displayTotal, sort, unique] = args; + const results = search( + input, + URL_REGEX, + null, + sort ? caseInsensitiveSort : null, + unique + ); + + if (displayTotal) { + return `Total found: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default ExtractURLs; diff --git a/src/core/operations/FangURL.mjs b/src/core/operations/FangURL.mjs new file mode 100644 index 00000000..ad5bf525 --- /dev/null +++ b/src/core/operations/FangURL.mjs @@ -0,0 +1,78 @@ +/** + * @author arnydo [github@arnydo.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * FangURL operation + */ +class FangURL extends Operation { + + /** + * FangURL constructor + */ + constructor() { + super(); + + this.name = "Fang URL"; + this.module = "Default"; + this.description = "Takes a 'Defanged' Universal Resource Locator (URL) and 'Fangs' it. Meaning, it removes the alterations (defanged) that render it useless so that it can be used again."; + this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Restore [.]", + type: "boolean", + value: true + }, + { + name: "Restore hxxp", + type: "boolean", + value: true + }, + { + name: "Restore ://", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [dots, http, slashes] = args; + + input = fangURL(input, dots, http, slashes); + + return input; + } + +} + + +/** + * Defangs a given URL + * + * @param {string} url + * @param {boolean} dots + * @param {boolean} http + * @param {boolean} slashes + * @returns {string} + */ +function fangURL(url, dots, http, slashes) { + if (dots) url = url.replace(/\[\.\]/g, "."); + if (http) url = url.replace(/hxxp/g, "http"); + if (slashes) url = url.replace(/\[:\/\/\]/g, "://"); + + return url; +} + +export default FangURL; diff --git a/src/core/operations/FernetDecrypt.mjs b/src/core/operations/FernetDecrypt.mjs new file mode 100644 index 00000000..78d6efb9 --- /dev/null +++ b/src/core/operations/FernetDecrypt.mjs @@ -0,0 +1,63 @@ +/** + * @author Karsten Silkenbäumer [github.com/kassi] + * @copyright Karsten Silkenbäumer 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import fernet from "fernet"; + +/** + * FernetDecrypt operation + */ +class FernetDecrypt extends Operation { + /** + * FernetDecrypt constructor + */ + constructor() { + super(); + + this.name = "Fernet Decrypt"; + this.module = "Default"; + this.description = "Fernet is a symmetric encryption method which makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random().

Key: The key must be 32 bytes (256 bits) encoded with Base64."; + this.infoURL = "https://asecuritysite.com/encryption/fer"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "" + }, + ]; + this.patterns = [ + { + match: "^[A-Z\\d\\-_=]{20,}$", + flags: "i", + args: [] + }, + ]; + } + /** + * @param {String} input + * @param {Object[]} args + * @returns {String} + */ + run(input, args) { + const [secretInput] = args; + try { + const secret = new fernet.Secret(secretInput); + const token = new fernet.Token({ + secret: secret, + token: input, + ttl: 0 + }); + return token.decode(); + } catch (err) { + throw new OperationError(err); + } + } +} + +export default FernetDecrypt; diff --git a/src/core/operations/FernetEncrypt.mjs b/src/core/operations/FernetEncrypt.mjs new file mode 100644 index 00000000..84778486 --- /dev/null +++ b/src/core/operations/FernetEncrypt.mjs @@ -0,0 +1,54 @@ +/** + * @author Karsten Silkenbäumer [github.com/kassi] + * @copyright Karsten Silkenbäumer 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import fernet from "fernet"; + +/** + * FernetEncrypt operation + */ +class FernetEncrypt extends Operation { + /** + * FernetEncrypt constructor + */ + constructor() { + super(); + + this.name = "Fernet Encrypt"; + this.module = "Default"; + this.description = "Fernet is a symmetric encryption method which makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random().

Key: The key must be 32 bytes (256 bits) encoded with Base64."; + this.infoURL = "https://asecuritysite.com/encryption/fer"; + 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 [secretInput] = args; + try { + const secret = new fernet.Secret(secretInput); + const token = new fernet.Token({ + secret: secret, + }); + return token.encode(input); + } catch (err) { + throw new OperationError(err); + } + } +} + +export default FernetEncrypt; diff --git a/src/core/operations/FileTree.mjs b/src/core/operations/FileTree.mjs new file mode 100644 index 00000000..9484313f --- /dev/null +++ b/src/core/operations/FileTree.mjs @@ -0,0 +1,94 @@ +/** + * @author sw5678 + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * Unique operation + */ +class FileTree extends Operation { + + /** + * Unique constructor + */ + constructor() { + super(); + + this.name = "File Tree"; + this.module = "Default"; + this.description = "Creates a file tree from a list of file paths (similar to the tree command in Linux)"; + this.infoURL = "https://wikipedia.org/wiki/Tree_(command)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "File Path Delimiter", + type: "binaryString", + value: "/" + }, + { + name: "Delimiter", + type: "option", + value: INPUT_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + // Set up arrow and pipe for nice output display + const ARROW = "|---"; + const PIPE = "| "; + + // Get args from input + const fileDelim = args[0]; + const entryDelim = Utils.charRep(args[1]); + + // Store path to print + const completedList = []; + const printList = []; + + // Loop through all entries + const filePaths = input.split(entryDelim).unique().sort(); + for (let i = 0; i < filePaths.length; i++) { + // Split by file delimiter + let path = filePaths[i].split(fileDelim); + + if (path[0] === "") { + path = path.slice(1, path.length); + } + + for (let j = 0; j < path.length; j++) { + let printLine; + let key; + if (j === 0) { + printLine = path[j]; + key = path[j]; + } else { + printLine = PIPE.repeat(j-1) + ARROW + path[j]; + key = path.slice(0, j+1).join("/"); + } + + // Check to see we have already added that path + if (!completedList.includes(key)) { + completedList.push(key); + printList.push(printLine); + } + } + } + return printList.join("\n"); + } + +} + +export default FileTree; diff --git a/src/core/operations/FileType.js b/src/core/operations/FileType.js deleted file mode 100755 index 43bc622b..00000000 --- a/src/core/operations/FileType.js +++ /dev/null @@ -1,531 +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) { - var 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 { - var 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) { - var 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 (var 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. - * - * @private - * @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..904bb65f --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import OperationError from "../errors/OperationError.mjs"; +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..4fc1e43c --- /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.mjs"; +import Utils from "../Utils.mjs"; +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..b91ec2a8 --- /dev/null +++ b/src/core/operations/Fletcher16Checksum.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + input = new Uint8Array(input); + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xff; + 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..7b41ce55 --- /dev/null +++ b/src/core/operations/Fletcher32Checksum.mjs @@ -0,0 +1,58 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + if (ArrayBuffer.isView(input)) { + input = new DataView(input.buffer, input.byteOffset, input.byteLength); + } else { + input = new DataView(input); + } + + for (let i = 0; i < input.byteLength - 1; i += 2) { + a = (a + input.getUint16(i, true)) % 0xffff; + b = (b + a) % 0xffff; + } + if (input.byteLength % 2 !== 0) { + a = (a + input.getUint8(input.byteLength - 1)) % 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..68328ea3 --- /dev/null +++ b/src/core/operations/Fletcher64Checksum.mjs @@ -0,0 +1,62 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + if (ArrayBuffer.isView(input)) { + input = new DataView(input.buffer, input.byteOffset, input.byteLength); + } else { + input = new DataView(input); + } + + for (let i = 0; i < input.byteLength - 3; i += 4) { + a = (a + input.getUint32(i, true)) % 0xffffffff; + b = (b + a) % 0xffffffff; + } + if (input.byteLength % 4 !== 0) { + let lastValue = 0; + for (let i = 0; i < input.byteLength % 4; i++) { + lastValue = (lastValue << 8) | input.getUint8(input.byteLength - 1 - i); + } + a = (a + lastValue) % 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..1200c00c --- /dev/null +++ b/src/core/operations/Fletcher8Checksum.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + input = new Uint8Array(input); + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xf; + b = (b + a) % 0xf; + } + + return Utils.hex(((b << 4) | a) >>> 0, 2); + } + +} + +export default Fletcher8Checksum; diff --git a/src/core/operations/FlipImage.mjs b/src/core/operations/FlipImage.mjs new file mode 100644 index 00000000..f4b7cba9 --- /dev/null +++ b/src/core/operations/FlipImage.mjs @@ -0,0 +1,101 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Flip Image operation + */ +class FlipImage extends Operation { + + /** + * FlipImage constructor + */ + constructor() { + super(); + + this.name = "Flip Image"; + this.module = "Image"; + this.description = "Flips an image along its X or Y axis."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Axis", + type: "option", + value: ["Horizontal", "Vertical"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [flipAxis] = args; + if (!isImage(input)) { + throw new OperationError("Invalid input file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("Flipping image..."); + switch (flipAxis) { + case "Horizontal": + image.flip(true, false); + break; + case "Vertical": + image.flip(false, true); + break; + } + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error flipping image. (${err})`); + } + } + + /** + * Displays the flipped image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default FlipImage; diff --git a/src/core/operations/Fork.mjs b/src/core/operations/Fork.mjs new file mode 100644 index 00000000..3d4c596a --- /dev/null +++ b/src/core/operations/Fork.mjs @@ -0,0 +1,126 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Recipe from "../Recipe.mjs"; +import Dish from "../Dish.mjs"; + +/** + * 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); + + // Set to 1 as if we are here, then there is one, the current one. + let numOp = 1; + // 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) { + numOp--; + if (numOp === 0 || opList[i].ingValues[0]) + break; + else + // Not this Fork's Merge. + subOpList.push(opList[i]); + } else { + if (opList[i].name === "Fork" || opList[i].name === "Subsection") + numOp++; + subOpList.push(opList[i]); + } + } + + const recipe = new Recipe(); + const outputs = []; + let 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 + recipe.opList.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; + } + outputs.push(await dish.get(outputType)); + } + + state.dish.set(outputs.join(mergeDelim), 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..41fac594 --- /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.mjs"; + +/** + * 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..9bcb3f7f --- /dev/null +++ b/src/core/operations/FrequencyDistribution.mjs @@ -0,0 +1,132 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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": true + }, + { + "name": "Show ASCII", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @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, showAscii] = args; + + // Print + let output = `
+Total data length: ${freq.dataLength} +Number of bytes represented: ${freq.bytesRepresented} +Number of bytes not represented: ${256 - freq.bytesRepresented} + + + + ${showAscii ? "" : ""}`; + + for (let i = 0; i < 256; i++) { + if (freq.distribution[i] || showZeroes) { + let c = ""; + if (showAscii) { + if (i <= 32) { + c = String.fromCharCode(0x2400 + i); + } else if (i === 127) { + c = String.fromCharCode(0x2421); + } else { + c = String.fromCharCode(i); + } + } + const bite = ``, + ascii = showAscii ? `` : "", + percentage = ``, + bars = ``; + + output += `${bite}${ascii}${percentage}${bars}`; + } + } + + output += "
ByteASCIIPercentage
${Utils.hex(i, 2)}${c}${(freq.percentages[i].toFixed(2).replace(".00", "") + "%").padEnd(8, " ")}${Array(Math.ceil(freq.percentages[i])+1).join("|")}
"; + 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..8fa990a4 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD.mjs"; +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.checks = [ + { + pattern: "^(?:\\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 = new Uint8Array(Utils.strToArrayBuffer(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); // lgtm [js/loop-iteration-skipped-due-to-shifting] + } + } + + 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..4abd5c44 --- /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.mjs"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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); + + 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..8ee0f1f8 --- /dev/null +++ b/src/core/operations/FromBase32.mjs @@ -0,0 +1,106 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base32.mjs"; + + +/** + * 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: "editableOption", + value: ALPHABET_OPTIONS + }, + { + name: "Remove non-alphabet chars", + type: "boolean", + value: true + } + ]; + this.checks = [ + { + pattern: "^(?:[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] + }, + { + pattern: "^(?:[0-9A-V]{8})+(?:[0-9A-V]{2}={6}|[0-9A-V]{4}={4}|[0-9A-V]{5}={3}|[0-9A-V]{7}={1})?$", + flags: "", + args: ["0-9A-V=", 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/FromBase45.mjs b/src/core/operations/FromBase45.mjs new file mode 100644 index 00000000..8cb202bc --- /dev/null +++ b/src/core/operations/FromBase45.mjs @@ -0,0 +1,101 @@ +/** + * @author Thomas Weißschuh [thomas@t-8ch.de] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import {ALPHABET, highlightToBase45, highlightFromBase45} from "../lib/Base45.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; + + +/** + * From Base45 operation + */ +class FromBase45 extends Operation { + + /** + * FromBase45 constructor + */ + constructor() { + super(); + + this.name = "From Base45"; + this.module = "Default"; + this.description = "Base45 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. Base45 is optimized for usage with QR codes."; + this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Alphabet", + type: "string", + value: ALPHABET + }, + { + name: "Remove non-alphabet chars", + type: "boolean", + value: true + }, + ]; + + this.highlight = highlightFromBase45; + this.highlightReverse = highlightToBase45; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (!input) return []; + const alphabet = Utils.expandAlphRange(args[0]).join(""); + const removeNonAlphChars = args[1]; + + const res = []; + + // Remove non-alphabet characters + if (removeNonAlphChars) { + const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); + input = input.replace(re, ""); + } + + for (const triple of Utils.chunked(input, 3)) { + triple.reverse(); + let b = 0; + for (const c of triple) { + const idx = alphabet.indexOf(c); + if (idx === -1) { + throw new OperationError(`Character not in alphabet: '${c}'`); + } + b *= 45; + b += idx; + } + + if (b > 65535) { + throw new OperationError(`Triplet too large: '${triple.join("")}'`); + } + + if (triple.length > 2) { + /** + * The last triple may only have 2 bytes so we push the MSB when we got 3 bytes + * Pushing MSB + */ + res.push(b >> 8); + } + + /** + * Pushing LSB + */ + res.push(b & 0xff); + + } + + return res; + } + +} + +export default FromBase45; diff --git a/src/core/operations/FromBase58.mjs b/src/core/operations/FromBase58.mjs new file mode 100644 index 00000000..cb491159 --- /dev/null +++ b/src/core/operations/FromBase58.mjs @@ -0,0 +1,113 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base58.mjs"; + +/** + * 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.checks = [ + { + pattern: "^[1-9A-HJ-NP-Za-km-z]{20,}$", + flags: "", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", false] + }, + { + pattern: "^[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 = []; + + 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 = index; + + for (let i = 0; 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..9e91f647 --- /dev/null +++ b/src/core/operations/FromBase62.mjs @@ -0,0 +1,65 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import BigNumber from "bignumber.js"; +import Utils from "../Utils.mjs"; + + +/** + * 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 BN62 = BigNumber.clone({ ALPHABET: alphabet }); + + const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); + input = input.replace(re, ""); + + // Read number in using Base62 alphabet + const number = new BN62(input, 62); + // Copy to new BigNumber object that uses the default alphabet + const normalized = new BigNumber(number); + + // Convert to hex and add leading 0 if required + let hex = normalized.toString(16); + if (hex.length % 2 !== 0) hex = "0" + hex; + + return Utils.convertToByteArray(hex, "Hex"); + } + +} + +export default FromBase62; diff --git a/src/core/operations/FromBase64.mjs b/src/core/operations/FromBase64.mjs new file mode 100644 index 00000000..292de1e2 --- /dev/null +++ b/src/core/operations/FromBase64.mjs @@ -0,0 +1,175 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {fromBase64, ALPHABET_OPTIONS} from "../lib/Base64.mjs"; + +/** + * 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 + }, + { + name: "Strict mode", + type: "boolean", + value: false + } + ]; + this.checks = [ + { + pattern: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["A-Za-z0-9+/=", true, false] + }, + { + pattern: "^\\s*[A-Z\\d\\-_]{20,}\\s*$", + flags: "i", + args: ["A-Za-z0-9-_", true, false] + }, + { + pattern: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$", + flags: "i", + args: ["A-Za-z0-9+\\-=", true, false] + }, + { + pattern: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$", + flags: "i", + args: ["./0-9A-Za-z=", true, false] + }, + { + pattern: "^\\s*[A-Z\\d_.]{20,}\\s*$", + flags: "i", + args: ["A-Za-z0-9_.", true, false] + }, + { + pattern: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$", + flags: "i", + args: ["A-Za-z0-9._-", true, false] + }, + { + pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["0-9a-zA-Z+/=", true, false] + }, + { + pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$", + flags: "i", + args: ["0-9A-Za-z+/=", true, false] + }, + { + pattern: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$", + flags: "", + args: [" -_", false, false] + }, + { + pattern: "^\\s*[A-Z\\d+\\-]{20,}\\s*$", + flags: "i", + args: ["+\\-0-9A-Za-z", true, false] + }, + { + pattern: "^\\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, false] + }, + { + pattern: "^\\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, false] + }, + { + pattern: "^\\s*[A-Z\\d./]{20,}\\s*$", + flags: "i", + args: ["./0-9A-Za-z", true, false] + }, + { + pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}CC|[A-Z=\\d\\+/]{3}C)?\\s*$", + flags: "i", + args: ["/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", true, false] + }, + { + pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$", + flags: "i", + args: ["3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", true, false] + }, + { + pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}22|[A-Z=\\d\\+/]{3}2)?\\s*$", + flags: "i", + args: ["ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", true, false] + }, + { + pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$", + flags: "i", + args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true, false] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const [alphabet, removeNonAlphChars, strictMode] = args; + + return fromBase64(input, alphabet, "byteArray", removeNonAlphChars, strictMode); + } + + /** + * 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..d0c70da5 --- /dev/null +++ b/src/core/operations/FromBase85.mjs @@ -0,0 +1,160 @@ +/** + * @author PenguinGeorge [george@penguingeorge.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base85.mjs"; + +/** + * 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 + }, + { + name: "Remove non-alphabet chars", + type: "boolean", + value: true + }, + { + name: "All-zero group char", + type: "binaryShortString", + value: "z", + maxLength: 1 + } + ]; + this.checks = [ + { + pattern: + "^\\s*(?:<~)?" + // Optional whitespace and starting marker + "[\\s!-uz]*" + // Any amount of base85 characters and whitespace + "[!-uz]{15}" + // At least 15 continoues base85 characters without whitespace + "[\\s!-uz]*" + // Any amount of base85 characters and whitespace + "(?:~>)?\\s*$", // Optional ending marker and whitespace + args: ["!-u"], + }, + { + pattern: + "^" + + "[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" + + "[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]{15}" + // At least 15 continoues base85 characters without whitespace + "[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" + + "$", + args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"], + }, + { + pattern: + "^" + + "[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" + + "[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]{15}" + // At least 15 continoues base85 characters without whitespace + "[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" + + "$", + args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"], + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const alphabet = Utils.expandAlphRange(args[0]).join(""), + removeNonAlphChars = args[1], + allZeroGroupChar = typeof args[2] === "string" ? args[2].slice(0, 1) : "", + result = []; + + if (alphabet.length !== 85 || + [].unique.call(alphabet).length !== 85) { + throw new OperationError("Alphabet must be of length 85"); + } + + if (allZeroGroupChar && alphabet.includes(allZeroGroupChar)) { + throw new OperationError("The all-zero group char cannot appear in the alphabet"); + } + + // Remove delimiters if present + const matches = input.match(/^<~(.+?)~>$/); + if (matches !== null) input = matches[1]; + + // Remove non-alphabet characters + if (removeNonAlphChars) { + const re = new RegExp("[^~" + allZeroGroupChar +alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); + input = input.replace(re, ""); + // Remove delimiters again if present (incase of non-alphabet characters in front/behind delimiters) + const matches = input.match(/^<~(.+?)~>$/); + if (matches !== null) input = matches[1]; + } + + if (input.length === 0) return []; + + let i = 0; + let block, blockBytes; + while (i < input.length) { + if (input[i] === allZeroGroupChar) { + 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) && chr !== allZeroGroupChar) { + throw `Invalid character '${chr}' at index ${i + 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/FromBase92.mjs b/src/core/operations/FromBase92.mjs new file mode 100644 index 00000000..8315a51c --- /dev/null +++ b/src/core/operations/FromBase92.mjs @@ -0,0 +1,55 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import { base92Ord } from "../lib/Base92.mjs"; +import Operation from "../Operation.mjs"; + +/** + * From Base92 operation + */ +class FromBase92 extends Operation { + /** + * FromBase92 constructor + */ + constructor() { + super(); + + this.name = "From Base92"; + this.module = "Default"; + this.description = "Base92 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.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "string"; + this.outputType = "byteArray"; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const res = []; + let bitString = ""; + + for (let i = 0; i < input.length; i += 2) { + if (i + 1 !== input.length) { + const x = base92Ord(input[i]) * 91 + base92Ord(input[i + 1]); + bitString += x.toString(2).padStart(13, "0"); + } else { + const x = base92Ord(input[i]); + bitString += x.toString(2).padStart(6, "0"); + } + while (bitString.length >= 8) { + res.push(parseInt(bitString.slice(0, 8), 2)); + bitString = bitString.slice(8); + } + } + + return res; + } +} + +export default FromBase92; diff --git a/src/core/operations/FromBinary.mjs b/src/core/operations/FromBinary.mjs new file mode 100644 index 00000000..9a51fc8b --- /dev/null +++ b/src/core/operations/FromBinary.mjs @@ -0,0 +1,125 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {BIN_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import {fromBinary} from "../lib/Binary.mjs"; + +/** + * 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 + }, + { + "name": "Byte Length", + "type": "number", + "value": 8, + "min": 1 + } + ]; + this.checks = [ + { + pattern: "^(?:[01]{8})+$", + flags: "", + args: ["None"] + }, + { + pattern: "^(?:[01]{8})(?: [01]{8})*$", + flags: "", + args: ["Space"] + }, + { + pattern: "^(?:[01]{8})(?:,[01]{8})*$", + flags: "", + args: ["Comma"] + }, + { + pattern: "^(?:[01]{8})(?:;[01]{8})*$", + flags: "", + args: ["Semi-colon"] + }, + { + pattern: "^(?:[01]{8})(?::[01]{8})*$", + flags: "", + args: ["Colon"] + }, + { + pattern: "^(?:[01]{8})(?:\\n[01]{8})*$", + flags: "", + args: ["Line feed"] + }, + { + pattern: "^(?:[01]{8})(?:\\r\\n[01]{8})*$", + flags: "", + args: ["CRLF"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const byteLen = args[1] ? args[1] : 8; + return fromBinary(input, args[0], byteLen); + } + + /** + * 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..adbcff91 --- /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.mjs"; +import {BRAILLE_LOOKUP} from "../lib/Braille.mjs"; + +/** + * 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..31cbfb99 --- /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.mjs"; + +/** + * 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..ef02d394 --- /dev/null +++ b/src/core/operations/FromCharcode.mjs @@ -0,0 +1,85 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { DELIM_OPTIONS } from "../lib/Delim.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "Base", + "type": "number", + "value": 16 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {ArrayBuffer} + * + * @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 new ArrayBuffer; + } + + if (base !== 16 && isWorkerEnvironment()) 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.strToArrayBuffer(latin1); + } + +} + +export default FromCharcode; diff --git a/src/core/operations/FromDecimal.mjs b/src/core/operations/FromDecimal.mjs new file mode 100644 index 00000000..f98931d6 --- /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.mjs"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; +import {fromDecimal} from "../lib/Decimal.mjs"; + +/** + * 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.checks = [ + { + pattern: "^(?:\\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] + }, + { + pattern: "^(?:\\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] + }, + { + pattern: "^(?:\\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] + }, + { + pattern: "^(?:\\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] + }, + { + pattern: "^(?:\\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] + }, + { + pattern: "^(?:\\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/FromFloat.mjs b/src/core/operations/FromFloat.mjs new file mode 100644 index 00000000..8bf56d81 --- /dev/null +++ b/src/core/operations/FromFloat.mjs @@ -0,0 +1,78 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import ieee754 from "ieee754"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * From Float operation + */ +class FromFloat extends Operation { + + /** + * FromFloat constructor + */ + constructor() { + super(); + + this.name = "From Float"; + this.module = "Default"; + this.description = "Convert from IEEE754 Floating Point Numbers"; + this.infoURL = "https://wikipedia.org/wiki/IEEE_754"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Endianness", + "type": "option", + "value": [ + "Big Endian", + "Little Endian" + ] + }, + { + "name": "Size", + "type": "option", + "value": [ + "Float (4 bytes)", + "Double (8 bytes)" + ] + }, + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (input.length === 0) return []; + + const [endianness, size, delimiterName] = args; + const delim = Utils.charRep(delimiterName || "Space"); + const byteSize = size === "Double (8 bytes)" ? 8 : 4; + const isLE = endianness === "Little Endian"; + const mLen = byteSize === 4 ? 23 : 52; + const floats = input.split(delim); + + const output = new Array(floats.length*byteSize); + for (let i = 0; i < floats.length; i++) { + ieee754.write(output, parseFloat(floats[i]), i*byteSize, isLE, mLen, byteSize); + } + return output; + } + +} + +export default FromFloat; diff --git a/src/core/operations/FromHTMLEntity.mjs b/src/core/operations/FromHTMLEntity.mjs new file mode 100644 index 00000000..4d09876d --- /dev/null +++ b/src/core/operations/FromHTMLEntity.mjs @@ -0,0 +1,1539 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * From HTML Entity operation + */ +class FromHTMLEntity extends Operation { + + /** + * FromHTMLEntity constructor + */ + constructor() { + super(); + + this.name = "From HTML Entity"; + this.module = "Encodings"; + 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.checks = [ + { + pattern: "&(?:#\\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,20});/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 = { + "Tab": 9, + "NewLine": 10, + "excl": 33, + "quot": 34, + "num": 35, + "dollar": 36, + "percnt": 37, + "amp": 38, + "apos": 39, + "lpar": 40, + "rpar": 41, + "ast": 42, + "plus": 43, + "comma": 44, + "period": 46, + "sol": 47, + "colon": 58, + "semi": 59, + "lt": 60, + "equals": 61, + "gt": 62, + "quest": 63, + "commat": 64, + "lsqb": 91, + "bsol": 92, + "rsqb": 93, + "Hat": 94, + "lowbar": 95, + "grave": 96, + "lcub": 123, + "verbar": 124, + "rcub": 125, + "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, + "Amacr": 256, + "amacr": 257, + "Abreve": 258, + "abreve": 259, + "Aogon": 260, + "aogon": 261, + "Cacute": 262, + "cacute": 263, + "Ccirc": 264, + "ccirc": 265, + "Cdot": 266, + "cdot": 267, + "Ccaron": 268, + "ccaron": 269, + "Dcaron": 270, + "dcaron": 271, + "Dstrok": 272, + "dstrok": 273, + "Emacr": 274, + "emacr": 275, + "Edot": 278, + "edot": 279, + "Eogon": 280, + "eogon": 281, + "Ecaron": 282, + "ecaron": 283, + "Gcirc": 284, + "gcirc": 285, + "Gbreve": 286, + "gbreve": 287, + "Gdot": 288, + "gdot": 289, + "Gcedil": 290, + "Hcirc": 292, + "hcirc": 293, + "Hstrok": 294, + "hstrok": 295, + "Itilde": 296, + "itilde": 297, + "Imacr": 298, + "imacr": 299, + "Iogon": 302, + "iogon": 303, + "Idot": 304, + "imath": 305, + "IJlig": 306, + "ijlig": 307, + "Jcirc": 308, + "jcirc": 309, + "Kcedil": 310, + "kcedil": 311, + "kgreen": 312, + "Lacute": 313, + "lacute": 314, + "Lcedil": 315, + "lcedil": 316, + "Lcaron": 317, + "lcaron": 318, + "Lmidot": 319, + "lmidot": 320, + "Lstrok": 321, + "lstrok": 322, + "Nacute": 323, + "nacute": 324, + "Ncedil": 325, + "ncedil": 326, + "Ncaron": 327, + "ncaron": 328, + "napos": 329, + "ENG": 330, + "eng": 331, + "Omacr": 332, + "omacr": 333, + "Odblac": 336, + "odblac": 337, + "OElig": 338, + "oelig": 339, + "Racute": 340, + "racute": 341, + "Rcedil": 342, + "rcedil": 343, + "Rcaron": 344, + "rcaron": 345, + "Sacute": 346, + "sacute": 347, + "Scirc": 348, + "scirc": 349, + "Scedil": 350, + "scedil": 351, + "Scaron": 352, + "scaron": 353, + "Tcedil": 354, + "tcedil": 355, + "Tcaron": 356, + "tcaron": 357, + "Tstrok": 358, + "tstrok": 359, + "Utilde": 360, + "utilde": 361, + "Umacr": 362, + "umacr": 363, + "Ubreve": 364, + "ubreve": 365, + "Uring": 366, + "uring": 367, + "Udblac": 368, + "udblac": 369, + "Uogon": 370, + "uogon": 371, + "Wcirc": 372, + "wcirc": 373, + "Ycirc": 374, + "ycirc": 375, + "Yuml": 376, + "Zacute": 377, + "zacute": 378, + "Zdot": 379, + "zdot": 380, + "Zcaron": 381, + "zcaron": 382, + "fnof": 402, + "imped": 437, + "gacute": 501, + "jmath": 567, + "circ": 710, + "caron": 711, + "breve": 728, + "dot": 729, + "ring": 730, + "ogon": 731, + "tilde": 732, + "dblac": 733, + "DownBreve": 785, + "UnderBar": 818, + "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, + "straightphi": 981, + "piv": 982, + "Gammad": 988, + "gammad": 989, + "kappav": 1008, + "rhov": 1009, + "epsi": 1013, + "bepsi": 1014, + "IOcy": 1025, + "DJcy": 1026, + "GJcy": 1027, + "Jukcy": 1028, + "DScy": 1029, + "Iukcy": 1030, + "YIcy": 1031, + "Jsercy": 1032, + "LJcy": 1033, + "NJcy": 1034, + "TSHcy": 1035, + "KJcy": 1036, + "Ubrcy": 1038, + "DZcy": 1039, + "Acy": 1040, + "Bcy": 1041, + "Vcy": 1042, + "Gcy": 1043, + "Dcy": 1044, + "IEcy": 1045, + "ZHcy": 1046, + "Zcy": 1047, + "Icy": 1048, + "Jcy": 1049, + "Kcy": 1050, + "Lcy": 1051, + "Mcy": 1052, + "Ncy": 1053, + "Ocy": 1054, + "Pcy": 1055, + "Rcy": 1056, + "Scy": 1057, + "Tcy": 1058, + "Ucy": 1059, + "Fcy": 1060, + "KHcy": 1061, + "TScy": 1062, + "CHcy": 1063, + "SHcy": 1064, + "SHCHcy": 1065, + "HARDcy": 1066, + "Ycy": 1067, + "SOFTcy": 1068, + "Ecy": 1069, + "YUcy": 1070, + "YAcy": 1071, + "acy": 1072, + "bcy": 1073, + "vcy": 1074, + "gcy": 1075, + "dcy": 1076, + "iecy": 1077, + "zhcy": 1078, + "zcy": 1079, + "icy": 1080, + "jcy": 1081, + "kcy": 1082, + "lcy": 1083, + "mcy": 1084, + "ncy": 1085, + "ocy": 1086, + "pcy": 1087, + "rcy": 1088, + "scy": 1089, + "tcy": 1090, + "ucy": 1091, + "fcy": 1092, + "khcy": 1093, + "tscy": 1094, + "chcy": 1095, + "shcy": 1096, + "shchcy": 1097, + "hardcy": 1098, + "ycy": 1099, + "softcy": 1100, + "ecy": 1101, + "yucy": 1102, + "yacy": 1103, + "iocy": 1105, + "djcy": 1106, + "gjcy": 1107, + "jukcy": 1108, + "dscy": 1109, + "iukcy": 1110, + "yicy": 1111, + "jsercy": 1112, + "ljcy": 1113, + "njcy": 1114, + "tshcy": 1115, + "kjcy": 1116, + "ubrcy": 1118, + "dzcy": 1119, + "ensp": 8194, + "emsp": 8195, + "emsp13": 8196, + "emsp14": 8197, + "numsp": 8199, + "puncsp": 8200, + "thinsp": 8201, + "hairsp": 8202, + "ZeroWidthSpace": 8203, + "zwnj": 8204, + "zwj": 8205, + "lrm": 8206, + "rlm": 8207, + "hyphen": 8208, + "ndash": 8211, + "mdash": 8212, + "horbar": 8213, + "Verbar": 8214, + "lsquo": 8216, + "rsquo": 8217, + "sbquo": 8218, + "ldquo": 8220, + "rdquo": 8221, + "bdquo": 8222, + "dagger": 8224, + "Dagger": 8225, + "bull": 8226, + "nldr": 8229, + "hellip": 8230, + "permil": 8240, + "pertenk": 8241, + "prime": 8242, + "Prime": 8243, + "tprime": 8244, + "bprime": 8245, + "lsaquo": 8249, + "rsaquo": 8250, + "oline": 8254, + "caret": 8257, + "hybull": 8259, + "frasl": 8260, + "bsemi": 8271, + "qprime": 8279, + "MediumSpace": 8287, + "NoBreak": 8288, + "ApplyFunction": 8289, + "InvisibleTimes": 8290, + "InvisibleComma": 8291, + "euro": 8364, + "tdot": 8411, + "TripleDot": 8411, + "DotDot": 8412, + "Copf": 8450, + "incare": 8453, + "gscr": 8458, + "hamilt": 8459, + "Hfr": 8460, + "quaternions": 8461, + "planckh": 8462, + "planck": 8463, + "Iscr": 8464, + "image": 8465, + "Lscr": 8466, + "ell": 8467, + "Nopf": 8469, + "numero": 8470, + "copysr": 8471, + "weierp": 8472, + "Popf": 8473, + "rationals": 8474, + "Rscr": 8475, + "real": 8476, + "reals": 8477, + "rx": 8478, + "trade": 8482, + "integers": 8484, + "ohm": 8486, + "mho": 8487, + "Zfr": 8488, + "iiota": 8489, + "angst": 8491, + "bernou": 8492, + "Cfr": 8493, + "escr": 8495, + "Escr": 8496, + "Fscr": 8497, + "phmmat": 8499, + "order": 8500, + "alefsym": 8501, + "beth": 8502, + "gimel": 8503, + "daleth": 8504, + "CapitalDifferentialD": 8517, + "DifferentialD": 8518, + "ExponentialE": 8519, + "ImaginaryI": 8520, + "frac13": 8531, + "frac23": 8532, + "frac15": 8533, + "frac25": 8534, + "frac35": 8535, + "frac45": 8536, + "frac16": 8537, + "frac56": 8538, + "frac18": 8539, + "frac38": 8540, + "frac58": 8541, + "frac78": 8542, + "larr": 8592, + "uarr": 8593, + "rarr": 8594, + "darr": 8595, + "harr": 8596, + "varr": 8597, + "nwarr": 8598, + "nearr": 8599, + "searr": 8600, + "swarr": 8601, + "nlarr": 8602, + "nrarr": 8603, + "rarrw": 8605, + "Larr": 8606, + "Uarr": 8607, + "Rarr": 8608, + "Darr": 8609, + "larrtl": 8610, + "rarrtl": 8611, + "LeftTeeArrow": 8612, + "UpTeeArrow": 8613, + "map": 8614, + "DownTeeArrow": 8615, + "larrhk": 8617, + "rarrhk": 8618, + "larrlp": 8619, + "rarrlp": 8620, + "harrw": 8621, + "nharr": 8622, + "lsh": 8624, + "rsh": 8625, + "ldsh": 8626, + "rdsh": 8627, + "crarr": 8629, + "cularr": 8630, + "curarr": 8631, + "olarr": 8634, + "orarr": 8635, + "lharu": 8636, + "lhard": 8637, + "uharr": 8638, + "uharl": 8639, + "rharu": 8640, + "rhard": 8641, + "dharr": 8642, + "dharl": 8643, + "rlarr": 8644, + "udarr": 8645, + "lrarr": 8646, + "llarr": 8647, + "uuarr": 8648, + "rrarr": 8649, + "ddarr": 8650, + "lrhar": 8651, + "rlhar": 8652, + "nlArr": 8653, + "nhArr": 8654, + "nrArr": 8655, + "lArr": 8656, + "uArr": 8657, + "rArr": 8658, + "dArr": 8659, + "hArr": 8660, + "vArr": 8661, + "nwArr": 8662, + "neArr": 8663, + "seArr": 8664, + "swArr": 8665, + "lAarr": 8666, + "rAarr": 8667, + "zigrarr": 8669, + "larrb": 8676, + "rarrb": 8677, + "duarr": 8693, + "loarr": 8701, + "roarr": 8702, + "hoarr": 8703, + "forall": 8704, + "comp": 8705, + "part": 8706, + "exist": 8707, + "nexist": 8708, + "empty": 8709, + "nabla": 8711, + "isin": 8712, + "notin": 8713, + "ni": 8715, + "notni": 8716, + "prod": 8719, + "coprod": 8720, + "sum": 8721, + "minus": 8722, + "mnplus": 8723, + "plusdo": 8724, + "setmn": 8726, + "lowast": 8727, + "compfn": 8728, + "radic": 8730, + "prop": 8733, + "infin": 8734, + "angrt": 8735, + "ang": 8736, + "angmsd": 8737, + "angsph": 8738, + "mid": 8739, + "nmid": 8740, + "par": 8741, + "npar": 8742, + "and": 8743, + "or": 8744, + "cap": 8745, + "cup": 8746, + "int": 8747, + "Int": 8748, + "tint": 8749, + "conint": 8750, + "Conint": 8751, + "Cconint": 8752, + "cwint": 8753, + "cwconint": 8754, + "awconint": 8755, + "there4": 8756, + "becaus": 8757, + "ratio": 8758, + "Colon": 8759, + "minusd": 8760, + "mDDot": 8762, + "homtht": 8763, + "sim": 8764, + "bsim": 8765, + "ac": 8766, + "acd": 8767, + "wreath": 8768, + "nsim": 8769, + "esim": 8770, + "sime": 8771, + "nsime": 8772, + "cong": 8773, + "simne": 8774, + "ncong": 8775, + "asymp": 8776, + "nap": 8777, + "ape": 8778, + "apid": 8779, + "bcong": 8780, + "asympeq": 8781, + "bump": 8782, + "bumpe": 8783, + "esdot": 8784, + "eDot": 8785, + "efDot": 8786, + "erDot": 8787, + "colone": 8788, + "ecolon": 8789, + "ecir": 8790, + "cire": 8791, + "wedgeq": 8793, + "veeeq": 8794, + "trie": 8796, + "equest": 8799, + "ne": 8800, + "equiv": 8801, + "nequiv": 8802, + "le": 8804, + "ge": 8805, + "lE": 8806, + "gE": 8807, + "lnE": 8808, + "gnE": 8809, + "Lt": 8810, + "Gt": 8811, + "twixt": 8812, + "NotCupCap": 8813, + "nlt": 8814, + "ngt": 8815, + "nle": 8816, + "nge": 8817, + "lsim": 8818, + "gsim": 8819, + "nlsim": 8820, + "ngsim": 8821, + "lg": 8822, + "gl": 8823, + "ntlg": 8824, + "ntgl": 8825, + "pr": 8826, + "sc": 8827, + "prcue": 8828, + "sccue": 8829, + "prsim": 8830, + "scsim": 8831, + "npr": 8832, + "nsc": 8833, + "sub": 8834, + "sup": 8835, + "nsub": 8836, + "nsup": 8837, + "sube": 8838, + "supe": 8839, + "nsube": 8840, + "nsupe": 8841, + "subne": 8842, + "supne": 8843, + "cupdot": 8845, + "uplus": 8846, + "sqsub": 8847, + "sqsup": 8848, + "sqsube": 8849, + "sqsupe": 8850, + "sqcap": 8851, + "sqcup": 8852, + "oplus": 8853, + "ominus": 8854, + "otimes": 8855, + "osol": 8856, + "odot": 8857, + "ocir": 8858, + "oast": 8859, + "odash": 8861, + "plusb": 8862, + "minusb": 8863, + "timesb": 8864, + "sdotb": 8865, + "vdash": 8866, + "dashv": 8867, + "top": 8868, + "perp": 8869, + "models": 8871, + "vDash": 8872, + "Vdash": 8873, + "Vvdash": 8874, + "VDash": 8875, + "nvdash": 8876, + "nvDash": 8877, + "nVdash": 8878, + "nVDash": 8879, + "prurel": 8880, + "vltri": 8882, + "vrtri": 8883, + "ltrie": 8884, + "rtrie": 8885, + "origof": 8886, + "imof": 8887, + "mumap": 8888, + "hercon": 8889, + "intcal": 8890, + "veebar": 8891, + "barvee": 8893, + "angrtvb": 8894, + "lrtri": 8895, + "xwedge": 8896, + "xvee": 8897, + "xcap": 8898, + "xcup": 8899, + "diam": 8900, + "sdot": 8901, + "sstarf": 8902, + "divonx": 8903, + "bowtie": 8904, + "ltimes": 8905, + "rtimes": 8906, + "lthree": 8907, + "rthree": 8908, + "bsime": 8909, + "cuvee": 8910, + "cuwed": 8911, + "Sub": 8912, + "Sup": 8913, + "Cap": 8914, + "Cup": 8915, + "fork": 8916, + "epar": 8917, + "ltdot": 8918, + "gtdot": 8919, + "Ll": 8920, + "Gg": 8921, + "leg": 8922, + "gel": 8923, + "cuepr": 8926, + "cuesc": 8927, + "nprcue": 8928, + "nsccue": 8929, + "nsqsube": 8930, + "nsqsupe": 8931, + "lnsim": 8934, + "gnsim": 8935, + "prnsim": 8936, + "scnsim": 8937, + "nltri": 8938, + "nrtri": 8939, + "nltrie": 8940, + "nrtrie": 8941, + "vellip": 8942, + "ctdot": 8943, + "utdot": 8944, + "dtdot": 8945, + "disin": 8946, + "isinsv": 8947, + "isins": 8948, + "isindot": 8949, + "notinvc": 8950, + "notinvb": 8951, + "isinE": 8953, + "nisd": 8954, + "xnis": 8955, + "nis": 8956, + "notnivc": 8957, + "notnivb": 8958, + "barwed": 8965, + "Barwed": 8966, + "lceil": 8968, + "rceil": 8969, + "lfloor": 8970, + "rfloor": 8971, + "drcrop": 8972, + "dlcrop": 8973, + "urcrop": 8974, + "ulcrop": 8975, + "bnot": 8976, + "profline": 8978, + "profsurf": 8979, + "telrec": 8981, + "target": 8982, + "ulcorn": 8988, + "urcorn": 8989, + "dlcorn": 8990, + "drcorn": 8991, + "frown": 8994, + "smile": 8995, + "lang": 9001, + "rang": 9002, + "cylcty": 9005, + "profalar": 9006, + "topbot": 9014, + "ovbar": 9021, + "solbar": 9023, + "angzarr": 9084, + "lmoust": 9136, + "rmoust": 9137, + "tbrk": 9140, + "bbrk": 9141, + "bbrktbrk": 9142, + "OverParenthesis": 9180, + "UnderParenthesis": 9181, + "OverBrace": 9182, + "UnderBrace": 9183, + "trpezium": 9186, + "elinters": 9191, + "blank": 9251, + "oS": 9416, + "boxh": 9472, + "boxv": 9474, + "boxdr": 9484, + "boxdl": 9488, + "boxur": 9492, + "boxul": 9496, + "boxvr": 9500, + "boxvl": 9508, + "boxhd": 9516, + "boxhu": 9524, + "boxvh": 9532, + "boxH": 9552, + "boxV": 9553, + "boxdR": 9554, + "boxDr": 9555, + "boxDR": 9556, + "boxdL": 9557, + "boxDl": 9558, + "boxDL": 9559, + "boxuR": 9560, + "boxUr": 9561, + "boxUR": 9562, + "boxuL": 9563, + "boxUl": 9564, + "boxUL": 9565, + "boxvR": 9566, + "boxVr": 9567, + "boxVR": 9568, + "boxvL": 9569, + "boxVl": 9570, + "boxVL": 9571, + "boxHd": 9572, + "boxhD": 9573, + "boxHD": 9574, + "boxHu": 9575, + "boxhU": 9576, + "boxHU": 9577, + "boxvH": 9578, + "boxVh": 9579, + "boxVH": 9580, + "uhblk": 9600, + "lhblk": 9604, + "block": 9608, + "blk14": 9617, + "blk12": 9618, + "blk34": 9619, + "squ": 9633, + "squf": 9642, + "EmptyVerySmallSquare": 9643, + "rect": 9645, + "marker": 9646, + "fltns": 9649, + "xutri": 9651, + "utrif": 9652, + "utri": 9653, + "rtrif": 9656, + "rtri": 9657, + "xdtri": 9661, + "dtrif": 9662, + "dtri": 9663, + "ltrif": 9666, + "ltri": 9667, + "loz": 9674, + "cir": 9675, + "tridot": 9708, + "xcirc": 9711, + "ultri": 9720, + "urtri": 9721, + "lltri": 9722, + "EmptySmallSquare": 9723, + "FilledSmallSquare": 9724, + "starf": 9733, + "bigstar": 9733, + "star": 9734, + "phone": 9742, + "female": 9792, + "male": 9794, + "spades": 9824, + "clubs": 9827, + "hearts": 9829, + "diams": 9830, + "sung": 9834, + "flat": 9837, + "natur": 9838, + "sharp": 9839, + "check": 10003, + "cross": 10007, + "malt": 10016, + "sext": 10038, + "VerticalSeparator": 10072, + "lbbrk": 10098, + "rbbrk": 10099, + "lobrk": 10214, + "robrk": 10215, + "Lang": 10218, + "Rang": 10219, + "loang": 10220, + "roang": 10221, + "xlarr": 10229, + "xrarr": 10230, + "xharr": 10231, + "xlArr": 10232, + "xrArr": 10233, + "xhArr": 10234, + "xmap": 10236, + "dzigrarr": 10239, + "nvlArr": 10498, + "nvrArr": 10499, + "nvHarr": 10500, + "Map": 10501, + "lbarr": 10508, + "rbarr": 10509, + "lBarr": 10510, + "rBarr": 10511, + "RBarr": 10512, + "DDotrahd": 10513, + "UpArrowBar": 10514, + "DownArrowBar": 10515, + "Rarrtl": 10518, + "latail": 10521, + "ratail": 10522, + "lAtail": 10523, + "rAtail": 10524, + "larrfs": 10525, + "rarrfs": 10526, + "larrbfs": 10527, + "rarrbfs": 10528, + "nwarhk": 10531, + "nearhk": 10532, + "searhk": 10533, + "swarhk": 10534, + "nwnear": 10535, + "nesear": 10536, + "seswar": 10537, + "swnwar": 10538, + "rarrc": 10547, + "cudarrr": 10549, + "ldca": 10550, + "rdca": 10551, + "cudarrl": 10552, + "larrpl": 10553, + "curarrm": 10556, + "cularrp": 10557, + "rarrpl": 10565, + "harrcir": 10568, + "Uarrocir": 10569, + "lurdshar": 10570, + "ldrushar": 10571, + "LeftRightVector": 10574, + "RightUpDownVector": 10575, + "DownLeftRightVector": 10576, + "LeftUpDownVector": 10577, + "LeftVectorBar": 10578, + "RightVectorBar": 10579, + "RightUpVectorBar": 10580, + "RightDownVectorBar": 10581, + "DownLeftVectorBar": 10582, + "DownRightVectorBar": 10583, + "LeftUpVectorBar": 10584, + "LeftDownVectorBar": 10585, + "LeftTeeVector": 10586, + "RightTeeVector": 10587, + "RightUpTeeVector": 10588, + "RightDownTeeVector": 10589, + "DownLeftTeeVector": 10590, + "DownRightTeeVector": 10591, + "LeftUpTeeVector": 10592, + "LeftDownTeeVector": 10593, + "lHar": 10594, + "uHar": 10595, + "rHar": 10596, + "dHar": 10597, + "luruhar": 10598, + "ldrdhar": 10599, + "ruluhar": 10600, + "rdldhar": 10601, + "lharul": 10602, + "llhard": 10603, + "rharul": 10604, + "lrhard": 10605, + "udhar": 10606, + "duhar": 10607, + "RoundImplies": 10608, + "erarr": 10609, + "simrarr": 10610, + "larrsim": 10611, + "rarrsim": 10612, + "rarrap": 10613, + "ltlarr": 10614, + "gtrarr": 10616, + "subrarr": 10617, + "suplarr": 10619, + "lfisht": 10620, + "rfisht": 10621, + "ufisht": 10622, + "dfisht": 10623, + "lopar": 10629, + "ropar": 10630, + "lbrke": 10635, + "rbrke": 10636, + "lbrkslu": 10637, + "rbrksld": 10638, + "lbrksld": 10639, + "rbrkslu": 10640, + "langd": 10641, + "rangd": 10642, + "lparlt": 10643, + "rpargt": 10644, + "gtlPar": 10645, + "ltrPar": 10646, + "vzigzag": 10650, + "vangrt": 10652, + "angrtvbd": 10653, + "ange": 10660, + "range": 10661, + "dwangle": 10662, + "uwangle": 10663, + "angmsdaa": 10664, + "angmsdab": 10665, + "angmsdac": 10666, + "angmsdad": 10667, + "angmsdae": 10668, + "angmsdaf": 10669, + "angmsdag": 10670, + "angmsdah": 10671, + "bemptyv": 10672, + "demptyv": 10673, + "cemptyv": 10674, + "raemptyv": 10675, + "laemptyv": 10676, + "ohbar": 10677, + "omid": 10678, + "opar": 10679, + "operp": 10681, + "olcross": 10683, + "odsold": 10684, + "olcir": 10686, + "ofcir": 10687, + "olt": 10688, + "ogt": 10689, + "cirscir": 10690, + "cirE": 10691, + "solb": 10692, + "bsolb": 10693, + "boxbox": 10697, + "trisb": 10701, + "rtriltri": 10702, + "LeftTriangleBar": 10703, + "RightTriangleBar": 10704, + "race": 10714, + "iinfin": 10716, + "infintie": 10717, + "nvinfin": 10718, + "eparsl": 10723, + "smeparsl": 10724, + "eqvparsl": 10725, + "lozf": 10731, + "RuleDelayed": 10740, + "dsol": 10742, + "xodot": 10752, + "xoplus": 10753, + "xotime": 10754, + "xuplus": 10756, + "xsqcup": 10758, + "qint": 10764, + "fpartint": 10765, + "cirfnint": 10768, + "awint": 10769, + "rppolint": 10770, + "scpolint": 10771, + "npolint": 10772, + "pointint": 10773, + "quatint": 10774, + "intlarhk": 10775, + "pluscir": 10786, + "plusacir": 10787, + "simplus": 10788, + "plusdu": 10789, + "plussim": 10790, + "plustwo": 10791, + "mcomma": 10793, + "minusdu": 10794, + "loplus": 10797, + "roplus": 10798, + "Cross": 10799, + "timesd": 10800, + "timesbar": 10801, + "smashp": 10803, + "lotimes": 10804, + "rotimes": 10805, + "otimesas": 10806, + "Otimes": 10807, + "odiv": 10808, + "triplus": 10809, + "triminus": 10810, + "tritime": 10811, + "iprod": 10812, + "amalg": 10815, + "capdot": 10816, + "ncup": 10818, + "ncap": 10819, + "capand": 10820, + "cupor": 10821, + "cupcap": 10822, + "capcup": 10823, + "cupbrcap": 10824, + "capbrcup": 10825, + "cupcup": 10826, + "capcap": 10827, + "ccups": 10828, + "ccaps": 10829, + "ccupssm": 10832, + "And": 10835, + "Or": 10836, + "andand": 10837, + "oror": 10838, + "orslope": 10839, + "andslope": 10840, + "andv": 10842, + "orv": 10843, + "andd": 10844, + "ord": 10845, + "wedbar": 10847, + "sdote": 10854, + "simdot": 10858, + "congdot": 10861, + "easter": 10862, + "apacir": 10863, + "apE": 10864, + "eplus": 10865, + "pluse": 10866, + "Esim": 10867, + "Colone": 10868, + "Equal": 10869, + "eDDot": 10871, + "equivDD": 10872, + "ltcir": 10873, + "gtcir": 10874, + "ltquest": 10875, + "gtquest": 10876, + "les": 10877, + "ges": 10878, + "lesdot": 10879, + "gesdot": 10880, + "lesdoto": 10881, + "gesdoto": 10882, + "lesdotor": 10883, + "gesdotol": 10884, + "lap": 10885, + "gap": 10886, + "lne": 10887, + "gne": 10888, + "lnap": 10889, + "gnap": 10890, + "lEg": 10891, + "gEl": 10892, + "lsime": 10893, + "gsime": 10894, + "lsimg": 10895, + "gsiml": 10896, + "lgE": 10897, + "glE": 10898, + "lesges": 10899, + "gesles": 10900, + "els": 10901, + "egs": 10902, + "elsdot": 10903, + "egsdot": 10904, + "el": 10905, + "eg": 10906, + "siml": 10909, + "simg": 10910, + "simlE": 10911, + "simgE": 10912, + "LessLess": 10913, + "GreaterGreater": 10914, + "glj": 10916, + "gla": 10917, + "ltcc": 10918, + "gtcc": 10919, + "lescc": 10920, + "gescc": 10921, + "smt": 10922, + "lat": 10923, + "smte": 10924, + "late": 10925, + "bumpE": 10926, + "pre": 10927, + "sce": 10928, + "prE": 10931, + "scE": 10932, + "prnE": 10933, + "scnE": 10934, + "prap": 10935, + "scap": 10936, + "prnap": 10937, + "scnap": 10938, + "Pr": 10939, + "Sc": 10940, + "subdot": 10941, + "supdot": 10942, + "subplus": 10943, + "supplus": 10944, + "submult": 10945, + "supmult": 10946, + "subedot": 10947, + "supedot": 10948, + "subE": 10949, + "supE": 10950, + "subsim": 10951, + "supsim": 10952, + "subnE": 10955, + "supnE": 10956, + "csub": 10959, + "csup": 10960, + "csube": 10961, + "csupe": 10962, + "subsup": 10963, + "supsub": 10964, + "subsub": 10965, + "supsup": 10966, + "suphsub": 10967, + "supdsub": 10968, + "forkv": 10969, + "topfork": 10970, + "mlcp": 10971, + "Dashv": 10980, + "Vdashl": 10982, + "Barv": 10983, + "vBar": 10984, + "vBarv": 10985, + "Vbar": 10987, + "Not": 10988, + "bNot": 10989, + "rnmid": 10990, + "cirmid": 10991, + "midcir": 10992, + "topcir": 10993, + "nhpar": 10994, + "parsim": 10995, + "parsl": 11005, + "fflig": 64256, + "filig": 64257, + "fllig": 64258, + "ffilig": 64259, + "ffllig": 64260, + "Ascr": 119964, + "Cscr": 119966, + "Dscr": 119967, + "Gscr": 119970, + "Jscr": 119973, + "Kscr": 119974, + "Nscr": 119977, + "Oscr": 119978, + "Pscr": 119979, + "Qscr": 119980, + "Sscr": 119982, + "Tscr": 119983, + "Uscr": 119984, + "Vscr": 119985, + "Wscr": 119986, + "Xscr": 119987, + "Yscr": 119988, + "Zscr": 119989, + "ascr": 119990, + "bscr": 119991, + "cscr": 119992, + "dscr": 119993, + "fscr": 119995, + "hscr": 119997, + "iscr": 119998, + "jscr": 119999, + "kscr": 120000, + "lscr": 120001, + "mscr": 120002, + "nscr": 120003, + "pscr": 120005, + "qscr": 120006, + "rscr": 120007, + "sscr": 120008, + "tscr": 120009, + "uscr": 120010, + "vscr": 120011, + "wscr": 120012, + "xscr": 120013, + "yscr": 120014, + "zscr": 120015, + "Afr": 120068, + "Bfr": 120069, + "Dfr": 120071, + "Efr": 120072, + "Ffr": 120073, + "Gfr": 120074, + "Jfr": 120077, + "Kfr": 120078, + "Lfr": 120079, + "Mfr": 120080, + "Nfr": 120081, + "Ofr": 120082, + "Pfr": 120083, + "Qfr": 120084, + "Sfr": 120086, + "Tfr": 120087, + "Ufr": 120088, + "Vfr": 120089, + "Wfr": 120090, + "Xfr": 120091, + "Yfr": 120092, + "afr": 120094, + "bfr": 120095, + "cfr": 120096, + "dfr": 120097, + "efr": 120098, + "ffr": 120099, + "gfr": 120100, + "hfr": 120101, + "ifr": 120102, + "jfr": 120103, + "kfr": 120104, + "lfr": 120105, + "mfr": 120106, + "nfr": 120107, + "ofr": 120108, + "pfr": 120109, + "qfr": 120110, + "rfr": 120111, + "sfr": 120112, + "tfr": 120113, + "ufr": 120114, + "vfr": 120115, + "wfr": 120116, + "xfr": 120117, + "yfr": 120118, + "zfr": 120119, + "Aopf": 120120, + "Bopf": 120121, + "Dopf": 120123, + "Eopf": 120124, + "Fopf": 120125, + "Gopf": 120126, + "Iopf": 120128, + "Jopf": 120129, + "Kopf": 120130, + "Lopf": 120131, + "Mopf": 120132, + "Oopf": 120134, + "Sopf": 120138, + "Topf": 120139, + "Uopf": 120140, + "Vopf": 120141, + "Wopf": 120142, + "Xopf": 120143, + "Yopf": 120144, + "aopf": 120146, + "bopf": 120147, + "copf": 120148, + "dopf": 120149, + "eopf": 120150, + "fopf": 120151, + "gopf": 120152, + "hopf": 120153, + "iopf": 120154, + "jopf": 120155, + "kopf": 120156, + "lopf": 120157, + "mopf": 120158, + "nopf": 120159, + "oopf": 120160, + "popf": 120161, + "qopf": 120162, + "ropf": 120163, + "sopf": 120164, + "topf": 120165, + "uopf": 120166, + "vopf": 120167, + "wopf": 120168, + "xopf": 120169, + "yopf": 120170, + "zopf": 120171 +}; + +export default FromHTMLEntity; diff --git a/src/core/operations/FromHex.mjs b/src/core/operations/FromHex.mjs new file mode 100644 index 00000000..cd82d7df --- /dev/null +++ b/src/core/operations/FromHex.mjs @@ -0,0 +1,152 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {fromHex, FROM_HEX_DELIM_OPTIONS} from "../lib/Hex.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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.checks = [ + { + pattern: "^(?:[\\dA-F]{2})+$", + flags: "i", + args: ["None"] + }, + { + pattern: "^[\\dA-F]{2}(?: [\\dA-F]{2})*$", + flags: "i", + args: ["Space"] + }, + { + pattern: "^[\\dA-F]{2}(?:,[\\dA-F]{2})*$", + flags: "i", + args: ["Comma"] + }, + { + pattern: "^[\\dA-F]{2}(?:;[\\dA-F]{2})*$", + flags: "i", + args: ["Semi-colon"] + }, + { + pattern: "^[\\dA-F]{2}(?::[\\dA-F]{2})*$", + flags: "i", + args: ["Colon"] + }, + { + pattern: "^[\\dA-F]{2}(?:\\n[\\dA-F]{2})*$", + flags: "i", + args: ["Line feed"] + }, + { + pattern: "^[\\dA-F]{2}(?:\\r\\n[\\dA-F]{2})*$", + flags: "i", + args: ["CRLF"] + }, + { + pattern: "^(?:0x[\\dA-F]{2})+$", + flags: "i", + args: ["0x"] + }, + { + pattern: "^0x[\\dA-F]{2}(?:,0x[\\dA-F]{2})*$", + flags: "i", + args: ["0x with comma"] + }, + { + pattern: "^(?:\\\\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..dc6c06f1 --- /dev/null +++ b/src/core/operations/FromHexContent.mjs @@ -0,0 +1,74 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {fromHex} from "../lib/Hex.mjs"; + +/** + * 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 = []; + this.checks = [ + { + pattern: "\\|([\\da-f]{2} ?)+\\|", + flags: "i", + 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 53% rename from src/core/operations/Hexdump.js rename to src/core/operations/FromHexdump.mjs index 9bb16df9..e8c25441 --- a/src/core/operations/Hexdump.js +++ b/src/core/operations/FromHexdump.mjs @@ -1,105 +1,69 @@ -/* 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.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + + +/** + * 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.checks = [ + { + pattern: "^(?:(?:[\\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) { - var length = args[0] || Hexdump.WIDTH; - var upperCase = args[1]; - var includeFinalLength = args[2]; - - var output = "", padding = 2; - for (var i = 0; i < input.length; i += length) { - var buff = input.slice(i, i+length); - var hexa = ""; - for (var j = 0; j < buff.length; j++) { - hexa += Utils.hex(buff[j], padding) + " "; - } - - var 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) { - var 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, " ")); - for (var i = 0; i < line.length; i++) { + line = fromHex(block[1].replace(/-/g, " ")); + for (let i = 0; i < line.length; i++) { output.push(line[i]); } } // Is this a CyberChef hexdump or is it from a different tool? - var width = input.indexOf("\n"); - var w = (width - 13) / 4; + const width = input.indexOf("\n"); + 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 (isWorkerEnvironment()) self.setOption("attemptHighlight", false); } return output; - }, - + } /** - * Highlight to hexdump + * Highlight From Hexdump * * @param {Object[]} pos * @param {number} pos[].start @@ -107,73 +71,12 @@ const Hexdump = { * @param {Object[]} args * @returns {Object[]} pos */ - highlightTo: function(pos, args) { - // Calculate overall selection - var w = args[0] || 16, - width = 14 + (w*4), - line = Math.floor(pos[0].start / w), - offset = pos[0].start % w, - start = 0, - end = 0; + highlight(pos, args) { + const w = args[0] || 16; + const width = 14 + (w*4); - 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 - var startLineNum = Math.floor(pos[0].start / width); - var 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 - var len = pos.length, lineNum = 0; - start = 0; - end = 0; - for (var 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 from hexdump - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightFrom: function(pos, args) { - var w = args[0] || 16; - var width = 14 + (w*4); - - var line = Math.floor(pos[0].start / width); - var offset = pos[0].start % width; + 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; @@ -195,8 +98,69 @@ const Hexdump = { } 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 + 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; -export default Hexdump; + 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; + } + +} + +export default FromHexdump; diff --git a/src/core/operations/FromMessagePack.mjs b/src/core/operations/FromMessagePack.mjs new file mode 100644 index 00000000..a557ebbe --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; +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/FromModhex.mjs b/src/core/operations/FromModhex.mjs new file mode 100644 index 00000000..029d95d8 --- /dev/null +++ b/src/core/operations/FromModhex.mjs @@ -0,0 +1,84 @@ +/** + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { FROM_MODHEX_DELIM_OPTIONS, fromModhex } from "../lib/Modhex.mjs"; + +/** + * From Modhex operation + */ +class FromModhex extends Operation { + + /** + * FromModhex constructor + */ + constructor() { + super(); + + this.name = "From Modhex"; + this.module = "Default"; + this.description = "Converts a modhex byte string back into its raw value."; + this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: FROM_MODHEX_DELIM_OPTIONS + } + ]; + this.checks = [ + { + pattern: "^(?:[cbdefghijklnrtuv]{2})+$", + flags: "i", + args: ["None"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?: [cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Space"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:,[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Comma"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:;[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Semi-colon"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?::[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Colon"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:\\n[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Line feed"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:\\r\\n[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["CRLF"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = args[0] || "Auto"; + return fromModhex(input, delim, 2); + } +} + +export default FromModhex; diff --git a/src/core/operations/FromMorseCode.mjs b/src/core/operations/FromMorseCode.mjs new file mode 100644 index 00000000..b0aa4ef2 --- /dev/null +++ b/src/core/operations/FromMorseCode.mjs @@ -0,0 +1,154 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * 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.checks = [ + { + pattern: "(?:^[-. \\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..7060cdfb --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * 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.checks = [ + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?: (?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Space"] + }, + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:,(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Comma"] + }, + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:;(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Semi-colon"] + }, + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?::(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Colon"] + }, + { + pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Line feed"] + }, + { + pattern: "^(?:[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..2217ee3b --- /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.mjs"; +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..7dce45b6 --- /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.mjs"; + +/** + * 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.

e.g. The quoted-printable encoded string hello=20world becomes hello world"; + this.infoURL = "https://wikipedia.org/wiki/Quoted-printable"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + this.checks = [ + { + pattern: "^[\\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..50d8539e --- /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.mjs"; +import moment from "moment-timezone"; +import {UNITS} from "../lib/DateTime.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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.checks = [ + { + pattern: "^1?\\d{9}$", + flags: "", + args: ["Seconds (s)"] + }, + { + pattern: "^1?\\d{12}$", + flags: "", + args: ["Milliseconds (ms)"] + }, + { + pattern: "^1?\\d{15}$", + flags: "", + args: ["Microseconds (μs)"] + }, + { + pattern: "^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/FuzzyMatch.mjs b/src/core/operations/FuzzyMatch.mjs new file mode 100644 index 00000000..c35dd0ab --- /dev/null +++ b/src/core/operations/FuzzyMatch.mjs @@ -0,0 +1,121 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {fuzzyMatch, calcMatchRanges, DEFAULT_WEIGHTS} from "../lib/FuzzyMatch.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Fuzzy Match operation + */ +class FuzzyMatch extends Operation { + + /** + * FuzzyMatch constructor + */ + constructor() { + super(); + + this.name = "Fuzzy Match"; + this.module = "Default"; + this.description = "Conducts a fuzzy search to find a pattern within the input based on weighted criteria.

e.g. A search for dpan will match on Don't Panic"; + this.infoURL = "https://wikipedia.org/wiki/Fuzzy_matching_(computer-assisted_translation)"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "Search", + type: "binaryString", + value: "" + }, + { + name: "Sequential bonus", + type: "number", + value: DEFAULT_WEIGHTS.sequentialBonus, + hint: "Bonus for adjacent matches" + }, + { + name: "Separator bonus", + type: "number", + value: DEFAULT_WEIGHTS.separatorBonus, + hint: "Bonus if match occurs after a separator" + }, + { + name: "Camel bonus", + type: "number", + value: DEFAULT_WEIGHTS.camelBonus, + hint: "Bonus if match is uppercase and previous is lower" + }, + { + name: "First letter bonus", + type: "number", + value: DEFAULT_WEIGHTS.firstLetterBonus, + hint: "Bonus if the first letter is matched" + }, + { + name: "Leading letter penalty", + type: "number", + value: DEFAULT_WEIGHTS.leadingLetterPenalty, + hint: "Penalty applied for every letter in the input before the first match" + }, + { + name: "Max leading letter penalty", + type: "number", + value: DEFAULT_WEIGHTS.maxLeadingLetterPenalty, + hint: "Maxiumum penalty for leading letters" + }, + { + name: "Unmatched letter penalty", + type: "number", + value: DEFAULT_WEIGHTS.unmatchedLetterPenalty + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const searchStr = args[0]; + const weights = { + sequentialBonus: args[1], + separatorBonus: args[2], + camelBonus: args[3], + firstLetterBonus: args[4], + leadingLetterPenalty: args[5], + maxLeadingLetterPenalty: args[6], + unmatchedLetterPenalty: args[7] + }; + const matches = fuzzyMatch(searchStr, input, true, weights); + + if (!matches) { + return "No matches."; + } + + let result = "", pos = 0, hlClass = "hl1"; + matches.forEach(([matches, score, idxs]) => { + const matchRanges = calcMatchRanges(idxs); + + matchRanges.forEach(([start, length], i) => { + result += Utils.escapeHtml(input.slice(pos, start)); + if (i === 0) result += ``; + pos = start + length; + result += `${Utils.escapeHtml(input.slice(start, pos))}`; + }); + result += ""; + hlClass = hlClass === "hl1" ? "hl2" : "hl1"; + }); + + result += Utils.escapeHtml(input.slice(pos, input.length)); + + return result; + } + +} + +export default FuzzyMatch; diff --git a/src/core/operations/GOSTDecrypt.mjs b/src/core/operations/GOSTDecrypt.mjs new file mode 100644 index 00000000..2e7467da --- /dev/null +++ b/src/core/operations/GOSTDecrypt.mjs @@ -0,0 +1,151 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Decrypt operation + */ +class GOSTDecrypt extends Operation { + + /** + * GOSTDecrypt constructor + */ + constructor() { + super(); + + this.name = "GOST Decrypt"; + this.module = "Ciphers"; + this.description = "The GOST block cipher (Magma), defined in the standard GOST 28147-89 (RFC 5830), is a Soviet and Russian government standard symmetric key block cipher with a block size of 64 bits. The original standard, published in 1989, did not give the cipher any name, but the most recent revision of the standard, GOST R 34.12-2015 (RFC 7801, RFC 8891), specifies that it may be referred to as Magma. The GOST hash function is based on this cipher. The new standard also specifies a new 128-bit block cipher called Kuznyechik.

Developed in the 1970s, the standard had been marked 'Top Secret' and then downgraded to 'Secret' in 1990. Shortly after the dissolution of the USSR, it was declassified and it was released to the public in 1994. GOST 28147 was a Soviet alternative to the United States standard algorithm, DES. Thus, the two are very similar in structure."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_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: "Input type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Output type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Block mode", + type: "option", + value: ["ECB", "CFB", "OFB", "CTR", "CBC"] + }, + { + name: "Key meshing mode", + type: "option", + value: ["NO", "CP"] + }, + { + name: "Padding", + type: "option", + value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "ES", + sBox: sBoxVal, + block: blockMode, + keyMeshing: keyMeshing, + padding: padding + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.decrypt(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTDecrypt; diff --git a/src/core/operations/GOSTEncrypt.mjs b/src/core/operations/GOSTEncrypt.mjs new file mode 100644 index 00000000..82bdaeda --- /dev/null +++ b/src/core/operations/GOSTEncrypt.mjs @@ -0,0 +1,151 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Encrypt operation + */ +class GOSTEncrypt extends Operation { + + /** + * GOSTEncrypt constructor + */ + constructor() { + super(); + + this.name = "GOST Encrypt"; + this.module = "Ciphers"; + this.description = "The GOST block cipher (Magma), defined in the standard GOST 28147-89 (RFC 5830), is a Soviet and Russian government standard symmetric key block cipher with a block size of 64 bits. The original standard, published in 1989, did not give the cipher any name, but the most recent revision of the standard, GOST R 34.12-2015 (RFC 7801, RFC 8891), specifies that it may be referred to as Magma. The GOST hash function is based on this cipher. The new standard also specifies a new 128-bit block cipher called Kuznyechik.

Developed in the 1970s, the standard had been marked 'Top Secret' and then downgraded to 'Secret' in 1990. Shortly after the dissolution of the USSR, it was declassified and it was released to the public in 1994. GOST 28147 was a Soviet alternative to the United States standard algorithm, DES. Thus, the two are very similar in structure."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_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: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Output type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Block mode", + type: "option", + value: ["ECB", "CFB", "OFB", "CTR", "CBC"] + }, + { + name: "Key meshing mode", + type: "option", + value: ["NO", "CP"] + }, + { + name: "Padding", + type: "option", + value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "ES", + sBox: sBoxVal, + block: blockMode, + keyMeshing: keyMeshing, + padding: padding + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.encrypt(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTEncrypt; diff --git a/src/core/operations/GOSTHash.mjs b/src/core/operations/GOSTHash.mjs new file mode 100644 index 00000000..5c8cc6f7 --- /dev/null +++ b/src/core/operations/GOSTHash.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import GostDigest from "../vendor/gost/gostDigest.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; + +/** + * GOST hash operation + */ +class GOSTHash extends Operation { + + /** + * GOSTHash constructor + */ + constructor() { + super(); + + this.name = "GOST Hash"; + this.module = "Hashing"; + this.description = "The GOST hash function, defined in the standards GOST R 34.11-94 and GOST 34.311-95 is a 256-bit cryptographic hash function. It was initially defined in the Russian national standard GOST R 34.11-94 Information Technology – Cryptographic Information Security – Hash Function. The equivalent standard used by other member-states of the CIS is GOST 34.311-95.

This function must not be confused with a different Streebog hash function, which is defined in the new revision of the standard GOST R 34.11-2012.

The GOST hash function is based on the GOST block cipher."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(hash_function)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1994)", + off: [1], + on: [2] + }, + { + name: "GOST R 34.11 (Streebog, 2012)", + on: [1], + off: [2] + } + ] + }, + { + name: "Digest length", + type: "option", + value: ["256", "512"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [version, length, sBox] = args; + + const versionNum = version === "GOST 28147 (1994)" ? 1994 : 2012; + const algorithm = { + name: versionNum === 1994 ? "GOST 28147" : "GOST R 34.10", + version: versionNum, + mode: "HASH" + }; + + if (versionNum === 1994) { + algorithm.sBox = sBox; + } else { + algorithm.length = parseInt(length, 10); + } + + try { + const gostDigest = new GostDigest(algorithm); + + return toHexFast(gostDigest.digest(input)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTHash; diff --git a/src/core/operations/GOSTKeyUnwrap.mjs b/src/core/operations/GOSTKeyUnwrap.mjs new file mode 100644 index 00000000..52b0a9ec --- /dev/null +++ b/src/core/operations/GOSTKeyUnwrap.mjs @@ -0,0 +1,142 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Key Unwrap operation + */ +class GOSTKeyUnwrap extends Operation { + + /** + * GOSTKeyUnwrap constructor + */ + constructor() { + super(); + + this.name = "GOST Key Unwrap"; + this.module = "Ciphers"; + this.description = "A decryptor for keys wrapped using one of the GOST block ciphers."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "User Key Material", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Output type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Key wrapping", + type: "option", + value: ["NO", "CP", "SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "KW", + sBox: sBoxVal, + keyWrapping: keyWrapping + }; + + try { + const Hex = CryptoGost.coding.Hex; + algorithm.ukm = Hex.decode(ukm); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.unwrapKey(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + if (err.toString().includes("Invalid typed array length")) { + throw new OperationError("Incorrect input length. Must be a multiple of the block size."); + } + throw new OperationError(err); + } + } + +} + +export default GOSTKeyUnwrap; diff --git a/src/core/operations/GOSTKeyWrap.mjs b/src/core/operations/GOSTKeyWrap.mjs new file mode 100644 index 00000000..443c9ba0 --- /dev/null +++ b/src/core/operations/GOSTKeyWrap.mjs @@ -0,0 +1,142 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Key Wrap operation + */ +class GOSTKeyWrap extends Operation { + + /** + * GOSTKeyWrap constructor + */ + constructor() { + super(); + + this.name = "GOST Key Wrap"; + this.module = "Ciphers"; + this.description = "A key wrapping algorithm for protecting keys in untrusted storage using one of the GOST block cipers."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "User Key Material", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Output type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Key wrapping", + type: "option", + value: ["NO", "CP", "SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "KW", + sBox: sBoxVal, + keyWrapping: keyWrapping + }; + + try { + const Hex = CryptoGost.coding.Hex; + algorithm.ukm = Hex.decode(ukm); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.wrapKey(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + if (err.toString().includes("Invalid typed array length")) { + throw new OperationError("Incorrect input length. Must be a multiple of the block size."); + } + throw new OperationError(err); + } + } + +} + +export default GOSTKeyWrap; diff --git a/src/core/operations/GOSTSign.mjs b/src/core/operations/GOSTSign.mjs new file mode 100644 index 00000000..2af850e7 --- /dev/null +++ b/src/core/operations/GOSTSign.mjs @@ -0,0 +1,142 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Sign operation + */ +class GOSTSign extends Operation { + + /** + * GOSTSign constructor + */ + constructor() { + super(); + + this.name = "GOST Sign"; + this.module = "Ciphers"; + this.description = "Sign a plaintext message using one of the GOST block ciphers."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_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: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Output type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "MAC length", + type: "number", + value: 32, + min: 8, + max: 64, + step: 8 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, sBox, macLength] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "MAC", + sBox: sBoxVal, + macLength: macLength + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.sign(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTSign; diff --git a/src/core/operations/GOSTVerify.mjs b/src/core/operations/GOSTVerify.mjs new file mode 100644 index 00000000..2fa362fb --- /dev/null +++ b/src/core/operations/GOSTVerify.mjs @@ -0,0 +1,136 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Verify operation + */ +class GOSTVerify extends Operation { + + /** + * GOSTVerify constructor + */ + constructor() { + super(); + + this.name = "GOST Verify"; + this.module = "Ciphers"; + this.description = "Verify the signature of a plaintext message using one of the GOST block ciphers. Enter the signature in the MAC field."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_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: "MAC", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1989)", + on: [5] + }, + { + name: "GOST R 34.12 (Magma, 2015)", + off: [5] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + off: [5] + } + ] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, macObj, inputType, version, sBox] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + const mac = toHexFast(Utils.convertToByteArray(macObj.string, macObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + let blockLength, versionNum; + switch (version) { + case "GOST 28147 (1989)": + versionNum = 1989; + blockLength = 64; + break; + case "GOST R 34.12 (Magma, 2015)": + versionNum = 2015; + blockLength = 64; + break; + case "GOST R 34.12 (Kuznyechik, 2015)": + versionNum = 2015; + blockLength = 128; + break; + default: + throw new OperationError(`Unknown algorithm version: ${version}`); + } + + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "MAC", + sBox: sBoxVal, + macLength: mac.length * 4 + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = cipher.verify(Hex.decode(key), Hex.decode(mac), Hex.decode(input)); + + return out ? "The signature matches" : "The signature does not match"; + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTVerify; diff --git a/src/core/operations/GenerateAllHashes.mjs b/src/core/operations/GenerateAllHashes.mjs new file mode 100644 index 00000000..06b5f7d9 --- /dev/null +++ b/src/core/operations/GenerateAllHashes.mjs @@ -0,0 +1,203 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author john19696 [john19696@protonmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import MD2 from "./MD2.mjs"; +import MD4 from "./MD4.mjs"; +import MD5 from "./MD5.mjs"; +import MD6 from "./MD6.mjs"; +import SHA0 from "./SHA0.mjs"; +import SHA1 from "./SHA1.mjs"; +import SHA2 from "./SHA2.mjs"; +import SHA3 from "./SHA3.mjs"; +import Keccak from "./Keccak.mjs"; +import Shake from "./Shake.mjs"; +import RIPEMD from "./RIPEMD.mjs"; +import HAS160 from "./HAS160.mjs"; +import Whirlpool from "./Whirlpool.mjs"; +import SSDEEP from "./SSDEEP.mjs"; +import CTPH from "./CTPH.mjs"; +import Fletcher8Checksum from "./Fletcher8Checksum.mjs"; +import Fletcher16Checksum from "./Fletcher16Checksum.mjs"; +import Fletcher32Checksum from "./Fletcher32Checksum.mjs"; +import Fletcher64Checksum from "./Fletcher64Checksum.mjs"; +import Adler32Checksum from "./Adler32Checksum.mjs"; +import CRCChecksum from "./CRCChecksum.mjs"; +import BLAKE2b from "./BLAKE2b.mjs"; +import BLAKE2s from "./BLAKE2s.mjs"; +import Streebog from "./Streebog.mjs"; +import GOSTHash from "./GOSTHash.mjs"; +import LMHash from "./LMHash.mjs"; +import NTHash from "./NTHash.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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 = [ + { + name: "Length (bits)", + type: "option", + value: [ + "All", "128", "160", "224", "256", "320", "384", "512" + ] + }, + { + name: "Include names", + type: "boolean", + value: true + }, + ]; + this.hashes = [ + {name: "MD2", algo: (new MD2()), inputType: "arrayBuffer", params: []}, + {name: "MD4", algo: (new MD4()), inputType: "arrayBuffer", params: []}, + {name: "MD5", algo: (new MD5()), inputType: "arrayBuffer", params: []}, + {name: "MD6", algo: (new MD6()), inputType: "str", params: []}, + {name: "SHA0", algo: (new SHA0()), inputType: "arrayBuffer", params: []}, + {name: "SHA1", algo: (new SHA1()), inputType: "arrayBuffer", params: []}, + {name: "SHA2 224", algo: (new SHA2()), inputType: "arrayBuffer", params: ["224"]}, + {name: "SHA2 256", algo: (new SHA2()), inputType: "arrayBuffer", params: ["256"]}, + {name: "SHA2 384", algo: (new SHA2()), inputType: "arrayBuffer", params: ["384"]}, + {name: "SHA2 512", algo: (new SHA2()), inputType: "arrayBuffer", params: ["512"]}, + {name: "SHA3 224", algo: (new SHA3()), inputType: "arrayBuffer", params: ["224"]}, + {name: "SHA3 256", algo: (new SHA3()), inputType: "arrayBuffer", params: ["256"]}, + {name: "SHA3 384", algo: (new SHA3()), inputType: "arrayBuffer", params: ["384"]}, + {name: "SHA3 512", algo: (new SHA3()), inputType: "arrayBuffer", params: ["512"]}, + {name: "Keccak 224", algo: (new Keccak()), inputType: "arrayBuffer", params: ["224"]}, + {name: "Keccak 256", algo: (new Keccak()), inputType: "arrayBuffer", params: ["256"]}, + {name: "Keccak 384", algo: (new Keccak()), inputType: "arrayBuffer", params: ["384"]}, + {name: "Keccak 512", algo: (new Keccak()), inputType: "arrayBuffer", params: ["512"]}, + {name: "Shake 128", algo: (new Shake()), inputType: "arrayBuffer", params: ["128", 256]}, + {name: "Shake 256", algo: (new Shake()), inputType: "arrayBuffer", params: ["256", 512]}, + {name: "RIPEMD-128", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["128"]}, + {name: "RIPEMD-160", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["160"]}, + {name: "RIPEMD-256", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["256"]}, + {name: "RIPEMD-320", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["320"]}, + {name: "HAS-160", algo: (new HAS160()), inputType: "arrayBuffer", params: []}, + {name: "Whirlpool-0", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool-0"]}, + {name: "Whirlpool-T", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool-T"]}, + {name: "Whirlpool", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool"]}, + {name: "BLAKE2b-128", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["128", "Hex", {string: "", option: "UTF8"}]}, + {name: "BLAKE2b-160", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["160", "Hex", {string: "", option: "UTF8"}]}, + {name: "BLAKE2b-256", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["256", "Hex", {string: "", option: "UTF8"}]}, + {name: "BLAKE2b-384", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["384", "Hex", {string: "", option: "UTF8"}]}, + {name: "BLAKE2b-512", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["512", "Hex", {string: "", option: "UTF8"}]}, + {name: "BLAKE2s-128", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["128", "Hex", {string: "", option: "UTF8"}]}, + {name: "BLAKE2s-160", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["160", "Hex", {string: "", option: "UTF8"}]}, + {name: "BLAKE2s-256", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["256", "Hex", {string: "", option: "UTF8"}]}, + {name: "Streebog-256", algo: (new Streebog), inputType: "arrayBuffer", params: ["256"]}, + {name: "Streebog-512", algo: (new Streebog), inputType: "arrayBuffer", params: ["512"]}, + {name: "GOST", algo: (new GOSTHash), inputType: "arrayBuffer", params: ["GOST 28147 (1994)", "256", "D-A"]}, + {name: "LM Hash", algo: (new LMHash), inputType: "str", params: []}, + {name: "NT Hash", algo: (new NTHash), inputType: "str", params: []}, + {name: "SSDEEP", algo: (new SSDEEP()), inputType: "str"}, + {name: "CTPH", algo: (new CTPH()), inputType: "str"} + ]; + this.checksums = [ + {name: "Fletcher-8", algo: (new Fletcher8Checksum), inputType: "byteArray", params: []}, + {name: "Fletcher-16", algo: (new Fletcher16Checksum), inputType: "byteArray", params: []}, + {name: "Fletcher-32", algo: (new Fletcher32Checksum), inputType: "byteArray", params: []}, + {name: "Fletcher-64", algo: (new Fletcher64Checksum), inputType: "byteArray", params: []}, + {name: "Adler-32", algo: (new Adler32Checksum), inputType: "byteArray", params: []}, + {name: "CRC-8", algo: (new CRCChecksum), inputType: "arrayBuffer", params: ["CRC-8"]}, + {name: "CRC-16", algo: (new CRCChecksum), inputType: "arrayBuffer", params: ["CRC-16"]}, + {name: "CRC-32", algo: (new CRCChecksum), inputType: "arrayBuffer", params: ["CRC-32"]} + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [length, includeNames] = args; + this.inputArrayBuffer = input; + this.inputStr = Utils.arrayBufferToStr(input, false); + this.inputByteArray = new Uint8Array(input); + + let digest, output = ""; + // iterate over each of the hashes + this.hashes.forEach(hash => { + digest = this.executeAlgo(hash.algo, hash.inputType, hash.params || []); + output += this.formatDigest(digest, length, includeNames, hash.name); + }); + + if (length === "All") { + output += "\nChecksums:\n"; + this.checksums.forEach(checksum => { + digest = this.executeAlgo(checksum.algo, checksum.inputType, checksum.params || []); + output += this.formatDigest(digest, length, includeNames, checksum.name); + }); + } + + return output; + } + + /** + * Executes a hash or checksum algorithm + * + * @param {Function} algo - The hash or checksum algorithm + * @param {string} inputType + * @param {Object[]} [params=[]] + * @returns {string} + */ + executeAlgo(algo, inputType, params=[]) { + let digest = null; + switch (inputType) { + case "arrayBuffer": + digest = algo.run(this.inputArrayBuffer, params); + break; + case "str": + digest = algo.run(this.inputStr, params); + break; + case "byteArray": + digest = algo.run(this.inputByteArray, params); + break; + default: + throw new OperationError("Unknown hash input type: " + inputType); + } + + return digest; + } + + /** + * Formats the digest depending on user-specified arguments + * @param {string} digest + * @param {string} length + * @param {boolean} includeNames + * @param {string} name + * @returns {string} + */ + formatDigest(digest, length, includeNames, name) { + if (length !== "All" && (digest.length * 4) !== parseInt(length, 10)) + return ""; + + if (!includeNames) + return digest + "\n"; + + return `${name}:${" ".repeat(13-name.length)}${digest}\n`; + } + +} + +export default GenerateAllHashes; diff --git a/src/core/operations/GenerateDeBruijnSequence.mjs b/src/core/operations/GenerateDeBruijnSequence.mjs new file mode 100644 index 00000000..f28d421f --- /dev/null +++ b/src/core/operations/GenerateDeBruijnSequence.mjs @@ -0,0 +1,85 @@ +/** + * @author gchq77703 [gchq77703@gchq.gov.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Generate De Bruijn Sequence operation + */ +class GenerateDeBruijnSequence extends Operation { + + /** + * GenerateDeBruijnSequence constructor + */ + constructor() { + super(); + + this.name = "Generate De Bruijn Sequence"; + this.module = "Default"; + this.description = "Generates rolling keycode combinations given a certain alphabet size and key length."; + this.infoURL = "https://wikipedia.org/wiki/De_Bruijn_sequence"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Alphabet size (k)", + type: "number", + value: 2 + }, + { + name: "Key length (n)", + type: "number", + value: 3 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [k, n] = args; + + if (k < 2 || k > 9) { + throw new OperationError("Invalid alphabet size, required to be between 2 and 9 (inclusive)."); + } + + if (n < 2) { + throw new OperationError("Invalid key length, required to be at least 2."); + } + + if (Math.pow(k, n) > 50000) { + throw new OperationError("Too many permutations, please reduce k^n to under 50,000."); + } + + const a = new Array(k * n).fill(0); + const sequence = []; + + (function db(t = 1, p = 1) { + if (t > n) { + if (n % p !== 0) return; + for (let j = 1; j <= p; j++) { + sequence.push(a[j]); + } + return; + } + + a[t] = a[t - p]; + db(t + 1, p); + for (let j = a[t - p] + 1; j < k; j++) { + a[t] = j; + db(t + 1, t); + } + })(); + + return sequence.join(""); + } +} + +export default GenerateDeBruijnSequence; diff --git a/src/core/operations/GenerateECDSAKeyPair.mjs b/src/core/operations/GenerateECDSAKeyPair.mjs new file mode 100644 index 00000000..14714a02 --- /dev/null +++ b/src/core/operations/GenerateECDSAKeyPair.mjs @@ -0,0 +1,102 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { cryptNotice } from "../lib/Crypt.mjs"; +import r from "jsrsasign"; + +/** + * Generate ECDSA Key Pair operation + */ +class GenerateECDSAKeyPair extends Operation { + + /** + * GenerateECDSAKeyPair constructor + */ + constructor() { + super(); + + this.name = "Generate ECDSA Key Pair"; + this.module = "Ciphers"; + this.description = `Generate an ECDSA key pair with a given Curve.

${cryptNotice}`; + this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Elliptic Curve", + type: "option", + value: [ + "P-256", + "P-384", + "P-521" + ] + }, + { + name: "Output Format", + type: "option", + value: [ + "PEM", + "DER", + "JWK" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [curveName, outputFormat] = args; + + return new Promise((resolve, reject) => { + let internalCurveName; + switch (curveName) { + case "P-256": + internalCurveName = "secp256r1"; + break; + case "P-384": + internalCurveName = "secp384r1"; + break; + case "P-521": + internalCurveName = "secp521r1"; + break; + } + const keyPair = r.KEYUTIL.generateKeypair("EC", internalCurveName); + + let pubKey; + let privKey; + let result; + switch (outputFormat) { + case "PEM": + pubKey = r.KEYUTIL.getPEM(keyPair.pubKeyObj).replace(/\r/g, ""); + privKey = r.KEYUTIL.getPEM(keyPair.prvKeyObj, "PKCS8PRV").replace(/\r/g, ""); + result = pubKey + "\n" + privKey; + break; + case "DER": + result = keyPair.prvKeyObj.prvKeyHex; + break; + case "JWK": + pubKey = r.KEYUTIL.getJWKFromKey(keyPair.pubKeyObj); + pubKey.key_ops = ["verify"]; // eslint-disable-line camelcase + pubKey.kid = "PublicKey"; + privKey = r.KEYUTIL.getJWKFromKey(keyPair.prvKeyObj); + privKey.key_ops = ["sign"]; // eslint-disable-line camelcase + privKey.kid = "PrivateKey"; + result = JSON.stringify({keys: [privKey, pubKey]}, null, 4); + break; + } + + resolve(result); + }); + } + +} + +export default GenerateECDSAKeyPair; diff --git a/src/core/operations/GenerateHOTP.mjs b/src/core/operations/GenerateHOTP.mjs new file mode 100644 index 00000000..75f5329f --- /dev/null +++ b/src/core/operations/GenerateHOTP.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import * as OTPAuth from "otpauth"; + +/** + * Generate HOTP operation + */ +class GenerateHOTP extends Operation { + /** + * + */ + 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 (OAUTH), 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Name", + "type": "string", + "value": "" + }, + { + "name": "Code length", + "type": "number", + "value": 6 + }, + { + "name": "Counter", + "type": "number", + "value": 0 + } + ]; + } + + /** + * + */ + run(input, args) { + const secretStr = new TextDecoder("utf-8").decode(input).trim(); + const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : ""; + + const hotp = new OTPAuth.HOTP({ + issuer: "", + label: args[0], + algorithm: "SHA1", + digits: args[1], + counter: args[2], + secret: OTPAuth.Secret.fromBase32(secret) + }); + + const uri = hotp.toString(); + const code = hotp.generate(); + + return `URI: ${uri}\n\nPassword: ${code}`; + } +} + +export default GenerateHOTP; diff --git a/src/core/operations/GenerateImage.mjs b/src/core/operations/GenerateImage.mjs new file mode 100644 index 00000000..118e3a32 --- /dev/null +++ b/src/core/operations/GenerateImage.mjs @@ -0,0 +1,184 @@ +/** + * @author pointhi [thomas.pointhuber@gmx.at] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {isImage} from "../lib/FileType.mjs"; +import {toBase64} from "../lib/Base64.mjs"; +import {isWorkerEnvironment} from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Generate Image operation + */ +class GenerateImage extends Operation { + + /** + * GenerateImage constructor + */ + constructor() { + super(); + + this.name = "Generate Image"; + this.module = "Image"; + this.description = "Generates an image using the input as pixel values."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + "name": "Mode", + "type": "option", + "value": ["Greyscale", "RG", "RGB", "RGBA", "Bits"] + }, + { + "name": "Pixel Scale Factor", + "type": "number", + "value": 8, + }, + { + "name": "Pixels per row", + "type": "number", + "value": 64, + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + const [mode, scale, width] = args; + input = new Uint8Array(input); + + if (scale <= 0) { + throw new OperationError("Pixel Scale Factor needs to be > 0"); + } + + if (width <= 0) { + throw new OperationError("Pixels per Row needs to be > 0"); + } + + const bytePerPixelMap = { + "Greyscale": 1, + "RG": 2, + "RGB": 3, + "RGBA": 4, + "Bits": 1/8, + }; + + const bytesPerPixel = bytePerPixelMap[mode]; + + if (bytesPerPixel > 0 && input.length % bytesPerPixel !== 0) { + throw new OperationError(`Number of bytes is not a divisor of ${bytesPerPixel}`); + } + + const height = Math.ceil(input.length / bytesPerPixel / width); + const image = await new Jimp(width, height, (err, image) => {}); + + if (isWorkerEnvironment()) + self.sendStatusMessage("Generating image from data..."); + + if (mode === "Bits") { + let index = 0; + for (let j = 0; j < input.length; j++) { + const curByte = Utils.bin(input[j]); + for (let k = 0; k < 8; k++, index++) { + const x = index % width; + const y = Math.floor(index / width); + + const value = curByte[k] === "0" ? 0xFF : 0x00; + const pixel = Jimp.rgbaToInt(value, value, value, 0xFF); + image.setPixelColor(pixel, x, y); + } + } + } else { + let i = 0; + while (i < input.length) { + const index = i / bytesPerPixel; + const x = index % width; + const y = Math.floor(index / width); + + let red = 0x00; + let green = 0x00; + let blue = 0x00; + let alpha = 0xFF; + + switch (mode) { + case "Greyscale": + red = green = blue = input[i++]; + break; + + case "RG": + red = input[i++]; + green = input[i++]; + break; + + case "RGB": + red = input[i++]; + green = input[i++]; + blue = input[i++]; + break; + + case "RGBA": + red = input[i++]; + green = input[i++]; + blue = input[i++]; + alpha = input[i++]; + break; + + default: + throw new OperationError(`Unsupported Mode: (${mode})`); + } + + try { + const pixel = Jimp.rgbaToInt(red, green, blue, alpha); + image.setPixelColor(pixel, x, y); + } catch (err) { + throw new OperationError(`Error while generating image from pixel values. (${err})`); + } + } + } + + if (scale !== 1) { + if (isWorkerEnvironment()) + self.sendStatusMessage("Scaling image..."); + + image.scaleToFit(width*scale, height*scale, Jimp.RESIZE_NEAREST_NEIGHBOR); + } + + try { + const imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error generating image. (${err})`); + } + } + + /** + * Displays the generated image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default GenerateImage; diff --git a/src/core/operations/GenerateLoremIpsum.mjs b/src/core/operations/GenerateLoremIpsum.mjs new file mode 100644 index 00000000..7bc636ac --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum.mjs"; + +/** + * 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..a26b5cc8 --- /dev/null +++ b/src/core/operations/GeneratePGPKeyPair.mjs @@ -0,0 +1,122 @@ +/** + * @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.mjs"; +import kbpgp from "kbpgp"; +import { getSubkeySize, ASP } from "../lib/PGP.mjs"; +import { cryptNotice } from "../lib/Crypt.mjs"; +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.

${cryptNotice}`; + 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", "ECC-521"] + }, + { + "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} + */ + async run(input, args) { + let [keyType, keySize] = args[0].split("-"); + const password = args[1], + name = args[2], + email = args[3]; + let userIdentifier = ""; + + keyType = keyType.toLowerCase(); + keySize = parseInt(keySize, 10); + + 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..d3e1ee3b --- /dev/null +++ b/src/core/operations/GenerateQRCode.mjs @@ -0,0 +1,94 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { generateQrCode } from "../lib/QRCode.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + "name": "Image Format", + "type": "option", + "value": ["PNG", "SVG", "EPS", "PDF"] + }, + { + "name": "Module size (px)", + "type": "number", + "value": 5, + "min": 1 + }, + { + "name": "Margin (num modules)", + "type": "number", + "value": 4, + "min": 0 + }, + { + "name": "Error correction", + "type": "option", + "value": ["Low", "Medium", "Quartile", "High"], + "defaultIndex": 1 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const [format, size, margin, errorCorrection] = args; + + return generateQrCode(input, format, size, margin, errorCorrection); + } + + /** + * Displays the QR image using HTML for web apps + * + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data, args) { + if (!data.byteLength && !data.length) return ""; + const dataArray = new Uint8Array(data), + [format] = args; + if (format === "PNG") { + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + + return Utils.arrayBufferToStr(data); + } + +} + +export default GenerateQRCode; diff --git a/src/core/operations/GenerateRSAKeyPair.mjs b/src/core/operations/GenerateRSAKeyPair.mjs new file mode 100644 index 00000000..3dd2837d --- /dev/null +++ b/src/core/operations/GenerateRSAKeyPair.mjs @@ -0,0 +1,88 @@ +/** + * @author Matt C [me@mitt.dev] + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import forge from "node-forge"; +import { cryptNotice } from "../lib/Crypt.mjs"; + +/** + * Generate RSA Key Pair operation + */ +class GenerateRSAKeyPair extends Operation { + + /** + * GenerateRSAKeyPair constructor + */ + constructor() { + super(); + + this.name = "Generate RSA Key Pair"; + this.module = "Ciphers"; + this.description = `Generate an RSA key pair with a given number of bits.

${cryptNotice}`; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA Key Length", + type: "option", + value: [ + "1024", + "2048", + "4096" + ] + }, + { + name: "Output Format", + type: "option", + value: [ + "PEM", + "JSON", + "DER" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyLength, outputFormat] = args; + + return new Promise((resolve, reject) => { + forge.pki.rsa.generateKeyPair({ + bits: Number(keyLength), + workers: -1, + workerScript: "assets/forge/prime.worker.min.js" + }, (err, keypair) => { + if (err) return reject(err); + + let result; + + switch (outputFormat) { + case "PEM": + result = forge.pki.publicKeyToPem(keypair.publicKey) + "\n" + forge.pki.privateKeyToPem(keypair.privateKey); + break; + case "JSON": + result = JSON.stringify(keypair); + break; + case "DER": + result = forge.asn1.toDer(forge.pki.privateKeyToAsn1(keypair.privateKey)).getBytes(); + break; + } + + resolve(result); + }); + }); + } + +} + +export default GenerateRSAKeyPair; diff --git a/src/core/operations/GenerateTOTP.mjs b/src/core/operations/GenerateTOTP.mjs new file mode 100644 index 00000000..187f8418 --- /dev/null +++ b/src/core/operations/GenerateTOTP.mjs @@ -0,0 +1,73 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import * as OTPAuth from "otpauth"; + +/** + * Generate TOTP operation + */ +class GenerateTOTP extends Operation { + /** + * + */ + 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 (OAUTH), 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Name", + "type": "string", + "value": "" + }, + { + "name": "Code length", + "type": "number", + "value": 6 + }, + { + "name": "Epoch offset (T0)", + "type": "number", + "value": 0 + }, + { + "name": "Interval (T1)", + "type": "number", + "value": 30 + } + ]; + } + + /** + * + */ + run(input, args) { + const secretStr = new TextDecoder("utf-8").decode(input).trim(); + const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : ""; + + const totp = new OTPAuth.TOTP({ + issuer: "", + label: args[0], + algorithm: "SHA1", + digits: args[1], + period: args[3], + epoch: args[2] * 1000, // Convert seconds to milliseconds + secret: OTPAuth.Secret.fromBase32(secret) + }); + + const uri = totp.toString(); + const code = totp.generate(); + + return `URI: ${uri}\n\nPassword: ${code}`; + } +} + +export default GenerateTOTP; diff --git a/src/core/operations/GenerateUUID.mjs b/src/core/operations/GenerateUUID.mjs new file mode 100644 index 00000000..1ee0faba --- /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.mjs"; +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..30e11cb3 --- /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.mjs"; + +/** + * 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/GetAllCasings.mjs b/src/core/operations/GetAllCasings.mjs new file mode 100644 index 00000000..b3f3e94e --- /dev/null +++ b/src/core/operations/GetAllCasings.mjs @@ -0,0 +1,53 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Permutate String operation + */ +class GetAllCasings extends Operation { + + /** + * GetAllCasings constructor + */ + constructor() { + super(); + + this.name = "Get All Casings"; + this.module = "Default"; + this.description = "Outputs all possible casing variations of a string."; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const length = input.length; + const max = 1 << length; + input = input.toLowerCase(); + let result = ""; + + for (let i = 0; i < max; i++) { + const temp = input.split(""); + for (let j = 0; j < length; j++) { + if (((i >> j) & 1) === 1) { + temp[j] = temp[j].toUpperCase(); + } + } + result += temp.join("") + "\n"; + } + return result.slice(0, -1); + } +} + +export default GetAllCasings; diff --git a/src/core/operations/GetTime.mjs b/src/core/operations/GetTime.mjs new file mode 100644 index 00000000..7d10864f --- /dev/null +++ b/src/core/operations/GetTime.mjs @@ -0,0 +1,63 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {UNITS} from "../lib/DateTime.mjs"; + +/** + * Get Time operation + */ +class GetTime extends Operation { + + /** + * GetTime constructor + */ + constructor() { + super(); + + this.name = "Get Time"; + this.module = "Default"; + this.description = "Generates a timestamp showing the amount of time since the UNIX epoch (1970-01-01 00:00:00 UTC). Uses the W3C High Resolution Time API."; + this.infoURL = "https://wikipedia.org/wiki/Unix_time"; + this.inputType = "string"; + this.outputType = "number"; + this.args = [ + { + name: "Granularity", + type: "option", + value: UNITS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const nowMs = (performance.timeOrigin + performance.now()), + granularity = args[0]; + + switch (granularity) { + case "Nanoseconds (ns)": + return Math.round(nowMs * 1000 * 1000); + case "Microseconds (μs)": + return Math.round(nowMs * 1000); + case "Milliseconds (ms)": + return Math.round(nowMs); + case "Seconds (s)": + return Math.round(nowMs / 1000); + default: + throw new OperationError("Unknown granularity value: " + granularity); + } + } + +} + +export default GetTime; diff --git a/src/core/operations/GroupIPAddresses.mjs b/src/core/operations/GroupIPAddresses.mjs new file mode 100644 index 00000000..06840eeb --- /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.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {IP_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import {ipv6ToStr, genIpv6Mask, IPV4_REGEX, strToIpv6, ipv4ToStr, IPV6_REGEX, strToIpv4} from "../lib/IP.mjs"; + +/** + * 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 (network in ipv4Networks) { + 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 (networkStr in ipv6Networks) { + 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..d3d97f6d --- /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.mjs"; +import gunzip from "zlibjs/bin/gunzip.min.js"; + +const Zlib = gunzip.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.checks = [ + { + pattern: "^\\x1f\\x8b\\x08", + flags: "", + args: [] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {File} + */ + run(input, args) { + const gzipObj = new Zlib.Gunzip(new Uint8Array(input)); + return new Uint8Array(gzipObj.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..093ae6a4 --- /dev/null +++ b/src/core/operations/Gzip.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib.mjs"; +import gzip from "zlibjs/bin/gzip.min.js"; + +const Zlib = gzip.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.comment = true; + options.comment = comment; + } + const gzipObj = new Zlib.Gzip(new Uint8Array(input), options); + const compressed = new Uint8Array(gzipObj.compress()); + if (options.flags.comment && !(compressed[3] & 0x10)) { + compressed[3] |= 0x10; + } + return compressed.buffer; + } + +} + +export default Gzip; diff --git a/src/core/operations/HAS160.mjs b/src/core/operations/HAS160.mjs new file mode 100644 index 00000000..707b99b3 --- /dev/null +++ b/src/core/operations/HAS160.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * 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, by default, of 80 rounds."; + this.infoURL = "https://wikipedia.org/wiki/HAS-160"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Rounds", + type: "number", + value: 80, + min: 1, + max: 80 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("has160", input, {rounds: args[0]}); + } + +} + +export default HAS160; diff --git a/src/core/operations/HASSHClientFingerprint.mjs b/src/core/operations/HASSHClientFingerprint.mjs new file mode 100644 index 00000000..e507cea1 --- /dev/null +++ b/src/core/operations/HASSHClientFingerprint.mjs @@ -0,0 +1,166 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * HASSH created by Salesforce + * Ben Reardon (@benreardon) + * Adel Karimi (@0x4d31) + * and the JA3 crew: + * John B. Althouse + * Jeff Atkinson + * Josh Atkins + * + * Algorithm released under the BSD-3-clause licence + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * HASSH Client Fingerprint operation + */ +class HASSHClientFingerprint extends Operation { + + /** + * HASSHClientFingerprint constructor + */ + constructor() { + super(); + + this.name = "HASSH Client Fingerprint"; + this.module = "Crypto"; + this.description = "Generates a HASSH fingerprint to help identify SSH clients based on hashing together values from the Client Key Exchange Init message.

Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Client to Server."; + this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Base64", "Raw"] + }, + { + name: "Output format", + type: "option", + value: ["Hash digest", "HASSH algorithms string", "Full details"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + + input = Utils.convertToByteArray(input, inputFormat); + const s = new Stream(new Uint8Array(input)); + + // Length + const length = s.readInt(4); + if (s.length !== length + 4) + throw new OperationError("Incorrect packet length."); + + // Padding length + const paddingLength = s.readInt(1); + + // Message code + const messageCode = s.readInt(1); + if (messageCode !== 20) + throw new OperationError("Not a Key Exchange Init."); + + // Cookie + s.moveForwardsBy(16); + + // KEX Algorithms + const kexAlgosLength = s.readInt(4); + const kexAlgos = s.readString(kexAlgosLength); + + // Server Host Key Algorithms + const serverHostKeyAlgosLength = s.readInt(4); + s.moveForwardsBy(serverHostKeyAlgosLength); + + // Encryption Algorithms Client to Server + const encAlgosC2SLength = s.readInt(4); + const encAlgosC2S = s.readString(encAlgosC2SLength); + + // Encryption Algorithms Server to Client + const encAlgosS2CLength = s.readInt(4); + s.moveForwardsBy(encAlgosS2CLength); + + // MAC Algorithms Client to Server + const macAlgosC2SLength = s.readInt(4); + const macAlgosC2S = s.readString(macAlgosC2SLength); + + // MAC Algorithms Server to Client + const macAlgosS2CLength = s.readInt(4); + s.moveForwardsBy(macAlgosS2CLength); + + // Compression Algorithms Client to Server + const compAlgosC2SLength = s.readInt(4); + const compAlgosC2S = s.readString(compAlgosC2SLength); + + // Compression Algorithms Server to Client + const compAlgosS2CLength = s.readInt(4); + s.moveForwardsBy(compAlgosS2CLength); + + // Languages Client to Server + const langsC2SLength = s.readInt(4); + s.moveForwardsBy(langsC2SLength); + + // Languages Server to Client + const langsS2CLength = s.readInt(4); + s.moveForwardsBy(langsS2CLength); + + // First KEX packet follows + s.moveForwardsBy(1); + + // Reserved + s.moveForwardsBy(4); + + // Padding string + s.moveForwardsBy(paddingLength); + + // Output + const hassh = [ + kexAlgos, + encAlgosC2S, + macAlgosC2S, + compAlgosC2S + ]; + const hasshStr = hassh.join(";"); + const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr)); + + switch (outputFormat) { + case "HASSH algorithms string": + return hasshStr; + case "Full details": + return `Hash digest: +${hasshHash} + +Full HASSH algorithms string: +${hasshStr} + +Key Exchange Algorithms: +${kexAlgos} +Encryption Algorithms Client to Server: +${encAlgosC2S} +MAC Algorithms Client to Server: +${macAlgosC2S} +Compression Algorithms Client to Server: +${compAlgosC2S}`; + case "Hash digest": + default: + return hasshHash; + } + } + +} + +export default HASSHClientFingerprint; diff --git a/src/core/operations/HASSHServerFingerprint.mjs b/src/core/operations/HASSHServerFingerprint.mjs new file mode 100644 index 00000000..f08a418b --- /dev/null +++ b/src/core/operations/HASSHServerFingerprint.mjs @@ -0,0 +1,166 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * HASSH created by Salesforce + * Ben Reardon (@benreardon) + * Adel Karimi (@0x4d31) + * and the JA3 crew: + * John B. Althouse + * Jeff Atkinson + * Josh Atkins + * + * Algorithm released under the BSD-3-clause licence +*/ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * HASSH Server Fingerprint operation + */ +class HASSHServerFingerprint extends Operation { + + /** + * HASSHServerFingerprint constructor + */ + constructor() { + super(); + + this.name = "HASSH Server Fingerprint"; + this.module = "Crypto"; + this.description = "Generates a HASSH fingerprint to help identify SSH servers based on hashing together values from the Server Key Exchange Init message.

Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Server to Client."; + this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Base64", "Raw"] + }, + { + name: "Output format", + type: "option", + value: ["Hash digest", "HASSH algorithms string", "Full details"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + + input = Utils.convertToByteArray(input, inputFormat); + const s = new Stream(new Uint8Array(input)); + + // Length + const length = s.readInt(4); + if (s.length !== length + 4) + throw new OperationError("Incorrect packet length."); + + // Padding length + const paddingLength = s.readInt(1); + + // Message code + const messageCode = s.readInt(1); + if (messageCode !== 20) + throw new OperationError("Not a Key Exchange Init."); + + // Cookie + s.moveForwardsBy(16); + + // KEX Algorithms + const kexAlgosLength = s.readInt(4); + const kexAlgos = s.readString(kexAlgosLength); + + // Server Host Key Algorithms + const serverHostKeyAlgosLength = s.readInt(4); + s.moveForwardsBy(serverHostKeyAlgosLength); + + // Encryption Algorithms Client to Server + const encAlgosC2SLength = s.readInt(4); + s.moveForwardsBy(encAlgosC2SLength); + + // Encryption Algorithms Server to Client + const encAlgosS2CLength = s.readInt(4); + const encAlgosS2C = s.readString(encAlgosS2CLength); + + // MAC Algorithms Client to Server + const macAlgosC2SLength = s.readInt(4); + s.moveForwardsBy(macAlgosC2SLength); + + // MAC Algorithms Server to Client + const macAlgosS2CLength = s.readInt(4); + const macAlgosS2C = s.readString(macAlgosS2CLength); + + // Compression Algorithms Client to Server + const compAlgosC2SLength = s.readInt(4); + s.moveForwardsBy(compAlgosC2SLength); + + // Compression Algorithms Server to Client + const compAlgosS2CLength = s.readInt(4); + const compAlgosS2C = s.readString(compAlgosS2CLength); + + // Languages Client to Server + const langsC2SLength = s.readInt(4); + s.moveForwardsBy(langsC2SLength); + + // Languages Server to Client + const langsS2CLength = s.readInt(4); + s.moveForwardsBy(langsS2CLength); + + // First KEX packet follows + s.moveForwardsBy(1); + + // Reserved + s.moveForwardsBy(4); + + // Padding string + s.moveForwardsBy(paddingLength); + + // Output + const hassh = [ + kexAlgos, + encAlgosS2C, + macAlgosS2C, + compAlgosS2C + ]; + const hasshStr = hassh.join(";"); + const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr)); + + switch (outputFormat) { + case "HASSH algorithms string": + return hasshStr; + case "Full details": + return `Hash digest: +${hasshHash} + +Full HASSH algorithms string: +${hasshStr} + +Key Exchange Algorithms: +${kexAlgos} +Encryption Algorithms Server to Client: +${encAlgosS2C} +MAC Algorithms Server to Client: +${macAlgosS2C} +Compression Algorithms Server to Client: +${compAlgosS2C}`; + case "Hash digest": + default: + return hasshHash; + } + } + +} + +export default HASSHServerFingerprint; diff --git a/src/core/operations/HMAC.mjs b/src/core/operations/HMAC.mjs new file mode 100644 index 00000000..cb129692 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import CryptoApi from "crypto-api/src/crypto-api.mjs"; + +/** + * 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 13f94d92..00000000 --- a/src/core/operations/HTML.js +++ /dev/null @@ -1,855 +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) { - var convertAll = args[0], - numeric = args[1] === "Numeric entities", - hexa = args[1] === "Hex entities"; - - var charcodes = Utils.strToCharcode(input); - var output = ""; - - for (var 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) { - var 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 - var 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. ) - var 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. :) - var 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) { - var 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) { - var 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) - var 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) - var 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_)); - } - - var 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), - m = (1 - g/255 - k) / (1 - k), // eslint-disable-line no-redeclare - y = (1 - b/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); - - var 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){ - var r, g, b; - - if (s === 0){ - r = g = b = l; // achromatic - } else { - var 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; - }; - - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var 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; - var 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 { - var 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, - }, - -}; - -export default HTML; diff --git a/src/core/operations/HTMLToText.mjs b/src/core/operations/HTMLToText.mjs new file mode 100644 index 00000000..ff90572a --- /dev/null +++ b/src/core/operations/HTMLToText.mjs @@ -0,0 +1,41 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * HTML To Text operation + */ +class HTMLToText extends Operation { + + /** + * HTMLToText constructor + */ + constructor() { + super(); + + this.name = "HTML To Text"; + this.module = "Default"; + this.description = "Converts an HTML output from an operation to a readable string instead of being rendered in the DOM."; + this.infoURL = ""; + this.inputType = "html"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {html} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input; + } + +} + +export default HTMLToText; diff --git a/src/core/operations/HTTP.js b/src/core/operations/HTTP.js deleted file mode 100755 index 2be0f0d8..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) { - var 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) { - var 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..7d061f37 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..7d5a9b1d --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {fromHex} from "../lib/Hex.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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 calculate 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] = new Uint8Array(Utils.strToArrayBuffer(samples[0])); + samples[1] = new Uint8Array(Utils.strToArrayBuffer(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 7605e7a2..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); - var 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) { - var hashFunc = args[1]; - input = CryptoJS.enc.Latin1.parse(input); - var 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) { - var 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, ""); - - var 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..680bff4d --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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[5]); + const lng2 = parseFloat(values[7]); + + 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..d74c3eea --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * 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/HeatmapChart.mjs b/src/core/operations/HeatmapChart.mjs new file mode 100644 index 00000000..96636986 --- /dev/null +++ b/src/core/operations/HeatmapChart.mjs @@ -0,0 +1,266 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import * as d3temp from "d3"; +import * as nodomtemp from "nodom"; +import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; + +const d3 = d3temp.default ? d3temp.default : d3temp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + +/** + * Heatmap chart operation + */ +class HeatmapChart extends Operation { + + /** + * HeatmapChart constructor + */ + constructor() { + super(); + + this.name = "Heatmap chart"; + this.module = "Charts"; + this.description = "A heatmap is a graphical representation of data where the individual values contained in a matrix are represented as colors."; + this.infoURL = "https://wikipedia.org/wiki/Heat_map"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "Record delimiter", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "Number of vertical bins", + type: "number", + value: 25, + }, + { + name: "Number of horizontal bins", + type: "number", + value: 25, + }, + { + name: "Use column headers as labels", + type: "boolean", + value: true, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Y label", + type: "string", + value: "", + }, + { + name: "Draw bin edges", + type: "boolean", + value: false, + }, + { + name: "Min colour value", + type: "string", + value: COLOURS.min, + }, + { + name: "Max colour value", + type: "string", + value: COLOURS.max, + }, + ]; + } + + /** + * Heatmap chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), + vBins = args[2], + hBins = args[3], + columnHeadingsAreIncluded = args[4], + drawEdges = args[7], + minColour = args[8], + maxColour = args[9], + dimension = 500; + if (vBins <= 0) throw new OperationError("Number of vertical bins must be greater than 0"); + if (hBins <= 0) throw new OperationError("Number of horizontal bins must be greater than 0"); + + let xLabel = args[5], + yLabel = args[6]; + const { headings, values } = getScatterValues( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + const margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + binWidth = width / hBins, + binHeight = height/ vBins, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + const bins = this.getHeatmapPacking(values, vBins, hBins), + maxCount = Math.max(...bins.map(row => { + const lengths = row.map(cell => cell.length); + return Math.max(...lengths); + })); + + const xExtent = d3.extent(values, d => d[0]), + yExtent = d3.extent(values, d => d[1]); + + const xAxis = d3.scaleLinear() + .domain(xExtent) + .range([0, width]); + const yAxis = d3.scaleLinear() + .domain(yExtent) + .range([height, 0]); + + const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) + .domain([0, maxCount]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + marginedSpace.append("g") + .attr("class", "bins") + .attr("clip-path", "url(#clip)") + .selectAll("g") + .data(bins) + .enter() + .append("g") + .selectAll("rect") + .data(d => d) + .enter() + .append("rect") + .attr("x", (d) => binWidth * d.x) + .attr("y", (d) => (height - binHeight * (d.y + 1))) + .attr("width", binWidth) + .attr("height", binHeight) + .attr("fill", (d) => colour(d.length)) + .attr("stroke", drawEdges ? "rgba(0, 0, 0, 0.5)" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + const count = d.length, + perc = 100.0 * d.length / values.length, + tooltip = `Count: ${count}\n + Percentage: ${perc.toFixed(2)}%\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + } + + /** + * Packs a list of x, y coordinates into a number of bins for use in a heatmap. + * + * @param {Object[]} points + * @param {number} number of vertical bins + * @param {number} number of horizontal bins + * @returns {Object[]} a list of bins (each bin is an Array) with x y coordinates, filled with the points + */ + getHeatmapPacking(values, vBins, hBins) { + const xBounds = d3.extent(values, d => d[0]), + yBounds = d3.extent(values, d => d[1]), + bins = []; + + if (xBounds[0] === xBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum X coordinate."; + if (yBounds[0] === yBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum Y coordinate."; + + for (let y = 0; y < vBins; y++) { + bins.push([]); + for (let x = 0; x < hBins; x++) { + const item = []; + item.y = y; + item.x = x; + + bins[y].push(item); + } // x + } // y + + const epsilon = 0.000000001; // This is to clamp values that are exactly the maximum; + + values.forEach(v => { + const fractionOfY = (v[1] - yBounds[0]) / ((yBounds[1] + epsilon) - yBounds[0]), + fractionOfX = (v[0] - xBounds[0]) / ((xBounds[1] + epsilon) - xBounds[0]), + y = Math.floor(vBins * fractionOfY), + x = Math.floor(hBins * fractionOfX); + + bins[y][x].push({x: v[0], y: v[1]}); + }); + + return bins; + } + +} + +export default HeatmapChart; diff --git a/src/core/operations/HexDensityChart.mjs b/src/core/operations/HexDensityChart.mjs new file mode 100644 index 00000000..72cab9fa --- /dev/null +++ b/src/core/operations/HexDensityChart.mjs @@ -0,0 +1,296 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import * as d3temp from "d3"; +import * as d3hexbintemp from "d3-hexbin"; +import * as nodomtemp from "nodom"; +import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +const d3 = d3temp.default ? d3temp.default : d3temp; +const d3hexbin = d3hexbintemp.default ? d3hexbintemp.default : d3hexbintemp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + + +/** + * Hex Density chart operation + */ +class HexDensityChart extends Operation { + + /** + * HexDensityChart constructor + */ + constructor() { + super(); + + this.name = "Hex Density chart"; + this.module = "Charts"; + this.description = "Hex density charts are used in a similar way to scatter charts, however rather than rendering tens of thousands of points, it groups the points into a few hundred hexagons to show the distribution."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "Record delimiter", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "Pack radius", + type: "number", + value: 25, + }, + { + name: "Draw radius", + type: "number", + value: 15, + }, + { + name: "Use column headers as labels", + type: "boolean", + value: true, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Y label", + type: "string", + value: "", + }, + { + name: "Draw hexagon edges", + type: "boolean", + value: false, + }, + { + name: "Min colour value", + type: "string", + value: COLOURS.min, + }, + { + name: "Max colour value", + type: "string", + value: COLOURS.max, + }, + { + name: "Draw empty hexagons within data boundaries", + type: "boolean", + value: false, + } + ]; + } + + + /** + * Hex Bin chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), + packRadius = args[2], + drawRadius = args[3], + columnHeadingsAreIncluded = args[4], + drawEdges = args[7], + minColour = args[8], + maxColour = args[9], + drawEmptyHexagons = args[10], + dimension = 500; + + let xLabel = args[5], + yLabel = args[6]; + const { headings, values } = getScatterValues( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + const margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + const hexbin = d3hexbin.hexbin() + .radius(packRadius) + .extent([0, 0], [width, height]); + + const hexPoints = hexbin(values), + maxCount = Math.max(...hexPoints.map(b => b.length)); + + const xExtent = d3.extent(hexPoints, d => d.x), + yExtent = d3.extent(hexPoints, d => d.y); + xExtent[0] -= 2 * packRadius; + xExtent[1] += 3 * packRadius; + yExtent[0] -= 2 * packRadius; + yExtent[1] += 2 * packRadius; + + const xAxis = d3.scaleLinear() + .domain(xExtent) + .range([0, width]); + const yAxis = d3.scaleLinear() + .domain(yExtent) + .range([height, 0]); + + const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) + .domain([0, maxCount]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + if (drawEmptyHexagons) { + marginedSpace.append("g") + .attr("class", "empty-hexagon") + .selectAll("path") + .data(this.getEmptyHexagons(hexPoints, packRadius)) + .enter() + .append("path") + .attr("d", d => { + return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; + }) + .attr("fill", (d) => colour(0)) + .attr("stroke", drawEdges ? "black" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + const count = 0, + perc = 0, + tooltip = `Count: ${count}\n + Percentage: ${perc.toFixed(2)}%\n + Center: ${d.x.toFixed(2)}, ${d.y.toFixed(2)}\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + } + + marginedSpace.append("g") + .attr("class", "hexagon") + .attr("clip-path", "url(#clip)") + .selectAll("path") + .data(hexPoints) + .enter() + .append("path") + .attr("d", d => { + return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; + }) + .attr("fill", (d) => colour(d.length)) + .attr("stroke", drawEdges ? "black" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + const count = d.length, + perc = 100.0 * d.length / values.length, + CX = d.x, + CY = d.y, + xMin = Math.min(...d.map(d => d[0])), + xMax = Math.max(...d.map(d => d[0])), + yMin = Math.min(...d.map(d => d[1])), + yMax = Math.max(...d.map(d => d[1])), + tooltip = `Count: ${count}\n + Percentage: ${perc.toFixed(2)}%\n + Center: ${CX.toFixed(2)}, ${CY.toFixed(2)}\n + Min X: ${xMin.toFixed(2)}\n + Max X: ${xMax.toFixed(2)}\n + Min Y: ${yMin.toFixed(2)}\n + Max Y: ${yMax.toFixed(2)} + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + } + + + /** + * Hex Bin chart operation. + * + * @param {Object[]} - centres + * @param {number} - radius + * @returns {Object[]} + */ + getEmptyHexagons(centres, radius) { + const emptyCentres = [], + boundingRect = [d3.extent(centres, d => d.x), d3.extent(centres, d => d.y)], + hexagonCenterToEdge = Math.cos(2 * Math.PI / 12) * radius, + hexagonEdgeLength = Math.sin(2 * Math.PI / 12) * radius; + let indent = false; + + for (let y = boundingRect[1][0]; y <= boundingRect[1][1] + radius; y += hexagonEdgeLength + radius) { + for (let x = boundingRect[0][0]; x <= boundingRect[0][1] + radius; x += 2 * hexagonCenterToEdge) { + let cx = x; + const cy = y; + + if (indent && x >= boundingRect[0][1]) break; + if (indent) cx += hexagonCenterToEdge; + + emptyCentres.push({x: cx, y: cy}); + } + indent = !indent; + } + + return emptyCentres; + } + +} + +export default HexDensityChart; diff --git a/src/core/operations/HexToObjectIdentifier.mjs b/src/core/operations/HexToObjectIdentifier.mjs new file mode 100644 index 00000000..67971f8d --- /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.mjs"; + +/** + * 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..8217ffbd --- /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.mjs"; + +/** + * 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 66ea2285..00000000 --- a/src/core/operations/IP.js +++ /dev/null @@ -1,1081 +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) { - var includeNetworkInfo = args[0], - enumerateAddresses = args[1], - allowLargeList = args[2]; - - // Check what type of input we are looking at - var 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) { - var match, - output = ""; - - if ((match = IP.IPV6_REGEX.exec(input))) { - var 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"; - var 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"; - - var 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."; - - var 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) { - var inFormat = args[0], - outFormat = args[1], - lines = input.split("\n"), - output = "", - j = 0; - - - for (var i = 0; i < lines.length; i++) { - if (lines[i] === "") continue; - var baIp = []; - - if (inFormat === outFormat) { - output += lines[i] + "\n"; - continue; - } - - // Convert to byte array IP from input format - switch (inFormat) { - case "Dotted Decimal": - var octets = lines[i].split("."); - for (j = 0; j < octets.length; j++) { - baIp.push(parseInt(octets[j], 10)); - } - break; - case "Decimal": - var 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"; - } - - // Convert byte array IP to output format - switch (outFormat) { - case "Dotted Decimal": - var ddIp = ""; - for (j = 0; j < baIp.length; j++) { - ddIp += baIp[j] + "."; - } - output += ddIp.slice(0, ddIp.length-1) + "\n"; - break; - case "Decimal": - var decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0; - output += decIp.toString() + "\n"; - break; - case "Hex": - var 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) { - var 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 = ""; - - 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 (var 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 (var 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) { - var format = args[0], - output; - - if (format === "Hex") { - input = Utils.fromHex(input); - } else if (format === "Raw") { - input = Utils.strToByteArray(input); - } else { - return "Unrecognised input format."; - } - - var 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... - var optionsLen = ihl * 4 - 20; - options = input.slice(20, optionsLen + 20); - } - - // Protocol - var protocolInfo = IP._protocolLookup[protocol] || {keyword: "", protocol: ""}; - - // Checksum - var 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) { - var output = "", - network = IP._strToIpv4(cidr[1]), - cidrRange = parseInt(cidr[2], 10); - - if (cidrRange < 0 || cidrRange > 31) { - return "IPv4 CIDR must be less than 32"; - } - - var 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) { - var 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"; - } - - var mask = IP._genIpv6Mask(cidrRange), - ip1 = new Array(8), - ip2 = new Array(8), - totalDiff = "", - total = new Array(128); - - for (var 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 (var 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) { - var mask = new Array(8), - shift; - - for (var 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) { - var output = "", - ip1 = IP._strToIpv4(range[1]), - ip2 = IP._strToIpv4(range[2]); - - // Calculate mask - var diff = ip1 ^ ip2, - cidr = 32, - mask = 0; - - while (diff !== 0) { - diff >>= 1; - cidr--; - mask = (mask << 1) | 1; - } - - mask = ~mask >>> 0; - var 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) { - var output = "", - ip1 = IP._strToIpv6(range[1]), - ip2 = IP._strToIpv6(range[14]); - - var t = "", - total = new Array(128); - - // Initialise total array to "0" - for (var i = 0; i < 128; i++) - total[i] = "0"; - - for (i = 0; i < 8; i++) { - t = (ip2[i] - ip1[i]).toString(2); - if (t !== "0") { - for (var 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) { - var 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."; - - var numBlocks = []; - for (var 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) { - var 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) { - var blocks = ipStr.split(":"), - numBlocks = parseBlocks(blocks), - j = 0, - ipv6 = new Array(8); - - for (var 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."; - var numBlocks = []; - for (var 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) { - var output = "", - i = 0; - - if (compact) { - var 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) { - var 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/IPv6TransitionAddresses.mjs b/src/core/operations/IPv6TransitionAddresses.mjs new file mode 100644 index 00000000..80d03fd0 --- /dev/null +++ b/src/core/operations/IPv6TransitionAddresses.mjs @@ -0,0 +1,209 @@ +/** + * @author jb30795 [jb30795@proton.me] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * IPv6 Transition Addresses operation + */ +class IPv6TransitionAddresses extends Operation { + + /** + * IPv6TransitionAddresses constructor + */ + constructor() { + super(); + + this.name = "IPv6 Transition Addresses"; + this.module = "Default"; + this.description = "Converts IPv4 addresses to their IPv6 Transition addresses. IPv6 Transition addresses can also be converted back into their original IPv4 address. MAC addresses can also be converted into the EUI-64 format, this can them be appended to your IPv6 /64 range to obtain a full /128 address.

Transition technologies enable translation between IPv4 and IPv6 addresses or tunneling to allow traffic to pass through the incompatible network, allowing the two standards to coexist.

Only /24 ranges and currently handled. Remove headers to easily copy out results."; + this.infoURL = "https://wikipedia.org/wiki/IPv6_transition_mechanism"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Ignore ranges", + "type": "boolean", + "value": true + }, + { + "name": "Remove headers", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const XOR = {"0": "2", "1": "3", "2": "0", "3": "1", "4": "6", "5": "7", "6": "4", "7": "5", "8": "a", "9": "b", "a": "8", "b": "9", "c": "e", "d": "f", "e": "c", "f": "d"}; + + /** + * Function to convert to hex + */ + function hexify(octet) { + return Number(octet).toString(16).padStart(2, "0"); + } + + /** + * Function to convert Hex to Int + */ + function intify(hex) { + return parseInt(hex, 16); + } + + /** + * Function converts IPv4 to IPv6 Transtion address + */ + function ipTransition(input, range) { + let output = ""; + const HEXIP = input.split("."); + + /** + * 6to4 + */ + if (!args[1]) { + output += "6to4: "; + } + output += "2002:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); + if (range) { + output += "00::/40\n"; + } else { + output += hexify(HEXIP[3]) + "::/48\n"; + } + + /** + * Mapped + */ + if (!args[1]) { + output += "IPv4 Mapped: "; + } + output += "::ffff:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); + if (range) { + output += "00/120\n"; + } else { + output += hexify(HEXIP[3]) + "\n"; + } + + /** + * Translated + */ + if (!args[1]) { + output += "IPv4 Translated: "; + } + output += "::ffff:0:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); + if (range) { + output += "00/120\n"; + } else { + output += hexify(HEXIP[3]) + "\n"; + } + + /** + * Nat64 + */ + if (!args[1]) { + output += "Nat 64: "; + } + output += "64:ff9b::" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]); + if (range) { + output += "00/120\n"; + } else { + output += hexify(HEXIP[3]) + "\n"; + } + + return output; + } + + /** + * Convert MAC to EUI-64 + */ + function macTransition(input) { + let output = ""; + const MACPARTS = input.split(":"); + if (!args[1]) { + output += "EUI-64 Interface ID: "; + } + const MAC = MACPARTS[0] + MACPARTS[1] + ":" + MACPARTS[2] + "ff:fe" + MACPARTS[3] + ":" + MACPARTS[4] + MACPARTS[5]; + output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2); + + return output; + } + + + /** + * Convert IPv6 address to its original IPv4 or MAC address + */ + function unTransition(input) { + let output = ""; + let hextets = ""; + + /** + * 6to4 + */ + if (input.startsWith("2002:")) { + if (!args[1]) { + output += "IPv4: "; + } + output += String(intify(input.slice(5, 7))) + "." + String(intify(input.slice(7, 9)))+ "." + String(intify(input.slice(10, 12)))+ "." + String(intify(input.slice(12, 14))) + "\n"; + } else if (input.startsWith("::ffff:") || input.startsWith("0000:0000:0000:0000:0000:ffff:") || input.startsWith("::ffff:0000:") || input.startsWith("0000:0000:0000:0000:ffff:0000:") || input.startsWith("64:ff9b::") || input.startsWith("0064:ff9b:0000:0000:0000:0000:")) { + /** + * Mapped/Translated/Nat64 + */ + hextets = /:([0-9a-z]{1,4}):[0-9a-z]{1,4}$/.exec(input)[1].padStart(4, "0") + /:([0-9a-z]{1,4})$/.exec(input)[1].padStart(4, "0"); + if (!args[1]) { + output += "IPv4: "; + } + output += intify(hextets.slice(-8, -7) + hextets.slice(-7, -6)) + "." +intify(hextets.slice(-6, -5) + hextets.slice(-5, -4)) + "." +intify(hextets.slice(-4, -3) + hextets.slice(-3, -2)) + "." +intify(hextets.slice(-2, -1) + hextets.slice(-1,)) + "\n"; + } else if (input.slice(-12, -7).toUpperCase() === "FF:FE") { + /** + * EUI-64 + */ + if (!args[1]) { + output += "Mac Address: "; + } + const MAC = (input.slice(-19, -17) + ":" + input.slice(-17, -15) + ":" + input.slice(-14, -12) + ":" + input.slice(-7, -5) + ":" + input.slice(-4, -2) + ":" + input.slice(-2,)).toUpperCase(); + output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2) + "\n"; + } + + return output; + } + + + /** + * Main + */ + let output = ""; + let inputs = input.split("\n"); + // Remove blank rows + inputs = inputs.filter(Boolean); + + for (let i = 0; i < inputs.length; i++) { + // if ignore ranges is checked and input is a range, skip + if ((args[0] && !inputs[i].includes("/")) || (!args[0])) { + if (/^[0-9]{1,3}(?:\.[0-9]{1,3}){3}$/.test(inputs[i])) { + output += ipTransition(inputs[i], false); + } else if (/\/24$/.test(inputs[i])) { + output += ipTransition(inputs[i], true); + } else if (/^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/.test(inputs[i])) { + output += macTransition(inputs[i]); + } else if (/^((?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/.test(inputs[i])) { + output += unTransition(inputs[i]); + } else { + output = "Enter compressed or expanded IPv6 address, IPv4 address or MAC Address."; + } + } + } + + return output; + } + +} + +export default IPv6TransitionAddresses; diff --git a/src/core/operations/ImageBrightnessContrast.mjs b/src/core/operations/ImageBrightnessContrast.mjs new file mode 100644 index 00000000..bb0541ab --- /dev/null +++ b/src/core/operations/ImageBrightnessContrast.mjs @@ -0,0 +1,110 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Image Brightness / Contrast operation + */ +class ImageBrightnessContrast extends Operation { + + /** + * ImageBrightnessContrast constructor + */ + constructor() { + super(); + + this.name = "Image Brightness / Contrast"; + this.module = "Image"; + this.description = "Adjust the brightness or contrast of an image."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Brightness", + type: "number", + value: 0, + min: -100, + max: 100 + }, + { + name: "Contrast", + type: "number", + value: 0, + min: -100, + max: 100 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [brightness, contrast] = args; + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (brightness !== 0) { + if (isWorkerEnvironment()) + self.sendStatusMessage("Changing image brightness..."); + image.brightness(brightness / 100); + } + if (contrast !== 0) { + if (isWorkerEnvironment()) + self.sendStatusMessage("Changing image contrast..."); + image.contrast(contrast / 100); + } + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error adjusting image brightness or contrast. (${err})`); + } + } + + /** + * Displays the image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default ImageBrightnessContrast; diff --git a/src/core/operations/ImageFilter.mjs b/src/core/operations/ImageFilter.mjs new file mode 100644 index 00000000..ed47bd2a --- /dev/null +++ b/src/core/operations/ImageFilter.mjs @@ -0,0 +1,101 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Image Filter operation + */ +class ImageFilter extends Operation { + + /** + * ImageFilter constructor + */ + constructor() { + super(); + + this.name = "Image Filter"; + this.module = "Image"; + this.description = "Applies a greyscale or sepia filter to an image."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Filter type", + type: "option", + value: [ + "Greyscale", + "Sepia" + ] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [filterType] = args; + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("Applying " + filterType.toLowerCase() + " filter to image..."); + if (filterType === "Greyscale") { + image.greyscale(); + } else { + image.sepia(); + } + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error applying filter to image. (${err})`); + } + } + + /** + * Displays the blurred image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default ImageFilter; diff --git a/src/core/operations/ImageHueSaturationLightness.mjs b/src/core/operations/ImageHueSaturationLightness.mjs new file mode 100644 index 00000000..a008c8f3 --- /dev/null +++ b/src/core/operations/ImageHueSaturationLightness.mjs @@ -0,0 +1,137 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Image Hue/Saturation/Lightness operation + */ +class ImageHueSaturationLightness extends Operation { + + /** + * ImageHueSaturationLightness constructor + */ + constructor() { + super(); + + this.name = "Image Hue/Saturation/Lightness"; + this.module = "Image"; + this.description = "Adjusts the hue / saturation / lightness (HSL) values of an image."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Hue", + type: "number", + value: 0, + min: -360, + max: 360 + }, + { + name: "Saturation", + type: "number", + value: 0, + min: -100, + max: 100 + }, + { + name: "Lightness", + type: "number", + value: 0, + min: -100, + max: 100 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [hue, saturation, lightness] = args; + + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (hue !== 0) { + if (isWorkerEnvironment()) + self.sendStatusMessage("Changing image hue..."); + image.colour([ + { + apply: "hue", + params: [hue] + } + ]); + } + if (saturation !== 0) { + if (isWorkerEnvironment()) + self.sendStatusMessage("Changing image saturation..."); + image.colour([ + { + apply: "saturate", + params: [saturation] + } + ]); + } + if (lightness !== 0) { + if (isWorkerEnvironment()) + self.sendStatusMessage("Changing image lightness..."); + image.colour([ + { + apply: "lighten", + params: [lightness] + } + ]); + } + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error adjusting image hue / saturation / lightness. (${err})`); + } + } + + /** + * Displays the image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } +} + +export default ImageHueSaturationLightness; diff --git a/src/core/operations/ImageOpacity.mjs b/src/core/operations/ImageOpacity.mjs new file mode 100644 index 00000000..fc0a23e7 --- /dev/null +++ b/src/core/operations/ImageOpacity.mjs @@ -0,0 +1,96 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Image Opacity operation + */ +class ImageOpacity extends Operation { + + /** + * ImageOpacity constructor + */ + constructor() { + super(); + + this.name = "Image Opacity"; + this.module = "Image"; + this.description = "Adjust the opacity of an image."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Opacity (%)", + type: "number", + value: 100, + min: 0, + max: 100 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [opacity] = args; + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("Changing image opacity..."); + image.opacity(opacity / 100); + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error changing image opacity. (${err})`); + } + } + + /** + * Displays the image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default ImageOpacity; diff --git a/src/core/operations/IndexOfCoincidence.mjs b/src/core/operations/IndexOfCoincidence.mjs new file mode 100644 index 00000000..8655ccd7 --- /dev/null +++ b/src/core/operations/IndexOfCoincidence.mjs @@ -0,0 +1,107 @@ +/** + * @author George O [georgeomnet+cyberchef@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Index of Coincidence operation + */ +class IndexOfCoincidence extends Operation { + + /** + * IndexOfCoincidence constructor + */ + constructor() { + super(); + + this.name = "Index of Coincidence"; + this.module = "Default"; + this.description = "Index of Coincidence (IC) is the probability of two randomly selected characters being the same. This can be used to determine whether text is readable or random, with English text having an IC of around 0.066. IC can therefore be a sound method to automate frequency analysis."; + this.infoURL = "https://wikipedia.org/wiki/Index_of_coincidence"; + this.inputType = "string"; + this.outputType = "number"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const text = input.toLowerCase().replace(/[^a-z]/g, ""), + frequencies = new Array(26).fill(0), + alphabet = Utils.expandAlphRange("a-z"); + let coincidence = 0.00, + density = 0.00, + result = 0.00, + i; + + for (i=0; i < alphabet.length; i++) { + frequencies[i] = text.count(alphabet[i]); + } + + for (i=0; i < frequencies.length; i++) { + coincidence += frequencies[i] * (frequencies[i] - 1); + } + + density = frequencies.sum(); + + // Ensure that we don't divide by 0 + if (density < 2) density = 2; + + result = coincidence / (density * (density - 1)); + + return result; + } + + /** + * Displays the IC as a scale bar for web apps. + * + * @param {number} ic + * @returns {html} + */ + present(ic) { + return `Index of Coincidence: ${ic} +Normalized: ${ic * 26} +

+- 0 represents complete randomness (all characters are unique), whereas 1 represents no randomness (all characters are identical). +- English text generally has an IC of between 0.67 to 0.78. +- 'Random' text is determined by the probability that each letter occurs the same number of times as another. + +The graph shows the IC of the input data. A low IC generally means that the text is random, compressed or encrypted. + + + `; + } + +} + +export default IndexOfCoincidence; diff --git a/src/core/operations/InvertImage.mjs b/src/core/operations/InvertImage.mjs new file mode 100644 index 00000000..0036564f --- /dev/null +++ b/src/core/operations/InvertImage.mjs @@ -0,0 +1,87 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Invert Image operation + */ +class InvertImage extends Operation { + + /** + * InvertImage constructor + */ + constructor() { + super(); + + this.name = "Invert Image"; + this.module = "Image"; + this.description = "Invert the colours of an image."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + if (!isImage(input)) { + throw new OperationError("Invalid input file format."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("Inverting image..."); + image.invert(); + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error inverting image. (${err})`); + } + } + + /** + * Displays the inverted image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default InvertImage; diff --git a/src/core/operations/JA3Fingerprint.mjs b/src/core/operations/JA3Fingerprint.mjs new file mode 100644 index 00000000..941e2fcb --- /dev/null +++ b/src/core/operations/JA3Fingerprint.mjs @@ -0,0 +1,205 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * JA3 created by Salesforce + * John B. Althouse + * Jeff Atkinson + * Josh Atkins + * + * Algorithm released under the BSD-3-clause licence + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * JA3 Fingerprint operation + */ +class JA3Fingerprint extends Operation { + + /** + * JA3Fingerprint constructor + */ + constructor() { + super(); + + this.name = "JA3 Fingerprint"; + this.module = "Crypto"; + this.description = "Generates a JA3 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.

Input: A hex stream of the TLS Client Hello packet application layer."; + this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Base64", "Raw"] + }, + { + name: "Output format", + type: "option", + value: ["Hash digest", "JA3 string", "Full details"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + + input = Utils.convertToByteArray(input, inputFormat); + const s = new Stream(new Uint8Array(input)); + + const handshake = s.readInt(1); + if (handshake !== 0x16) + throw new OperationError("Not handshake data."); + + // Version + s.moveForwardsBy(2); + + // Length + const length = s.readInt(2); + if (s.length !== length + 5) + throw new OperationError("Incorrect handshake length."); + + // Handshake type + const handshakeType = s.readInt(1); + if (handshakeType !== 1) + throw new OperationError("Not a Client Hello."); + + // Handshake length + const handshakeLength = s.readInt(3); + if (s.length !== handshakeLength + 9) + throw new OperationError("Not enough data in Client Hello."); + + // Hello version + const helloVersion = s.readInt(2); + + // Random + s.moveForwardsBy(32); + + // Session ID + const sessionIDLength = s.readInt(1); + s.moveForwardsBy(sessionIDLength); + + // Cipher suites + const cipherSuitesLength = s.readInt(2); + const cipherSuites = s.getBytes(cipherSuitesLength); + const cs = new Stream(cipherSuites); + const cipherSegment = parseJA3Segment(cs, 2); + + // Compression Methods + const compressionMethodsLength = s.readInt(1); + s.moveForwardsBy(compressionMethodsLength); + + // Extensions + const extensionsLength = s.readInt(2); + const extensions = s.getBytes(extensionsLength); + const es = new Stream(extensions); + let ecsLen, ecs, ellipticCurves = "", ellipticCurvePointFormats = ""; + const exts = []; + while (es.hasMore()) { + const type = es.readInt(2); + const length = es.readInt(2); + switch (type) { + case 0x0a: // Elliptic curves + ecsLen = es.readInt(2); + ecs = new Stream(es.getBytes(ecsLen)); + ellipticCurves = parseJA3Segment(ecs, 2); + break; + case 0x0b: // Elliptic curve point formats + ecsLen = es.readInt(1); + ecs = new Stream(es.getBytes(ecsLen)); + ellipticCurvePointFormats = parseJA3Segment(ecs, 1); + break; + default: + es.moveForwardsBy(length); + } + if (!GREASE_CIPHERSUITES.includes(type)) + exts.push(type); + } + + // Output + const ja3 = [ + helloVersion.toString(), + cipherSegment, + exts.join("-"), + ellipticCurves, + ellipticCurvePointFormats + ]; + const ja3Str = ja3.join(","); + const ja3Hash = runHash("md5", Utils.strToArrayBuffer(ja3Str)); + + switch (outputFormat) { + case "JA3 string": + return ja3Str; + case "Full details": + return `Hash digest: +${ja3Hash} + +Full JA3 string: +${ja3Str} + +TLS Version: +${helloVersion.toString()} +Cipher Suites: +${cipherSegment} +Extensions: +${exts.join("-")} +Elliptic Curves: +${ellipticCurves} +Elliptic Curve Point Formats: +${ellipticCurvePointFormats}`; + case "Hash digest": + default: + return ja3Hash; + } + } + +} + +/** + * Parses a JA3 segment, returning a "-" separated list + * + * @param {Stream} stream + * @returns {string} + */ +function parseJA3Segment(stream, size=2) { + const segment = []; + while (stream.hasMore()) { + const element = stream.readInt(size); + if (!GREASE_CIPHERSUITES.includes(element)) + segment.push(element); + } + return segment.join("-"); +} + +const GREASE_CIPHERSUITES = [ + 0x0a0a, + 0x1a1a, + 0x2a2a, + 0x3a3a, + 0x4a4a, + 0x5a5a, + 0x6a6a, + 0x7a7a, + 0x8a8a, + 0x9a9a, + 0xaaaa, + 0xbaba, + 0xcaca, + 0xdada, + 0xeaea, + 0xfafa +]; + +export default JA3Fingerprint; diff --git a/src/core/operations/JA3SFingerprint.mjs b/src/core/operations/JA3SFingerprint.mjs new file mode 100644 index 00000000..9c3b88da --- /dev/null +++ b/src/core/operations/JA3SFingerprint.mjs @@ -0,0 +1,145 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * JA3S created by Salesforce + * John B. Althouse + * Jeff Atkinson + * Josh Atkins + * + * Algorithm released under the BSD-3-clause licence + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * JA3S Fingerprint operation + */ +class JA3SFingerprint extends Operation { + + /** + * JA3SFingerprint constructor + */ + constructor() { + super(); + + this.name = "JA3S Fingerprint"; + this.module = "Crypto"; + this.description = "Generates a JA3S fingerprint to help identify TLS servers based on hashing together values from the Server Hello.

Input: A hex stream of the TLS Server Hello record application layer."; + this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Base64", "Raw"] + }, + { + name: "Output format", + type: "option", + value: ["Hash digest", "JA3S string", "Full details"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + + input = Utils.convertToByteArray(input, inputFormat); + const s = new Stream(new Uint8Array(input)); + + const handshake = s.readInt(1); + if (handshake !== 0x16) + throw new OperationError("Not handshake data."); + + // Version + s.moveForwardsBy(2); + + // Length + const length = s.readInt(2); + if (s.length !== length + 5) + throw new OperationError("Incorrect handshake length."); + + // Handshake type + const handshakeType = s.readInt(1); + if (handshakeType !== 2) + throw new OperationError("Not a Server Hello."); + + // Handshake length + const handshakeLength = s.readInt(3); + if (s.length !== handshakeLength + 9) + throw new OperationError("Not enough data in Server Hello."); + + // Hello version + const helloVersion = s.readInt(2); + + // Random + s.moveForwardsBy(32); + + // Session ID + const sessionIDLength = s.readInt(1); + s.moveForwardsBy(sessionIDLength); + + // Cipher suite + const cipherSuite = s.readInt(2); + + // Compression Method + s.moveForwardsBy(1); + + // Extensions + const extensionsLength = s.readInt(2); + const extensions = s.getBytes(extensionsLength); + const es = new Stream(extensions); + const exts = []; + while (es.hasMore()) { + const type = es.readInt(2); + const length = es.readInt(2); + es.moveForwardsBy(length); + exts.push(type); + } + + // Output + const ja3s = [ + helloVersion.toString(), + cipherSuite, + exts.join("-") + ]; + const ja3sStr = ja3s.join(","); + const ja3sHash = runHash("md5", Utils.strToArrayBuffer(ja3sStr)); + + switch (outputFormat) { + case "JA3S string": + return ja3sStr; + case "Full details": + return `Hash digest: +${ja3sHash} + +Full JA3S string: +${ja3sStr} + +TLS Version: +${helloVersion.toString()} +Cipher Suite: +${cipherSuite} +Extensions: +${exts.join("-")}`; + case "Hash digest": + default: + return ja3sHash; + } + } + +} + +export default JA3SFingerprint; diff --git a/src/core/operations/JA4Fingerprint.mjs b/src/core/operations/JA4Fingerprint.mjs new file mode 100644 index 00000000..31f57525 --- /dev/null +++ b/src/core/operations/JA4Fingerprint.mjs @@ -0,0 +1,73 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {toJA4} from "../lib/JA4.mjs"; + +/** + * JA4 Fingerprint operation + */ +class JA4Fingerprint extends Operation { + + /** + * JA4Fingerprint constructor + */ + constructor() { + super(); + + this.name = "JA4 Fingerprint"; + this.module = "Crypto"; + this.description = "Generates a JA4 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.

Input: A hex stream of the TLS or QUIC Client Hello packet application layer."; + this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Base64", "Raw"] + }, + { + name: "Output format", + type: "option", + value: ["JA4", "JA4 Original Rendering", "JA4 Raw", "JA4 Raw Original Rendering", "All"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + input = Utils.convertToByteArray(input, inputFormat); + const ja4 = toJA4(new Uint8Array(input)); + + // Output + switch (outputFormat) { + case "JA4": + return ja4.JA4; + case "JA4 Original Rendering": + return ja4.JA4_o; + case "JA4 Raw": + return ja4.JA4_r; + case "JA4 Raw Original Rendering": + return ja4.JA4_ro; + case "All": + default: + return `JA4: ${ja4.JA4} +JA4_o: ${ja4.JA4_o} +JA4_r: ${ja4.JA4_r} +JA4_ro: ${ja4.JA4_ro}`; + } + } + +} + +export default JA4Fingerprint; diff --git a/src/core/operations/JA4ServerFingerprint.mjs b/src/core/operations/JA4ServerFingerprint.mjs new file mode 100644 index 00000000..662285a8 --- /dev/null +++ b/src/core/operations/JA4ServerFingerprint.mjs @@ -0,0 +1,66 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {toJA4S} from "../lib/JA4.mjs"; + +/** + * JA4Server Fingerprint operation + */ +class JA4ServerFingerprint extends Operation { + + /** + * JA4ServerFingerprint constructor + */ + constructor() { + super(); + + this.name = "JA4Server Fingerprint"; + this.module = "Crypto"; + this.description = "Generates a JA4Server Fingerprint (JA4S) to help identify TLS servers or sessions based on hashing together values from the Server Hello.

Input: A hex stream of the TLS or QUIC Server Hello packet application layer."; + this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Base64", "Raw"] + }, + { + name: "Output format", + type: "option", + value: ["JA4S", "JA4S Raw", "Both"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + input = Utils.convertToByteArray(input, inputFormat); + const ja4s = toJA4S(new Uint8Array(input)); + + // Output + switch (outputFormat) { + case "JA4S": + return ja4s.JA4S; + case "JA4S Raw": + return ja4s.JA4S_r; + case "Both": + default: + return `JA4S: ${ja4s.JA4S}\nJA4S_r: ${ja4s.JA4S_r}`; + } + } + +} + +export default JA4ServerFingerprint; diff --git a/src/core/operations/JPathExpression.mjs b/src/core/operations/JPathExpression.mjs new file mode 100644 index 00000000..c7f515d5 --- /dev/null +++ b/src/core/operations/JPathExpression.mjs @@ -0,0 +1,71 @@ +/** + * @author Matt C (matt@artemisbot.uk) + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import {JSONPath} from "jsonpath-plus"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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, jsonObj; + + try { + jsonObj = JSON.parse(input); + } catch (err) { + throw new OperationError(`Invalid input JSON: ${err.message}`); + } + + try { + results = JSONPath({ + path: query, + json: jsonObj + }); + } 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 93f22a3c..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) { - var 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) { - var 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 - }); - - var 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) { - var 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..923ae7dc --- /dev/null +++ b/src/core/operations/JSONBeautify.mjs @@ -0,0 +1,244 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author Phillip Nordwall [phillip.nordwall@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import JSON5 from "json5"; +import OperationError from "../errors/OperationError.mjs"; +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * JSON Beautify operation + */ +class JSONBeautify extends Operation { + + /** + * JSONBeautify constructor + */ + constructor() { + super(); + + this.name = "JSON Beautify"; + this.module = "Code"; + this.description = "Indents and pretty prints JavaScript Object Notation (JSON) code.

Tags: json viewer, prettify, syntax highlighting"; + this.inputType = "string"; + this.outputType = "string"; + this.presentType = "html"; + this.args = [ + { + name: "Indent string", + type: "binaryShortString", + value: " " + }, + { + name: "Sort Object Keys", + type: "boolean", + value: false + }, + { + name: "Formatted", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + + const [indentStr, sortBool] = args; + let json = null; + + try { + json = JSON5.parse(input); + } catch (err) { + throw new OperationError("Unable to parse input as JSON.\n" + err); + } + + if (sortBool) json = sortKeys(json); + + return JSON.stringify(json, null, indentStr); + } + + /** + * Adds various dynamic features to the JSON blob + * + * @param {string} data + * @param {Object[]} args + * @returns {html} + */ + present(data, args) { + const formatted = args[2]; + if (!formatted) return Utils.escapeHtml(data); + + const json = JSON5.parse(data); + const options = { + withLinks: true, + bigNumbers: true + }; + let html = '
'; + + if (isCollapsable(json)) { + const isArr = json instanceof Array; + html += '
' + + `` + + json2html(json, options) + + "
"; + } else { + html += json2html(json, options); + } + + html += "
"; + return html; + } +} + +/** + * Sort keys in a JSON object + * + * @author Phillip Nordwall [phillip.nordwall@gmail.com] + * @param {object} o + * @returns {object} + */ +function sortKeys(o) { + if (Array.isArray(o)) { + return o.map(sortKeys); + } else if ("[object Object]" === Object.prototype.toString.call(o)) { + return Object.keys(o).sort().reduce(function(a, k) { + a[k] = sortKeys(o[k]); + return a; + }, {}); + } + return o; +} + + +/** + * Check if arg is either an array with at least 1 element, or a dict with at least 1 key + * @returns {boolean} + */ +function isCollapsable(arg) { + return arg instanceof Object && Object.keys(arg).length > 0; +} + +/** + * Check if a string looks like a URL, based on protocol + * @returns {boolean} + */ +function isUrl(string) { + const protocols = ["http", "https", "ftp", "ftps"]; + for (let i = 0; i < protocols.length; i++) { + if (string.startsWith(protocols[i] + "://")) { + return true; + } + } + return false; +} + +/** + * Transform a json object into html representation + * + * Adapted for CyberChef by @n1474335 from jQuery json-viewer + * @author Alexandre Bodelot + * @link https://github.com/abodelot/jquery.json-viewer + * @license MIT + * + * @returns {string} + */ +function json2html(json, options) { + let html = ""; + if (typeof json === "string") { + // Escape tags and quotes + json = Utils.escapeHtml(json); + + if (options.withLinks && isUrl(json)) { + html += `${json}`; + } else { + // Escape double quotes in the rendered non-URL string. + json = json.replace(/"/g, "\\""); + html += `"${json}"`; + } + } else if (typeof json === "number" || typeof json === "bigint") { + html += `${json}`; + } else if (typeof json === "boolean") { + html += `${json}`; + } else if (json === null) { + html += 'null'; + } else if (json instanceof Array) { + if (json.length > 0) { + html += '[
    '; + for (let i = 0; i < json.length; i++) { + html += "
  1. "; + + // Add toggle button if item is collapsable + if (isCollapsable(json[i])) { + const isArr = json[i] instanceof Array; + html += '
    ' + + `` + + json2html(json[i], options) + + "
    "; + } else { + html += json2html(json[i], options); + } + + // Add comma if item is not last + if (i < json.length - 1) { + html += ','; + } + html += "
  2. "; + } + html += '
]'; + } else { + html += '[]'; + } + } else if (typeof json === "object") { + // Optional support different libraries for big numbers + // json.isLosslessNumber: package lossless-json + // json.toExponential(): packages bignumber.js, big.js, decimal.js, decimal.js-light, others? + if (options.bigNumbers && (typeof json.toExponential === "function" || json.isLosslessNumber)) { + html += `${json.toString()}`; + } else { + let keyCount = Object.keys(json).length; + if (keyCount > 0) { + html += '{
    '; + for (const key in json) { + if (Object.prototype.hasOwnProperty.call(json, key)) { + const safeKey = Utils.escapeHtml(key); + html += "
  • "; + + // Add toggle button if item is collapsable + if (isCollapsable(json[key])) { + const isArr = json[key] instanceof Array; + html += '
    ' + + `${safeKey}: ` + + json2html(json[key], options) + + "
    "; + } else { + html += safeKey + ': ' + json2html(json[key], options); + } + + // Add comma if item is not last + if (--keyCount > 0) { + html += ','; + } + html += "
  • "; + } + } + html += '
}'; + } else { + html += '{}'; + } + } + } + return html; +} + +export default JSONBeautify; diff --git a/src/core/operations/JSONMinify.mjs b/src/core/operations/JSONMinify.mjs new file mode 100644 index 00000000..7f0c6f2b --- /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.mjs"; + +/** + * 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..875ff6e8 --- /dev/null +++ b/src/core/operations/JSONToCSV.mjs @@ -0,0 +1,142 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import * as flat from "flat"; +const flatten = flat.default ? flat.default.flatten : flat.flatten; + +/** + * 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" + } + ]; + } + + /** + * Converts JSON to a CSV equivalent. + * + * @param {boolean} force - Whether to force conversion of data to fit in a cell + * @returns {string} + */ + toCSV(force=false) { + const self = this; + // If the JSON is an array of arrays, this is easy + if (this.flattened[0] instanceof Array) { + return this.flattened + .map(row => row + .map(d => self.escapeCellContents(d, force)) + .join(this.cellDelim) + ) + .join(this.rowDelim) + + this.rowDelim; + } + + // If it's an array of dictionaries... + const header = Object.keys(this.flattened[0]); + return header + .map(d => self.escapeCellContents(d, force)) + .join(this.cellDelim) + + this.rowDelim + + this.flattened + .map(row => header + .map(h => row[h]) + .map(d => self.escapeCellContents(d, force)) + .join(this.cellDelim) + ) + .join(this.rowDelim) + + this.rowDelim; + } + + /** + * @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; + this.flattened = input; + if (!(this.flattened instanceof Array)) { + this.flattened = [input]; + } + + try { + return this.toCSV(); + } catch (err) { + try { + this.flattened = flatten(input); + if (!(this.flattened instanceof Array)) { + this.flattened = [this.flattened]; + } + return this.toCSV(true); + } 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 + * @param {boolean} force - Whether to force conversion of data to fit in a cell + * @returns {string} + */ + escapeCellContents(data, force=false) { + if (data !== "string") { + const isPrimitive = data == null || typeof data !== "object"; + if (isPrimitive) data = `${data}`; + else if (force) data = JSON.stringify(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 must 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/JSONtoYAML.mjs b/src/core/operations/JSONtoYAML.mjs new file mode 100644 index 00000000..0d4cc626 --- /dev/null +++ b/src/core/operations/JSONtoYAML.mjs @@ -0,0 +1,46 @@ +/** + * @author ccarpo [ccarpo@gmx.net] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import YAML from "yaml"; + +/** + * JSON to YAML operation + */ +class JSONtoYAML extends Operation { + + /** + * JSONtoYAML constructor + */ + constructor() { + super(); + + this.name = "JSON to YAML"; + this.module = "Default"; + this.description = "Format a JSON object into YAML"; + this.infoURL = "https://en.wikipedia.org/wiki/YAML"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + try { + return YAML.stringify(input); + } catch (err) { + throw new OperationError("Test"); + } + } + +} + +export default JSONtoYAML; diff --git a/src/core/operations/JWKToPem.mjs b/src/core/operations/JWKToPem.mjs new file mode 100644 index 00000000..c8c00270 --- /dev/null +++ b/src/core/operations/JWKToPem.mjs @@ -0,0 +1,80 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * PEM to JWK operation + */ +class PEMToJWK extends Operation { + + /** + * PEMToJWK constructor + */ + constructor() { + super(); + + this.name = "JWK to PEM"; + this.module = "PublicKey"; + this.description = "Converts Keys in JSON Web Key format to PEM format (PKCS#8)."; + this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + "pattern": "\"kty\":\\s*\"(EC|RSA)\"", + "flags": "gm", + "args": [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const inputJson = JSON.parse(input); + + let keys = []; + if (Array.isArray(inputJson)) { + // list of keys => transform all keys + keys = inputJson; + } else if (Array.isArray(inputJson.keys)) { + // JSON Web Key Set => transform all keys + keys = inputJson.keys; + } else if (typeof inputJson === "object") { + // single key + keys.push(inputJson); + } else { + throw new OperationError("Input is not a JSON Web Key"); + } + + let output = ""; + for (let i=0; i" + 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..bfa73f6a --- /dev/null +++ b/src/core/operations/JavaScriptMinify.mjs @@ -0,0 +1,45 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; +import Operation from "../Operation.mjs"; +import * as terser from "terser"; + +/** + * 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} + */ + async run(input, args) { + const result = await terser.minify(input); + if (result.error) { + throw new OperationError(`Error minifying JavaScript. (${result.error})`); + } + return result.code; + } + +} + +export default JavaScriptMinify; diff --git a/src/core/operations/JavaScriptParser.mjs b/src/core/operations/JavaScriptParser.mjs new file mode 100644 index 00000000..2fb13891 --- /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.mjs"; +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://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/Jq.mjs b/src/core/operations/Jq.mjs new file mode 100644 index 00000000..c1e02b34 --- /dev/null +++ b/src/core/operations/Jq.mjs @@ -0,0 +1,57 @@ +/** + * @author zhzy0077 [zhzy0077@hotmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import jq from "jq-web"; + +/** + * jq operation + */ +class Jq extends Operation { + + /** + * Jq constructor + */ + constructor() { + super(); + + this.name = "Jq"; + this.module = "Jq"; + this.description = "jq is a lightweight and flexible command-line JSON processor."; + this.infoURL = "https://github.com/jqlang/jq"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = [ + { + name: "Query", + type: "string", + value: "" + } + ]; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [query] = args; + let result; + + try { + result = jq.json(input, query); + } catch (err) { + throw new OperationError(`Invalid jq expression: ${err.message}`); + } + + return JSON.stringify(result); + } + +} + +export default Jq; diff --git a/src/core/operations/Jump.mjs b/src/core/operations/Jump.mjs new file mode 100644 index 00000000..26a2c922 --- /dev/null +++ b/src/core/operations/Jump.mjs @@ -0,0 +1,66 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { getLabelIndex } from "../lib/FlowControl.mjs"; + +/** + * 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) { + state.numJumps = 0; + 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..151a4e82 --- /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.mjs"; +import JSSHA3 from "js-sha3"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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/LMHash.mjs b/src/core/operations/LMHash.mjs new file mode 100644 index 00000000..2bedf0e8 --- /dev/null +++ b/src/core/operations/LMHash.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {smbhash} from "ntlm"; + +/** + * LM Hash operation + */ +class LMHash extends Operation { + + /** + * LMHash constructor + */ + constructor() { + super(); + + this.name = "LM Hash"; + this.module = "Crypto"; + this.description = "An LM Hash, or LAN Manager Hash, is a deprecated way of storing passwords on old Microsoft operating systems. It is particularly weak and can be cracked in seconds on modern hardware using rainbow tables."; + this.infoURL = "https://wikipedia.org/wiki/LAN_Manager#Password_hashing_algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return smbhash.lmhash(input); + } + +} + +export default LMHash; diff --git a/src/core/operations/LS47Decrypt.mjs b/src/core/operations/LS47Decrypt.mjs new file mode 100644 index 00000000..d5764d7f --- /dev/null +++ b/src/core/operations/LS47Decrypt.mjs @@ -0,0 +1,57 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import * as LS47 from "../lib/LS47.mjs"; + +/** + * LS47 Decrypt operation + */ +class LS47Decrypt extends Operation { + + /** + * LS47Decrypt constructor + */ + constructor() { + super(); + + this.name = "LS47 Decrypt"; + this.module = "Crypto"; + this.description = "This is a slight improvement of the ElsieFour cipher as described by Alan Kaminsky. We use 7x7 characters instead of original (barely fitting) 6x6, to be able to encrypt some structured information. We also describe a simple key-expansion algorithm, because remembering passwords is popular. Similar security considerations as with ElsieFour hold.
The LS47 alphabet consists of following characters: _abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()
An LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption."; + this.infoURL = "https://github.com/exaexa/ls47"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Password", + type: "string", + value: "" + }, + { + name: "Padding", + type: "number", + value: 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + this.paddingSize = parseInt(args[1], 10); + + LS47.initTiles(); + + const key = LS47.deriveKey(args[0]); + return LS47.decryptPad(key, input, this.paddingSize); + } + +} + +export default LS47Decrypt; diff --git a/src/core/operations/LS47Encrypt.mjs b/src/core/operations/LS47Encrypt.mjs new file mode 100644 index 00000000..02f7d994 --- /dev/null +++ b/src/core/operations/LS47Encrypt.mjs @@ -0,0 +1,62 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import * as LS47 from "../lib/LS47.mjs"; + +/** + * LS47 Encrypt operation + */ +class LS47Encrypt extends Operation { + + /** + * LS47Encrypt constructor + */ + constructor() { + super(); + + this.name = "LS47 Encrypt"; + this.module = "Crypto"; + this.description = "This is a slight improvement of the ElsieFour cipher as described by Alan Kaminsky. We use 7x7 characters instead of original (barely fitting) 6x6, to be able to encrypt some structured information. We also describe a simple key-expansion algorithm, because remembering passwords is popular. Similar security considerations as with ElsieFour hold.
The LS47 alphabet consists of following characters: _abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()
A LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption."; + this.infoURL = "https://github.com/exaexa/ls47"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Password", + type: "string", + value: "" + }, + { + name: "Padding", + type: "number", + value: 10 + }, + { + name: "Signature", + type: "string", + value: "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + this.paddingSize = parseInt(args[1], 10); + + LS47.initTiles(); + + const key = LS47.deriveKey(args[0]); + return LS47.encryptPad(key, input, args[2], this.paddingSize); + } + +} + +export default LS47Encrypt; diff --git a/src/core/operations/LZ4Compress.mjs b/src/core/operations/LZ4Compress.mjs new file mode 100644 index 00000000..1f43785f --- /dev/null +++ b/src/core/operations/LZ4Compress.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import lz4 from "lz4js"; + +/** + * LZ4 Compress operation + */ +class LZ4Compress extends Operation { + + /** + * LZ4Compress constructor + */ + constructor() { + super(); + + this.name = "LZ4 Compress"; + this.module = "Compression"; + this.description = "LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes."; + this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inBuf = new Uint8Array(input); + const compressed = lz4.compress(inBuf); + return compressed.buffer; + } + +} + +export default LZ4Compress; diff --git a/src/core/operations/LZ4Decompress.mjs b/src/core/operations/LZ4Decompress.mjs new file mode 100644 index 00000000..2ba50416 --- /dev/null +++ b/src/core/operations/LZ4Decompress.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import lz4 from "lz4js"; + +/** + * LZ4 Decompress operation + */ +class LZ4Decompress extends Operation { + + /** + * LZ4Decompress constructor + */ + constructor() { + super(); + + this.name = "LZ4 Decompress"; + this.module = "Compression"; + this.description = "LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes."; + this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inBuf = new Uint8Array(input); + const decompressed = lz4.decompress(inBuf); + return decompressed.buffer; + } + +} + +export default LZ4Decompress; diff --git a/src/core/operations/LZMACompress.mjs b/src/core/operations/LZMACompress.mjs new file mode 100644 index 00000000..5a252db2 --- /dev/null +++ b/src/core/operations/LZMACompress.mjs @@ -0,0 +1,64 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +import { compress } from "@blu3r4y/lzma"; +import {isWorkerEnvironment} from "../Utils.mjs"; + +/** + * LZMA Compress operation + */ +class LZMACompress extends Operation { + + /** + * LZMACompress constructor + */ + constructor() { + super(); + + this.name = "LZMA Compress"; + this.module = "Compression"; + this.description = "Compresses data using the Lempel\u2013Ziv\u2013Markov chain algorithm. Compression mode determines the speed and effectiveness of the compression: 1 is fastest and less effective, 9 is slowest and most effective"; + this.infoURL = "https://wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Compression Mode", + type: "option", + value: [ + "1", "2", "3", "4", "5", "6", "7", "8", "9" + ], + "defaultIndex": 6 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + const mode = Number(args[0]); + return new Promise((resolve, reject) => { + compress(new Uint8Array(input), mode, (result, error) => { + if (error) { + reject(new OperationError(`Failed to compress input: ${error.message}`)); + } + // The compression returns as an Int8Array, but we can just get the unsigned data from the buffer + resolve(new Int8Array(result).buffer); + }, (percent) => { + if (isWorkerEnvironment()) self.sendStatusMessage(`Compressing input: ${(percent*100).toFixed(2)}%`); + }); + }); + } + +} + +export default LZMACompress; diff --git a/src/core/operations/LZMADecompress.mjs b/src/core/operations/LZMADecompress.mjs new file mode 100644 index 00000000..3bebb860 --- /dev/null +++ b/src/core/operations/LZMADecompress.mjs @@ -0,0 +1,57 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {decompress} from "@blu3r4y/lzma"; +import Utils, {isWorkerEnvironment} from "../Utils.mjs"; + +/** + * LZMA Decompress operation + */ +class LZMADecompress extends Operation { + + /** + * LZMADecompress constructor + */ + constructor() { + super(); + + this.name = "LZMA Decompress"; + this.module = "Compression"; + this.description = "Decompresses data using the Lempel-Ziv-Markov chain Algorithm."; + this.infoURL = "https://wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + return new Promise((resolve, reject) => { + decompress(new Uint8Array(input), (result, error) => { + if (error) { + reject(new OperationError(`Failed to decompress input: ${error.message}`)); + } + // The decompression returns either a String or an untyped unsigned int8 array, but we can just get the unsigned data from the buffer + + if (typeof result == "string") { + resolve(Utils.strToArrayBuffer(result)); + } else { + resolve(new Int8Array(result).buffer); + } + }, (percent) => { + if (isWorkerEnvironment()) self.sendStatusMessage(`Decompressing input: ${(percent*100).toFixed(2)}%`); + }); + }); + } + +} + +export default LZMADecompress; diff --git a/src/core/operations/LZNT1Decompress.mjs b/src/core/operations/LZNT1Decompress.mjs new file mode 100644 index 00000000..b5308e77 --- /dev/null +++ b/src/core/operations/LZNT1Decompress.mjs @@ -0,0 +1,41 @@ +/** + * @author 0xThiebaut [thiebaut.dev] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {decompress} from "../lib/LZNT1.mjs"; + +/** + * LZNT1 Decompress operation + */ +class LZNT1Decompress extends Operation { + + /** + * LZNT1 Decompress constructor + */ + constructor() { + super(); + + this.name = "LZNT1 Decompress"; + this.module = "Compression"; + this.description = "Decompresses data using the LZNT1 algorithm.

Similar to the Windows API RtlDecompressBuffer."; + this.infoURL = "https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-xca/5655f4a3-6ba4-489b-959f-e1f407c52f15"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + return decompress(input); + } + +} + +export default LZNT1Decompress; diff --git a/src/core/operations/LZStringCompress.mjs b/src/core/operations/LZStringCompress.mjs new file mode 100644 index 00000000..11d5710a --- /dev/null +++ b/src/core/operations/LZStringCompress.mjs @@ -0,0 +1,55 @@ +/** + * @author crespyl [peter@crespyl.net] + * @copyright Peter Jacobs 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +import {COMPRESSION_OUTPUT_FORMATS, COMPRESSION_FUNCTIONS} from "../lib/LZString.mjs"; + +/** + * LZString Compress operation + */ +class LZStringCompress extends Operation { + + /** + * LZStringCompress constructor + */ + constructor() { + super(); + + this.name = "LZString Compress"; + this.module = "Compression"; + this.description = "Compress the input with lz-string."; + this.infoURL = "https://pieroxy.net/blog/pages/lz-string/index.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Compression Format", + type: "option", + defaultIndex: 0, + value: COMPRESSION_OUTPUT_FORMATS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const compress = COMPRESSION_FUNCTIONS[args[0]]; + if (compress) { + return compress(input); + } else { + throw new OperationError("Unable to find compression function"); + } + } + +} + +export default LZStringCompress; diff --git a/src/core/operations/LZStringDecompress.mjs b/src/core/operations/LZStringDecompress.mjs new file mode 100644 index 00000000..87d0d85b --- /dev/null +++ b/src/core/operations/LZStringDecompress.mjs @@ -0,0 +1,56 @@ +/** + * @author crespyl [peter@crespyl.net] + * @copyright Peter Jacobs 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +import {COMPRESSION_OUTPUT_FORMATS, DECOMPRESSION_FUNCTIONS} from "../lib/LZString.mjs"; + +/** + * LZString Decompress operation + */ +class LZStringDecompress extends Operation { + + /** + * LZStringDecompress constructor + */ + constructor() { + super(); + + this.name = "LZString Decompress"; + this.module = "Compression"; + this.description = "Decompresses data that was compressed with lz-string."; + this.infoURL = "https://pieroxy.net/blog/pages/lz-string/index.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Compression Format", + type: "option", + defaultIndex: 0, + value: COMPRESSION_OUTPUT_FORMATS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const decompress = DECOMPRESSION_FUNCTIONS[args[0]]; + if (decompress) { + return decompress(input); + } else { + throw new OperationError("Unable to find decompression function"); + } + } + + +} + +export default LZStringDecompress; diff --git a/src/core/operations/Label.mjs b/src/core/operations/Label.mjs new file mode 100644 index 00000000..fb13d803 --- /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.mjs"; + +/** + * 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/LevenshteinDistance.mjs b/src/core/operations/LevenshteinDistance.mjs new file mode 100644 index 00000000..b9d30aa0 --- /dev/null +++ b/src/core/operations/LevenshteinDistance.mjs @@ -0,0 +1,98 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Levenshtein Distance operation + */ +class LevenshteinDistance extends Operation { + + /** + * LevenshteinDistance constructor + */ + constructor() { + super(); + + this.name = "Levenshtein Distance"; + this.module = "Default"; + this.description = "Levenshtein Distance (also known as Edit Distance) is a string metric to measure a difference between two strings that counts operations (insertions, deletions, and substitutions) on single character that are required to change one string to another."; + this.infoURL = "https://wikipedia.org/wiki/Levenshtein_distance"; + this.inputType = "string"; + this.outputType = "number"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n" + }, + { + name: "Insertion cost", + type: "number", + value: 1 + }, + { + name: "Deletion cost", + type: "number", + value: 1 + }, + { + name: "Substitution cost", + type: "number", + value: 1 + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const [delim, insCost, delCost, subCost] = args; + const samples = input.split(delim); + if (samples.length !== 2) { + throw new OperationError("Incorrect number of samples. Check your input and/or delimiter."); + } + if (insCost < 0 || delCost < 0 || subCost < 0) { + throw new OperationError("Negative costs are not allowed."); + } + const src = samples[0], dest = samples[1]; + let currentCost = new Array(src.length + 1); + let nextCost = new Array(src.length + 1); + for (let i = 0; i < currentCost.length; i++) { + currentCost[i] = delCost * i; + } + for (let i = 0; i < dest.length; i++) { + const destc = dest.charAt(i); + nextCost[0] = currentCost[0] + insCost; + for (let j = 0; j < src.length; j++) { + let candidate; + // insertion + let optCost = currentCost[j + 1] + insCost; + // deletion + candidate = nextCost[j] + delCost; + if (candidate < optCost) optCost = candidate; + // substitution or matched character + candidate = currentCost[j]; + if (src.charAt(j) !== destc) candidate += subCost; + if (candidate < optCost) optCost = candidate; + // store calculated cost + nextCost[j + 1] = optCost; + } + const tempCost = nextCost; + nextCost = currentCost; + currentCost = tempCost; + } + + return currentCost[currentCost.length - 1]; + } + +} + +export default LevenshteinDistance; diff --git a/src/core/operations/Lorenz.mjs b/src/core/operations/Lorenz.mjs new file mode 100644 index 00000000..28ac3bf7 --- /dev/null +++ b/src/core/operations/Lorenz.mjs @@ -0,0 +1,763 @@ +/** + * Emulation of the Lorenz SZ40/42a/42b cipher attachment. + * + * Tested against the Colossus Rebuild at Bletchley Park's TNMOC + * using a variety of inputs and settings to confirm correctness. + * + * @author VirtualColossus [martin@virtualcolossus.co.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Lorenz operation + */ +class Lorenz extends Operation { + + /** + * Lorenz constructor + */ + constructor() { + super(); + + this.name = "Lorenz"; + this.module = "Bletchley"; + this.description = "The Lorenz SZ40/42 cipher attachment was a WW2 German rotor cipher machine with twelve rotors which attached in-line between remote teleprinters.

It used the Vernam cipher with two groups of five rotors (named the psi(ψ) wheels and chi(χ) wheels at Bletchley Park) to create two pseudorandom streams of five bits, encoded in ITA2, which were XOR added to the plaintext. Two other rotors, dubbed the mu(μ) or motor wheels, could hold up the stepping of the psi wheels meaning they stepped intermittently.

Each rotor has a different number of cams/lugs around their circumference which could be set active or inactive changing the key stream.

Three models of the Lorenz are emulated, SZ40, SZ42a and SZ42b and three example wheel patterns (the lug settings) are included (KH, ZMUG & BREAM) with the option to set a custom set using the letter 'x' for active or '.' for an inactive lug.

The input can either be plaintext or ITA2 when sending and ITA2 when receiving.

To learn more, Virtual Lorenz, an online, browser based simulation of the Lorenz SZ40/42 is available at lorenz.virtualcolossus.co.uk.

A more detailed description of this operation can be found here."; + this.infoURL = "https://wikipedia.org/wiki/Lorenz_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Model", + type: "option", + value: ["SZ40", "SZ42a", "SZ42b"] + }, + { + name: "Wheel Pattern", + type: "argSelector", + value: [ + { + name: "KH Pattern", + off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + }, + { + name: "ZMUG Pattern", + off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + }, + { + name: "BREAM Pattern", + off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + }, + { + name: "No Pattern", + off: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + }, + { + name: "Custom", + on: [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + } + ] + }, + { + name: "KT-Schalter", + type: "boolean", + value: false + }, + { + name: "Mode", + type: "argSelector", + value: [ + { + name: "Send", + on: [4], + off: [5] + }, + { + name: "Receive", + off: [4], + on: [5] + } + ] + }, + { + name: "Input Type", + type: "option", + value: ["Plaintext", "ITA2"] + }, + { + name: "Output Type", + type: "option", + value: ["Plaintext", "ITA2"] + }, + { + name: "ITA2 Format", + type: "option", + value: ["5/8/9", "+/-/."] + }, + { + name: "Ψ1 start (1-43)", + type: "number", + value: 1 + }, + { + name: "Ψ2 start (1-47)", + type: "number", + value: 1 + }, + { + name: "Ψ3 start (1-51)", + type: "number", + value: 1 + }, + { + name: "Ψ4 start (1-53)", + type: "number", + value: 1 + }, + { + name: "Ψ5 start (1-59)", + type: "number", + value: 1 + }, + { + name: "Μ37 start (1-37)", + type: "number", + value: 1 + }, + { + name: "Μ61 start (1-61)", + type: "number", + value: 1 + }, + { + name: "Χ1 start (1-41)", + type: "number", + value: 1 + }, + { + name: "Χ2 start (1-31)", + type: "number", + value: 1 + }, + { + name: "Χ3 start (1-29)", + type: "number", + value: 1 + }, + { + name: "Χ4 start (1-26)", + type: "number", + value: 1 + }, + { + name: "Χ5 start (1-23)", + type: "number", + value: 1 + }, + { + name: "Ψ1 lugs (43)", + type: "string", + value: ".x...xx.x.x..xxx.x.x.xxxx.x.x.x.x.x..x.xx.x" + }, + { + name: "Ψ2 lugs (47)", + type: "string", + value: ".xx.x.xxx..x.x.x..x.xx.x.xxx.x....x.xx.x.x.x..x" + }, + { + name: "Ψ3 lugs (51)", + type: "string", + value: ".x.x.x..xxx....x.x.xx.x.x.x..xxx.x.x..x.x.xx..x.x.x" + }, + { + name: "Ψ4 lugs (53)", + type: "string", + value: ".xx...xxxxx.x.x.xx...x.xx.x.x..x.x.xx.x..x.x.x.x.x.x." + }, + { + name: "Ψ5 lugs (59)", + type: "string", + value: "xx...xx.x..x.xx.x...x.x.x.x.x.x.x.x.xx..xxxx.x.x...xx.x..x." + }, + { + name: "Μ37 lugs (37)", + type: "string", + value: "x.x.x.x.x.x...x.x.x...x.x.x...x.x...." + }, + { + name: "Μ61 lugs (61)", + type: "string", + value: ".xxxx.xxxx.xxx.xxxx.xx....xxx.xxxx.xxxx.xxxx.xxxx.xxx.xxxx..." + }, + { + name: "Χ1 lugs (41)", + type: "string", + value: ".x...xxx.x.xxxx.x...x.x..xxx....xx.xxxx.." + }, + { + name: "Χ2 lugs (31)", + type: "string", + value: "x..xxx...x.xxxx..xx..x..xx.xx.." + }, + { + name: "Χ3 lugs (29)", + type: "string", + value: "..xx..x.xxx...xx...xx..xx.xx." + }, + { + name: "Χ4 lugs (26)", + type: "string", + value: "xx..x..xxxx..xx.xxx....x.." + }, + { + name: "Χ5 lugs (23)", + type: "string", + value: "xx..xx....xxxx.x..x.x.." + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + const model = args[0], + pattern = args[1], + kt = args[2], + mode = args[3], + intype = args[4], + outtype = args[5], + format = args[6], + lugs1 = args[19], + lugs2 = args[20], + lugs3 = args[21], + lugs4 = args[22], + lugs5 = args[23], + lugm37 = args[24], + lugm61 = args[25], + lugx1 = args[26], + lugx2 = args[27], + lugx3 = args[28], + lugx4 = args[29], + lugx5 = args[30]; + + let s1 = args[7], + s2 = args[8], + s3 = args[9], + s4 = args[10], + s5 = args[11], + m37 = args[12], + m61 = args[13], + x1 = args[14], + x2 = args[15], + x3 = args[16], + x4 = args[17], + x5 = args[18]; + + this.reverseTable(); + + if (s1<1 || s1>43) throw new OperationError("Ψ1 start must be between 1 and 43"); + if (s2<1 || s2>47) throw new OperationError("Ψ2 start must be between 1 and 47"); + if (s3<1 || s3>51) throw new OperationError("Ψ3 start must be between 1 and 51"); + if (s4<1 || s4>53) throw new OperationError("Ψ4 start must be between 1 and 53"); + if (s5<1 || s5>59) throw new OperationError("Ψ5 start must be between 1 and 59"); + if (m37<1 || m37>37) throw new OperationError("Μ37 start must be between 1 and 37"); + if (m61<1 || m61>61) throw new OperationError("Μ61 start must be between 1 and 61"); + if (x1<1 || x1>41) throw new OperationError("Χ1 start must be between 1 and 41"); + if (x2<1 || x2>31) throw new OperationError("Χ2 start must be between 1 and 31"); + if (x3<1 || x3>29) throw new OperationError("Χ3 start must be between 1 and 29"); + if (x4<1 || x4>26) throw new OperationError("Χ4 start must be between 1 and 26"); + if (x5<1 || x5>23) throw new OperationError("Χ5 start must be between 1 and 23"); + + // Initialise chosen wheel pattern + let chosenSetting = ""; + if (pattern === "Custom") { + const re = new RegExp("^[.xX]*$"); + if (lugs1.length !== 43 || !re.test(lugs1)) throw new OperationError("Ψ1 custom lugs must be 43 long and can only include . or x "); + if (lugs2.length !== 47 || !re.test(lugs2)) throw new OperationError("Ψ2 custom lugs must be 47 long and can only include . or x"); + if (lugs3.length !== 51 || !re.test(lugs3)) throw new OperationError("Ψ3 custom lugs must be 51 long and can only include . or x"); + if (lugs4.length !== 53 || !re.test(lugs4)) throw new OperationError("Ψ4 custom lugs must be 53 long and can only include . or x"); + if (lugs5.length !== 59 || !re.test(lugs5)) throw new OperationError("Ψ5 custom lugs must be 59 long and can only include . or x"); + if (lugm37.length !== 37 || !re.test(lugm37)) throw new OperationError("M37 custom lugs must be 37 long and can only include . or x"); + if (lugm61.length !== 61 || !re.test(lugm61)) throw new OperationError("M61 custom lugs must be 61 long and can only include . or x"); + if (lugx1.length !== 41 || !re.test(lugx1)) throw new OperationError("Χ1 custom lugs must be 41 long and can only include . or x"); + if (lugx2.length !== 31 || !re.test(lugx2)) throw new OperationError("Χ2 custom lugs must be 31 long and can only include . or x"); + if (lugx3.length !== 29 || !re.test(lugx3)) throw new OperationError("Χ3 custom lugs must be 29 long and can only include . or x"); + if (lugx4.length !== 26 || !re.test(lugx4)) throw new OperationError("Χ4 custom lugs must be 26 long and can only include . or x"); + if (lugx5.length !== 23 || !re.test(lugx5)) throw new OperationError("Χ5 custom lugs must be 23 long and can only include . or x"); + chosenSetting = INIT_PATTERNS["No Pattern"]; + chosenSetting.S[1] = this.readLugs(lugs1); + chosenSetting.S[2] = this.readLugs(lugs2); + chosenSetting.S[3] = this.readLugs(lugs3); + chosenSetting.S[4] = this.readLugs(lugs4); + chosenSetting.S[5] = this.readLugs(lugs5); + chosenSetting.M[1] = this.readLugs(lugm61); + chosenSetting.M[2] = this.readLugs(lugm37); + chosenSetting.X[1] = this.readLugs(lugx1); + chosenSetting.X[2] = this.readLugs(lugx2); + chosenSetting.X[3] = this.readLugs(lugx3); + chosenSetting.X[4] = this.readLugs(lugx4); + chosenSetting.X[5] = this.readLugs(lugx5); + } else { + chosenSetting = INIT_PATTERNS[pattern]; + } + const chiSettings = chosenSetting.X; // Pin settings for Chi links (X) + const psiSettings = chosenSetting.S; // Pin settings for Psi links (S) + const muSettings = chosenSetting.M; // Pin settings for Motor links (M) + + // Convert input text to ITA2 (including figure/letter shifts) + const ita2Input = this.convertToITA2(input, intype, mode); + + let thisPsi = []; + let thisChi = []; + let m61lug = muSettings[1][m61-1]; + let m37lug = muSettings[2][m37-1]; + const p5 = [0, 0, 0]; + + const self = this; + const letters = Array.prototype.map.call(ita2Input, function(character) { + const letter = character.toUpperCase(); + + // Store lugs used in limitations, need these later + let x2bptr = x2+1; + if (x2bptr===32) x2bptr=1; + let s1bptr = s1+1; + if (s1bptr===44) s1bptr=1; + + thisChi = [ + chiSettings[1][x1-1], + chiSettings[2][x2-1], + chiSettings[3][x3-1], + chiSettings[4][x4-1], + chiSettings[5][x5-1] + ]; + + thisPsi = [ + psiSettings[1][s1-1], + psiSettings[2][s2-1], + psiSettings[3][s3-1], + psiSettings[4][s4-1], + psiSettings[5][s5-1] + ]; + + if (typeof ITA2_TABLE[letter] == "undefined") { + return ""; + } + + // The encipher calculation + + // We calculate Bitwise XOR for each of the 5 bits across our input ( K XOR Psi XOR Chi ) + const xorSum = []; + for (let i=0;i<=4;i++) { + xorSum[i] = ITA2_TABLE[letter][i] ^ thisPsi[i] ^ thisChi[i]; + } + const resultStr = xorSum.join(""); + + // Wheel movement + + // Chi wheels always move one back after each letter + if (--x1 < 1) x1 = 41; + if (--x2 < 1) x2 = 31; + if (--x3 < 1) x3 = 29; + if (--x4 < 1) x4 = 26; + if (--x5 < 1) x5 = 23; + + // Motor wheel (61 pin) also moves one each letter + if (--m61 < 1) m61 = 61; + + // If M61 is set, we also move M37 + if (m61lug === 1) { + if (--m37 < 1) m37 = 37; + } + + // Psi wheels only move sometimes, dependent on M37 current setting and limitations + + const basicmotor = m37lug; + let totalmotor; + let lim = 0; + + p5[2] = p5[1]; + p5[1] = p5[0]; + if (mode==="Send") { + p5[0] = parseInt(ITA2_TABLE[letter][4], 10); + } else { + p5[0] = parseInt(xorSum[4], 10); + } + + // Limitations here + if (model==="SZ42a") { + // Chi 2 one back lim - The active character of Chi 2 (2nd Chi wheel) in the previous position + lim = parseInt(chiSettings[2][x2bptr-1], 10); + if (kt) { + // p5 back 2 + if (lim===p5[2]) { + lim = 0; + } else { + lim=1; + } + } + + // If basic motor = 0 and limitation = 1, Total motor = 0 [no move], otherwise, total motor = 1 [move] + if (basicmotor===0 && lim===1) { + totalmotor = 0; + } else { + totalmotor = 1; + } + + } else if (model==="SZ42b") { + // Chi 2 one back + Psi 1 one back. + const x2b1lug = parseInt(chiSettings[2][x2bptr-1], 10); + const s1b1lug = parseInt(psiSettings[1][s1bptr-1], 10); + lim = 1; + if (x2b1lug===s1b1lug) lim=0; + if (kt) { + // p5 back 2 + if (lim===p5[2]) { + lim=0; + } else { + lim=1; + } + } + // If basic motor = 0 and limitation = 1, Total motor = 0 [no move], otherwise, total motor = 1 [move] + if (basicmotor===0 && lim===1) { + totalmotor = 0; + } else { + totalmotor = 1; + } + + } else if (model==="SZ40") { + // SZ40 - just move based on the M37 motor wheel + totalmotor = basicmotor; + } else { + throw new OperationError("Lorenz model type not recognised"); + } + + // Move the Psi wheels when current totalmotor active + if (totalmotor === 1) { + if (--s1 < 1) s1 = 43; + if (--s2 < 1) s2 = 47; + if (--s3 < 1) s3 = 51; + if (--s4 < 1) s4 = 53; + if (--s5 < 1) s5 = 59; + } + + m61lug = muSettings[1][m61-1]; + m37lug = muSettings[2][m37-1]; + + let rtnstr = self.REVERSE_ITA2_TABLE[resultStr]; + if (format==="5/8/9") { + if (rtnstr==="+") rtnstr="5"; // + or 5 used to represent figure shift + if (rtnstr==="-") rtnstr="8"; // - or 8 used to represent letter shift + if (rtnstr===".") rtnstr="9"; // . or 9 used to represent space + } + return rtnstr; + }); + + const ita2output = letters.join(""); + + return this.convertFromITA2(ita2output, outtype, mode); + + } + + /** + * Reverses the ITA2 Code lookup table + */ + reverseTable() { + this.REVERSE_ITA2_TABLE = {}; + this.REVERSE_FIGSHIFT_TABLE = {}; + + for (const letter in ITA2_TABLE) { + const code = ITA2_TABLE[letter]; + this.REVERSE_ITA2_TABLE[code] = letter; + } + for (const letter in figShiftArr) { + const ltr = figShiftArr[letter]; + this.REVERSE_FIGSHIFT_TABLE[ltr] = letter; + } + } + + /** + * Read lugs settings - convert to 0|1 + */ + readLugs(lugstr) { + const arr = Array.prototype.map.call(lugstr, function(lug) { + if (lug===".") { + return 0; + } else { + return 1; + } + }); + return arr; + } + + /** + * Convert input plaintext to ITA2 + */ + convertToITA2(input, intype, mode) { + let result = ""; + let figShifted = false; + + for (const character of input) { + const letter = character.toUpperCase(); + + // Convert input text to ITA2 (including figure/letter shifts) + if (intype === "ITA2" || mode === "Receive") { + if (validITA2.indexOf(letter) === -1) { + let errltr = letter; + if (errltr==="\n") errltr = "Carriage Return"; + if (errltr===" ") errltr = "Space"; + throw new OperationError("Invalid ITA2 character : "+errltr); + } + result += letter; + } else { + if (validChars.indexOf(letter) === -1) throw new OperationError("Invalid Plaintext character : "+letter); + + if (!figShifted && figShiftedChars.indexOf(letter) !== -1) { + // in letters mode and next char needs to be figure shifted + figShifted = true; + result += "55" + figShiftArr[letter]; + } else if (figShifted) { + // in figures mode and next char needs to be letter shifted + if (letter==="\n") { + result += "34"; + } else if (letter==="\r") { + result += "4"; + } else if (figShiftedChars.indexOf(letter) === -1) { + figShifted = false; + result += "88" + letter; + } else { + result += figShiftArr[letter]; + } + + } else { + if (letter==="\n") { + result += "34"; + } else if (letter==="\r") { + result += "4"; + } else { + result += letter; + } + } + + } + + } + + return result; + } + + /** + * Convert final result ITA2 to plaintext + */ + convertFromITA2(input, outtype, mode) { + let result = ""; + let figShifted = false; + for (const letter of input) { + if (mode === "Receive") { + + // Convert output ITA2 to plaintext (including figure/letter shifts) + if (outtype === "Plaintext") { + + if (letter === "5" || letter === "+") { + figShifted = true; + } else if (letter === "8" || letter === "-") { + figShifted = false; + } else if (letter === "9") { + result += " "; + } else if (letter === "3") { + result += "\n"; + } else if (letter === "4") { + result += ""; + } else if (letter === "/") { + result += "/"; + } else { + + if (figShifted) { + result += this.REVERSE_FIGSHIFT_TABLE[letter]; + } else { + result += letter; + } + + } + + } else { + result += letter; + } + + } else { + result += letter; + } + } + + return result; + + } + +} + +const ITA2_TABLE = { + "A": "11000", + "B": "10011", + "C": "01110", + "D": "10010", + "E": "10000", + "F": "10110", + "G": "01011", + "H": "00101", + "I": "01100", + "J": "11010", + "K": "11110", + "L": "01001", + "M": "00111", + "N": "00110", + "O": "00011", + "P": "01101", + "Q": "11101", + "R": "01010", + "S": "10100", + "T": "00001", + "U": "11100", + "V": "01111", + "W": "11001", + "X": "10111", + "Y": "10101", + "Z": "10001", + "3": "00010", + "4": "01000", + "9": "00100", + "/": "00000", + " ": "00100", + ".": "00100", + "8": "11111", + "5": "11011", + "-": "11111", + "+": "11011" +}; + +const validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-'()/:=?,. \n\r"; +const validITA2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ34589+-./"; +const figShiftedChars = "1234567890+-'()/:=?,."; + +const figShiftArr = { + "1": "Q", + "2": "W", + "3": "E", + "4": "R", + "5": "T", + "6": "Y", + "7": "U", + "8": "I", + "9": "O", + "0": "P", + " ": "9", + "-": "A", + "?": "B", + ":": "C", + "#": "D", + "%": "F", + "@": "G", + "£": "H", + "": "J", + "(": "K", + ")": "L", + ".": "M", + ",": "N", + "'": "S", + "=": "V", + "/": "X", + "+": "Z", + "\n": "3", + "\r": "4" +}; + +const INIT_PATTERNS = { + "No Pattern": { + "X": { + 1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 3: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + "S": { + 1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 3: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + "M": { + 1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + } + + }, + "KH Pattern": { + "X": { + 1: [0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0], + 2: [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0], + 3: [0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0], + 4: [1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0], + 5: [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0] + }, + "S": { + 1: [0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1], + 2: [0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1], + 3: [0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1], + 4: [0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0], + 5: [1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0] + }, + "M": { + 1: [0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0], + 2: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0] + } + }, + "ZMUG Pattern": { + "X": { + 1: [0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0], + 2: [1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0], + 3: [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0], + 4: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1], + 5: [0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1] + }, + "S": { + 1: [1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0], + 2: [0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1], + 3: [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1], + 4: [0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1], + 5: [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0] + }, + "M": { + 1: [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1], + 2: [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1] + } + }, + "BREAM Pattern": { + "X": { + 1: [0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0], + 2: [0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1], + 3: [1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + 4: [1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0], + 5: [0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0] + }, + "S": { + 1: [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0], + 2: [1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0], + 3: [1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + 4: [0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1], + 5: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] + }, + "M": { + 1: [1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1], + 2: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1] + } + } +}; + +export default Lorenz; diff --git a/src/core/operations/LuhnChecksum.mjs b/src/core/operations/LuhnChecksum.mjs new file mode 100644 index 00000000..58e904fb --- /dev/null +++ b/src/core/operations/LuhnChecksum.mjs @@ -0,0 +1,96 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @author k3ach [k3ach@proton.me] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Luhn Checksum operation + */ +class LuhnChecksum extends Operation { + + /** + * LuhnChecksum constructor + */ + constructor() { + super(); + + this.name = "Luhn Checksum"; + this.module = "Default"; + this.description = "The Luhn mod N algorithm using the english alphabet. The Luhn mod N algorithm is an extension to the Luhn algorithm (also known as mod 10 algorithm) that allows it to work with sequences of values in any even-numbered base. This can be useful when a check digit is required to validate an identification string composed of letters, a combination of letters and digits or any arbitrary set of N characters where N is divisible by 2."; + this.infoURL = "https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Radix", + "type": "number", + "value": 10 + } + ]; + } + + /** + * Generates the Luhn checksum from the input. + * + * @param {string} inputStr + * @returns {number} + */ + checksum(inputStr, radix = 10) { + let even = false; + return inputStr.split("").reverse().reduce((acc, elem) => { + // Convert element to an integer based on the provided radix. + let temp = parseInt(elem, radix); + + // If element is not a valid number in the given radix. + if (isNaN(temp)) { + throw new Error("Character: " + elem + " is not valid in radix " + radix + "."); + } + + // If element is in an even position + if (even) { + // Double the element and sum the quotient and remainder. + temp = 2 * temp; + temp = Math.floor(temp / radix) + (temp % radix); + } + + even = !even; + return acc + temp; + }, 0) % radix; // Use radix as the modulus base + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + + const radix = args[0]; + + if (radix < 2 || radix > 36) { + throw new OperationError("Error: Radix argument must be between 2 and 36"); + } + + if (radix % 2 !== 0) { + throw new OperationError("Error: Radix argument must be divisible by 2"); + } + + const checkSum = this.checksum(input, radix).toString(radix); + let checkDigit = this.checksum(input + "0", radix); + checkDigit = checkDigit === 0 ? 0 : (radix - checkDigit); + checkDigit = checkDigit.toString(radix); + + return `Checksum: ${checkSum} +Checkdigit: ${checkDigit} +Luhn Validated String: ${input + "" + checkDigit}`; + } + +} + +export default LuhnChecksum; diff --git a/src/core/operations/MAC.js b/src/core/operations/MAC.js deleted file mode 100755 index 7affbfc4..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 ""; - - var 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) { - var 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..38f6d325 --- /dev/null +++ b/src/core/operations/MD2.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * 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. The message digest algorithm consists, by default, of 18 rounds."; + this.infoURL = "https://wikipedia.org/wiki/MD2_(cryptography)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Rounds", + type: "number", + value: 18, + min: 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("md2", input, {rounds: args[0]}); + } + +} + +export default MD2; diff --git a/src/core/operations/MD4.mjs b/src/core/operations/MD4.mjs new file mode 100644 index 00000000..381528cc --- /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.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * 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..f55edaf5 --- /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.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * 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..0ab13ffe --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; +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/MIMEDecoding.mjs b/src/core/operations/MIMEDecoding.mjs new file mode 100644 index 00000000..7b52fbdd --- /dev/null +++ b/src/core/operations/MIMEDecoding.mjs @@ -0,0 +1,171 @@ +/** + * @author mshwed [m@ttshwed.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import { fromBase64 } from "../lib/Base64.mjs"; +import cptable from "codepage"; + +/** + * MIME Decoding operation + */ +class MIMEDecoding extends Operation { + + /** + * MIMEDecoding constructor + */ + constructor() { + super(); + + this.name = "MIME Decoding"; + this.module = "Default"; + this.description = "Enables the decoding of MIME message header extensions for non-ASCII text"; + this.infoURL = "https://tools.ietf.org/html/rfc2047"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const mimeEncodedText = Utils.byteArrayToUtf8(input); + const encodedHeaders = mimeEncodedText.replace(/\r\n/g, "\n"); + + const decodedHeader = this.decodeHeaders(encodedHeaders); + + return decodedHeader; + } + + /** + * Decode MIME header strings + * + * @param headerString + */ + decodeHeaders(headerString) { + // No encoded words detected + let i = headerString.indexOf("=?"); + if (i === -1) return headerString; + + let decodedHeaders = headerString.slice(0, i); + let header = headerString.slice(i); + + let isBetweenWords = false; + let start, cur, charset, encoding, j, end, text; + while (header.length > -1) { + start = header.indexOf("=?"); + if (start === -1) break; + cur = start + "=?".length; + + i = header.slice(cur).indexOf("?"); + if (i === -1) break; + + charset = header.slice(cur, cur + i); + cur += i + "?".length; + + if (header.length < cur + "Q??=".length) break; + + encoding = header[cur]; + cur += 1; + + if (header[cur] !== "?") break; + + cur += 1; + + j = header.slice(cur).indexOf("?="); + if (j === -1) break; + + text = header.slice(cur, cur + j); + end = cur + j + "?=".length; + + if (encoding.toLowerCase() === "b") { + text = fromBase64(text); + } else if (encoding.toLowerCase() === "q") { + text = this.parseQEncodedWord(text); + } else { + isBetweenWords = false; + decodedHeaders += header.slice(0, start + 2); + header = header.slice(start + 2); + } + + if (start > 0 && (!isBetweenWords || header.slice(0, start).search(/\S/g) > -1)) { + decodedHeaders += header.slice(0, start); + } + + decodedHeaders += this.convertFromCharset(charset, text); + + header = header.slice(end); + isBetweenWords = true; + } + + if (header.length > 0) { + decodedHeaders += header; + } + + return decodedHeaders; + } + + /** + * Converts decoded text for supported charsets. + * Supports UTF-8, US-ASCII, ISO-8859-* + * + * @param encodedWord + */ + convertFromCharset(charset, encodedText) { + charset = charset.toLowerCase(); + const parsedCharset = charset.split("-"); + + if (parsedCharset.length === 2 && parsedCharset[0] === "utf" && charset === "utf-8") { + return cptable.utils.decode(65001, encodedText); + } else if (parsedCharset.length === 2 && charset === "us-ascii") { + return cptable.utils.decode(20127, encodedText); + } else if (parsedCharset.length === 3 && parsedCharset[0] === "iso" && parsedCharset[1] === "8859") { + const isoCharset = parseInt(parsedCharset[2], 10); + if (isoCharset >= 1 && isoCharset <= 16) { + return cptable.utils.decode(28590 + isoCharset, encodedText); + } + } + + throw new OperationError("Unhandled Charset"); + } + + /** + * Parses a Q encoded word + * + * @param encodedWord + */ + parseQEncodedWord(encodedWord) { + let decodedWord = ""; + for (let i = 0; i < encodedWord.length; i++) { + if (encodedWord[i] === "_") { + decodedWord += " "; + // Parse hex encoding + } else if (encodedWord[i] === "=") { + if ((i + 2) >= encodedWord.length) throw new OperationError("Incorrectly Encoded Word"); + const decodedHex = Utils.byteArrayToChars(fromHex(encodedWord.substring(i + 1, i + 3))); + decodedWord += decodedHex; + i += 2; + } else if ( + (encodedWord[i].charCodeAt(0) >= " ".charCodeAt(0) && encodedWord[i].charCodeAt(0) <= "~".charCodeAt(0)) || + encodedWord[i] === "\n" || + encodedWord[i] === "\r" || + encodedWord[i] === "\t") { + decodedWord += encodedWord[i]; + } else { + throw new OperationError("Incorrectly Encoded Word"); + } + } + + return decodedWord; + } +} + +export default MIMEDecoding; diff --git a/src/core/operations/Magic.mjs b/src/core/operations/Magic.mjs new file mode 100644 index 00000000..69cad1db --- /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.mjs"; +import Utils from "../Utils.mjs"; +import Dish from "../Dish.mjs"; +import MagicLib from "../lib/Magic.mjs"; + +/** + * 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.escapeWhitespace(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..ee826201 --- /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.mjs"; +import { mean, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; +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..4ec9eceb --- /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.mjs"; +import { median, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + +/** + * 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..573e62e0 --- /dev/null +++ b/src/core/operations/Merge.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * 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. Unticking the Merge All checkbox will only consolidate all branches up to the nearest Fork/Subsection."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Merge All", + type: "boolean", + value: true, + } + ]; + } + + /** + * @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..3e59e95e --- /dev/null +++ b/src/core/operations/MicrosoftScriptDecoder.mjs @@ -0,0 +1,225 @@ +/** + * @author bmwhitn [brian.m.whitney@outlook.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * 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 = []; + this.checks = [ + { + pattern: "#@~\\^.{6}==(.+).{6}==\\^#~@", + flags: "i", + 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 3d00c56d..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) { - var format = args[0].split("/"); - var dash = format[0]; - var dot = format[1]; - - var letterDelim = Utils.charRep[args[1]]; - var wordDelim = Utils.charRep[args[2]]; - - input = input.split(/\r?\n/); - input = Array.prototype.map.call(input, function(line) { - var words = line.split(/ +/); - words = Array.prototype.map.call(words, function(word) { - var letters = Array.prototype.map.call(word, function(character) { - var 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() { - var reversedTable = null; - var reverseTable = function() { - reversedTable = {}; - - for (var letter in MorseCode.MORSE_TABLE) { - var signal = MorseCode.MORSE_TABLE[letter]; - reversedTable[signal] = letter; - } - }; - - return function(input, args) { - if (reversedTable === null) { - reverseTable(); - } - - var letterDelim = Utils.charRep[args[0]]; - var 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, ""); - - var words = input.split(wordDelim); - words = Array.prototype.map.call(words, function(word) { - var signals = word.split(letterDelim); - - var 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/MultipleBombe.mjs b/src/core/operations/MultipleBombe.mjs new file mode 100644 index 00000000..1af471c2 --- /dev/null +++ b/src/core/operations/MultipleBombe.mjs @@ -0,0 +1,307 @@ +/** + * Emulation of the Bombe machine. + * This version carries out multiple Bombe runs to handle unknown rotor configurations. + * + * @author s2224834 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { BombeMachine } from "../lib/Bombe.mjs"; +import { ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector } from "../lib/Enigma.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + + +/** + * Convenience method for flattening the preset ROTORS object into a newline-separated string. + * @param {Object[]} - Preset rotors object + * @param {number} s - Start index + * @param {number} n - End index + * @returns {string} + */ +function rotorsFormat(rotors, s, n) { + const res = []; + for (const i of rotors.slice(s, n)) { + res.push(i.value); + } + return res.join("\n"); +} + +/** + * Combinatorics choose function + * @param {number} n + * @param {number} k + * @returns number + */ +function choose(n, k) { + let res = 1; + for (let i=1; i<=k; i++) { + res *= (n + 1 - i) / i; + } + return res; +} + +/** + * Bombe operation + */ +class MultipleBombe extends Operation { + /** + * Bombe constructor + */ + constructor() { + super(); + + this.name = "Multiple Bombe"; + this.module = "Bletchley"; + this.description = "Emulation of the Bombe machine used to attack Enigma. This version carries out multiple Bombe runs to handle unknown rotor configurations.

You should test your menu on the single Bombe operation before running it here. See the description of the Bombe operation for instructions on choosing a crib.

More detailed descriptions of the Enigma, Typex and Bombe operations can be found here."; + this.infoURL = "https://wikipedia.org/wiki/Bombe"; + this.inputType = "string"; + this.outputType = "JSON"; + this.presentType = "html"; + this.args = [ + { + "name": "Standard Enigmas", + "type": "populateMultiOption", + "value": [ + { + name: "German Service Enigma (First - 3 rotor)", + value: [ + rotorsFormat(ROTORS, 0, 5), + "", + rotorsFormat(REFLECTORS, 0, 1) + ] + }, + { + name: "German Service Enigma (Second - 3 rotor)", + value: [ + rotorsFormat(ROTORS, 0, 8), + "", + rotorsFormat(REFLECTORS, 0, 2) + ] + }, + { + name: "German Service Enigma (Third - 4 rotor)", + value: [ + rotorsFormat(ROTORS, 0, 8), + rotorsFormat(ROTORS_FOURTH, 1, 2), + rotorsFormat(REFLECTORS, 2, 3) + ] + }, + { + name: "German Service Enigma (Fourth - 4 rotor)", + value: [ + rotorsFormat(ROTORS, 0, 8), + rotorsFormat(ROTORS_FOURTH, 1, 3), + rotorsFormat(REFLECTORS, 2, 4) + ] + }, + { + name: "User defined", + value: ["", "", ""] + }, + ], + "target": [1, 2, 3] + }, + { + name: "Main rotors", + type: "text", + value: "" + }, + { + name: "4th rotor", + type: "text", + value: "" + }, + { + name: "Reflectors", + type: "text", + value: "" + }, + { + name: "Crib", + type: "string", + value: "" + }, + { + name: "Crib offset", + type: "number", + value: 0 + }, + { + name: "Use checking machine", + type: "boolean", + value: true + } + ]; + } + + /** + * Format and send a status update message. + * @param {number} nLoops - Number of loops in the menu + * @param {number} nStops - How many stops so far + * @param {number} progress - Progress (as a float in the range 0..1) + */ + updateStatus(nLoops, nStops, progress, start) { + const elapsed = Date.now() - start; + const remaining = (elapsed / progress) * (1 - progress) / 1000; + const hours = Math.floor(remaining / 3600); + const minutes = `0${Math.floor((remaining % 3600) / 60)}`.slice(-2); + const seconds = `0${Math.floor(remaining % 60)}`.slice(-2); + const msg = `Bombe run with ${nLoops} loop${nLoops === 1 ? "" : "s"} in menu (2+ desirable): ${nStops} stops, ${Math.floor(100 * progress)}% done, ${hours}:${minutes}:${seconds} remaining`; + self.sendStatusMessage(msg); + } + + /** + * Early rotor description string validation. + * Drops stepping information. + * @param {string} rstr - The rotor description string + * @returns {string} - Rotor description with stepping stripped, if any + */ + validateRotor(rstr) { + // The Bombe doesn't take stepping into account so we'll just ignore it here + if (rstr.includes("<")) { + rstr = rstr.split("<", 2)[0]; + } + // Duplicate the validation of the rotor strings here, otherwise you might get an error + // thrown halfway into a big Bombe run + if (!/^[A-Z]{26}$/.test(rstr)) { + throw new OperationError("Rotor wiring must be 26 unique uppercase letters"); + } + if (new Set(rstr).size !== 26) { + throw new OperationError("Rotor wiring must be 26 unique uppercase letters"); + } + return rstr; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const mainRotorsStr = args[1]; + const fourthRotorsStr = args[2]; + const reflectorsStr = args[3]; + let crib = args[4]; + const offset = args[5]; + const check = args[6]; + const rotors = []; + const fourthRotors = []; + const reflectors = []; + for (let rstr of mainRotorsStr.split("\n")) { + rstr = this.validateRotor(rstr); + rotors.push(rstr); + } + if (rotors.length < 3) { + throw new OperationError("A minimum of three rotors must be supplied"); + } + if (fourthRotorsStr !== "") { + for (let rstr of fourthRotorsStr.split("\n")) { + rstr = this.validateRotor(rstr); + fourthRotors.push(rstr); + } + } + if (fourthRotors.length === 0) { + fourthRotors.push(""); + } + for (const rstr of reflectorsStr.split("\n")) { + const reflector = new Reflector(rstr); + reflectors.push(reflector); + } + if (reflectors.length === 0) { + throw new OperationError("A minimum of one reflector must be supplied"); + } + if (crib.length === 0) { + throw new OperationError("Crib cannot be empty"); + } + if (offset < 0) { + throw new OperationError("Offset cannot be negative"); + } + // For symmetry with the Enigma op, for the input we'll just remove all invalid characters + input = input.replace(/[^A-Za-z]/g, "").toUpperCase(); + crib = crib.replace(/[^A-Za-z]/g, "").toUpperCase(); + const ciphertext = input.slice(offset); + let update; + if (isWorkerEnvironment()) { + update = this.updateStatus; + } else { + update = undefined; + } + let bombe = undefined; + const output = {bombeRuns: []}; + // I could use a proper combinatorics algorithm here... but it would be more code to + // write one, and we don't seem to have one in our existing libraries, so massively nested + // for loop it is + const totalRuns = choose(rotors.length, 3) * 6 * fourthRotors.length * reflectors.length; + let nRuns = 0; + let nStops = 0; + const start = Date.now(); + for (const rotor1 of rotors) { + for (const rotor2 of rotors) { + if (rotor2 === rotor1) { + continue; + } + for (const rotor3 of rotors) { + if (rotor3 === rotor2 || rotor3 === rotor1) { + continue; + } + for (const rotor4 of fourthRotors) { + for (const reflector of reflectors) { + nRuns++; + const runRotors = [rotor1, rotor2, rotor3]; + if (rotor4 !== "") { + runRotors.push(rotor4); + } + if (bombe === undefined) { + bombe = new BombeMachine(runRotors, reflector, ciphertext, crib, check); + output.nLoops = bombe.nLoops; + } else { + bombe.changeRotors(runRotors, reflector); + } + const result = bombe.run(); + nStops += result.length; + if (update !== undefined) { + update(bombe.nLoops, nStops, nRuns / totalRuns, start); + } + if (result.length > 0) { + output.bombeRuns.push({ + rotors: runRotors, + reflector: reflector.pairs, + result: result + }); + } + } + } + } + } + } + return output; + } + + + /** + * Displays the MultiBombe results in an HTML table + * + * @param {Object} output + * @param {number} output.nLoops + * @param {Array[]} output.result + * @returns {html} + */ + present(output) { + let html = `Bombe run on menu with ${output.nLoops} loop${output.nLoops === 1 ? "" : "s"} (2+ desirable). Note: Rotors and rotor positions are listed left to right, ignore stepping and the ring setting, and positions start at the beginning of the crib. Some plugboard settings are determined. A decryption preview starting at the beginning of the crib and ignoring stepping is also provided.\n`; + + for (const run of output.bombeRuns) { + html += `\nRotors: ${run.rotors.slice().reverse().join(", ")}\nReflector: ${run.reflector}\n`; + html += "\n"; + for (const [setting, stecker, decrypt] of run.result) { + html += `\n`; + } + html += "
Rotor stops Partial plugboard Decryption preview
${setting} ${stecker} ${decrypt}
\n"; + } + return html; + } +} + +export default MultipleBombe; diff --git a/src/core/operations/Multiply.mjs b/src/core/operations/Multiply.mjs new file mode 100644 index 00000000..9f1666e8 --- /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.mjs"; +import { multi, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + + +/** + * 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/MurmurHash3.mjs b/src/core/operations/MurmurHash3.mjs new file mode 100644 index 00000000..7921633f --- /dev/null +++ b/src/core/operations/MurmurHash3.mjs @@ -0,0 +1,139 @@ +/** + * Based on murmurhash-js (https://github.com/garycourt/murmurhash-js) + * @author Gary Court + * @license MIT + * + * @author AliceGrey [alice@grey.systems] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * MurmurHash3 operation + */ +class MurmurHash3 extends Operation { + + /** + * MurmurHash3 constructor + */ + constructor() { + super(); + + this.name = "MurmurHash3"; + this.module = "Hashing"; + this.description = "Generates a MurmurHash v3 for a string input and an optional seed input"; + this.infoURL = "https://wikipedia.org/wiki/MurmurHash"; + this.inputType = "string"; + this.outputType = "number"; + this.args = [ + { + name: "Seed", + type: "number", + value: 0 + }, + { + name: "Convert to Signed", + type: "boolean", + value: false + } + ]; + } + + /** + * Calculates the MurmurHash3 hash of the input. + * Based on Gary Court's JS MurmurHash implementation + * @see http://github.com/garycourt/murmurhash-js + * @author AliceGrey [alice@grey.systems] + * @param {string} input ASCII only + * @param {number} seed Positive integer only + * @return {number} 32-bit positive integer hash + */ + mmh3(input, seed) { + let h1b; + let k1; + const remainder = input.length & 3; // input.length % 4 + const bytes = input.length - remainder; + let h1 = seed; + const c1 = 0xcc9e2d51; + const c2 = 0x1b873593; + let i = 0; + + while (i < bytes) { + k1 = + ((input.charCodeAt(i) & 0xff)) | + ((input.charCodeAt(++i) & 0xff) << 8) | + ((input.charCodeAt(++i) & 0xff) << 16) | + ((input.charCodeAt(++i) & 0xff) << 24); + ++i; + + k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; + + h1 ^= k1; + h1 = (h1 << 13) | (h1 >>> 19); + h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; + h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); + } + + k1 = 0; + + if (remainder === 3) { + k1 ^= (input.charCodeAt(i + 2) & 0xff) << 16; + } + + if (remainder === 3 || remainder === 2) { + k1 ^= (input.charCodeAt(i + 1) & 0xff) << 8; + } + + if (remainder === 3 || remainder === 2 || remainder === 1) { + k1 ^= (input.charCodeAt(i) & 0xff); + + k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; + h1 ^= k1; + } + + h1 ^= input.length; + + h1 ^= h1 >>> 16; + h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; + h1 ^= h1 >>> 13; + h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; + h1 ^= h1 >>> 16; + + return h1 >>> 0; + } + + /** + * Converts an unsigned 32-bit integer to a signed 32-bit integer + * @author AliceGrey [alice@grey.systems] + * @param {value} 32-bit unsigned integer + * @return {number} 32-bit signed integer + */ + unsignedToSigned(value) { + return value & 0x80000000 ? -0x100000000 + value : value; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + if (args && args.length >= 1) { + const seed = args[0]; + const hash = this.mmh3(input, seed); + if (args.length > 1 && args[1]) { + return this.unsignedToSigned(hash); + } + return hash; + } + return this.mmh3(input); + } +} + +export default MurmurHash3; diff --git a/src/core/operations/NOT.mjs b/src/core/operations/NOT.mjs new file mode 100644 index 00000000..46fc1b8c --- /dev/null +++ b/src/core/operations/NOT.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { bitOp, not } from "../lib/BitwiseOp.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + input = new Uint8Array(input); + 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/NTHash.mjs b/src/core/operations/NTHash.mjs new file mode 100644 index 00000000..bde7a24a --- /dev/null +++ b/src/core/operations/NTHash.mjs @@ -0,0 +1,48 @@ +/** + * @author brun0ne [brunonblok@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * NT Hash operation + */ +class NTHash extends Operation { + + /** + * NTHash constructor + */ + constructor() { + super(); + + this.name = "NT Hash"; + this.module = "Crypto"; + this.description = "An NT Hash, sometimes referred to as an NTLM hash, is a method of storing passwords on Windows systems. It works by running MD4 on UTF-16LE encoded input. NTLM hashes are considered weak because they can be brute-forced very easily with modern hardware."; + this.infoURL = "https://wikipedia.org/wiki/NT_LAN_Manager"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + // Convert to UTF-16LE + const buf = new ArrayBuffer(input.length * 2); + const bufView = new Uint16Array(buf); + for (let i = 0; i < input.length; i++) { + bufView[i] = input.charCodeAt(i); + } + + const hashed = runHash("md4", buf); + return hashed.toUpperCase(); + } +} + +export default NTHash; diff --git a/src/core/operations/NetBIOS.js b/src/core/operations/NetBIOS.js deleted file mode 100644 index 7791c840..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) { - var output = [], - offset = args[0]; - - for (var 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) { - var output = [], - offset = args[0]; - - for (var 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/NormaliseImage.mjs b/src/core/operations/NormaliseImage.mjs new file mode 100644 index 00000000..e67570bc --- /dev/null +++ b/src/core/operations/NormaliseImage.mjs @@ -0,0 +1,85 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Normalise Image operation + */ +class NormaliseImage extends Operation { + + /** + * NormaliseImage constructor + */ + constructor() { + super(); + + this.name = "Normalise Image"; + this.module = "Image"; + this.description = "Normalise the image colours."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType= "html"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error opening image file. (${err})`); + } + + try { + image.normalize(); + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error normalising image. (${err})`); + } + } + + /** + * Displays the normalised image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default NormaliseImage; diff --git a/src/core/operations/NormaliseUnicode.mjs b/src/core/operations/NormaliseUnicode.mjs new file mode 100644 index 00000000..80d3be3c --- /dev/null +++ b/src/core/operations/NormaliseUnicode.mjs @@ -0,0 +1,62 @@ +/** + * @author Matthieu [m@tthieu.xyz] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {UNICODE_NORMALISATION_FORMS} from "../lib/ChrEnc.mjs"; +import unorm from "unorm"; + +/** + * Normalise Unicode operation + */ +class NormaliseUnicode extends Operation { + + /** + * NormaliseUnicode constructor + */ + constructor() { + super(); + + this.name = "Normalise Unicode"; + this.module = "Encodings"; + this.description = "Transform Unicode characters to one of the Normalisation Forms"; + this.infoURL = "https://wikipedia.org/wiki/Unicode_equivalence#Normal_forms"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Normal Form", + type: "option", + value: UNICODE_NORMALISATION_FORMS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [normalForm] = args; + + switch (normalForm) { + case "NFD": + return unorm.nfd(input); + case "NFC": + return unorm.nfc(input); + case "NFKD": + return unorm.nfkd(input); + case "NFKC": + return unorm.nfkc(input); + default: + throw new OperationError("Unknown Normalisation Form"); + } + } + +} + +export default NormaliseUnicode; diff --git a/src/core/operations/Numberwang.js b/src/core/operations/Numberwang.js deleted file mode 100755 index 4fff3184..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!"; - var 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..7f94e360 --- /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.mjs"; + +/** + * 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..183fb1fe --- /dev/null +++ b/src/core/operations/OR.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { bitOp, or, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": BITWISE_OP_DELIMS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); + input = new Uint8Array(input); + + return bitOp(input, key, or); + } + + /** + * 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 ae842b8f..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) { - var 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) { - var 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) { - var 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..3e78cc03 --- /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.mjs"; + +/** + * 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..0f66e591 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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/OpticalCharacterRecognition.mjs b/src/core/operations/OpticalCharacterRecognition.mjs new file mode 100644 index 00000000..dfcff965 --- /dev/null +++ b/src/core/operations/OpticalCharacterRecognition.mjs @@ -0,0 +1,96 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author mshwed [m@ttshwed.com] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +import { createWorker } from "tesseract.js"; + +const OEM_MODES = ["Tesseract only", "LSTM only", "Tesseract/LSTM Combined"]; + +/** + * Optical Character Recognition operation + */ +class OpticalCharacterRecognition extends Operation { + + /** + * OpticalCharacterRecognition constructor + */ + constructor() { + super(); + + this.name = "Optical Character Recognition"; + this.module = "OCR"; + this.description = "Optical character recognition or optical character reader (OCR) is the mechanical or electronic conversion of images of typed, handwritten or printed text into machine-encoded text.

Supported image formats: png, jpg, bmp, pbm."; + this.infoURL = "https://wikipedia.org/wiki/Optical_character_recognition"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Show confidence", + type: "boolean", + value: true + }, + { + name: "OCR Engine Mode", + type: "option", + value: OEM_MODES, + defaultIndex: 1 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [showConfidence, oemChoice] = args; + + if (!isWorkerEnvironment()) throw new OperationError("This operation only works in a browser"); + + const type = isImage(input); + if (!type) { + throw new OperationError("Unsupported file type (supported: jpg,png,pbm,bmp) or no file provided"); + } + + const assetDir = `${self.docURL}/assets/`; + const oem = OEM_MODES.indexOf(oemChoice); + + try { + self.sendStatusMessage("Spinning up Tesseract worker..."); + const image = `data:${type};base64,${toBase64(input)}`; + const worker = await createWorker("eng", oem, { + workerPath: `${assetDir}tesseract/worker.min.js`, + langPath: `${assetDir}tesseract/lang-data`, + corePath: `${assetDir}tesseract/tesseract-core.wasm.js`, + logger: progress => { + if (isWorkerEnvironment()) { + self.sendStatusMessage(`Status: ${progress.status}${progress.status === "recognizing text" ? ` - ${(parseFloat(progress.progress)*100).toFixed(2)}%`: "" }`); + } + } + }); + self.sendStatusMessage("Finding text..."); + const result = await worker.recognize(image); + + if (showConfidence) { + return `Confidence: ${result.data.confidence}%\n\n${result.data.text}`; + } else { + return result.data.text; + } + } catch (err) { + throw new OperationError(`Error performing OCR on image. (${err})`); + } + } +} + +export default OpticalCharacterRecognition; diff --git a/src/core/operations/PEMToHex.mjs b/src/core/operations/PEMToHex.mjs new file mode 100644 index 00000000..afbb4972 --- /dev/null +++ b/src/core/operations/PEMToHex.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author cplussharp + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import { fromBase64 } from "../lib/Base64.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * PEM to Hex operation + */ +class PEMToHex extends Operation { + + /** + * PEMToHex constructor + */ + constructor() { + super(); + + this.name = "PEM to Hex"; + this.module = "Default"; + this.description = "Converts PEM (Privacy Enhanced Mail) format to a hexadecimal DER (Distinguished Encoding Rules) string."; + this.infoURL = "https://wikipedia.org/wiki/Privacy-Enhanced_Mail#Format"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + "pattern": "----BEGIN ([A-Z][A-Z ]+[A-Z])-----", + "args": [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const output = []; + let match; + const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g; + while ((match = regex.exec(input)) !== null) { + // find corresponding end tag + const indexBase64 = match.index + match[0].length; + const footer = `-----END ${match[1]}-----`; + const indexFooter = input.indexOf(footer, indexBase64); + if (indexFooter === -1) { + throw new OperationError(`PEM footer '${footer}' not found`); + } + + // decode base64 content + const base64 = input.substring(indexBase64, indexFooter); + const bytes = fromBase64(base64, "A-Za-z0-9+/=", "byteArray", true); + const hex = toHexFast(bytes); + output.push(hex); + } + return output.join("\n"); + } + +} + +export default PEMToHex; diff --git a/src/core/operations/PEMToJWK.mjs b/src/core/operations/PEMToJWK.mjs new file mode 100644 index 00000000..61fde371 --- /dev/null +++ b/src/core/operations/PEMToJWK.mjs @@ -0,0 +1,88 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * PEM to JWK operation + */ +class PEMToJWK extends Operation { + + /** + * PEMToJWK constructor + */ + constructor() { + super(); + + this.name = "PEM to JWK"; + this.module = "PublicKey"; + this.description = "Converts Keys in PEM format to a JSON Web Key format."; + this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = [ + { + "pattern": "-----BEGIN ((RSA |EC )?(PRIVATE|PUBLIC) KEY|CERTIFICATE)-----", + "args": [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output = ""; + let match; + const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g; + while ((match = regex.exec(input)) !== null) { + // find corresponding end tag + const indexBase64 = match.index + match[0].length; + const header = input.substring(match.index, indexBase64); + const footer = `-----END ${match[1]}-----`; + const indexFooter = input.indexOf(footer, indexBase64); + if (indexFooter === -1) { + throw new OperationError(`PEM footer '${footer}' not found`); + } + + const pem = input.substring(match.index, indexFooter + footer.length); + if (match[1].indexOf("KEY") !== -1) { + if (header === "-----BEGIN RSA PUBLIC KEY-----") { + throw new OperationError("Unsupported RSA public key format. Only PKCS#8 is supported."); + } + + const key = r.KEYUTIL.getKey(pem); + if (key.type === "DSA") { + throw new OperationError("DSA keys are not supported for JWK"); + } + const jwk = r.KEYUTIL.getJWKFromKey(key); + if (output.length > 0) { + output += "\n"; + } + output += JSON.stringify(jwk); + } else if (match[1] === "CERTIFICATE") { + const cert = new r.X509(); + cert.readCertPEM(pem); + const key = cert.getPublicKey(); + const jwk = r.KEYUTIL.getJWKFromKey(key); + if (output.length > 0) { + output += "\n"; + } + output += JSON.stringify(jwk); + } else { + throw new OperationError(`Unsupported PEM type '${match[1]}'`); + } + } + return output; + } +} + +export default PEMToJWK; diff --git a/src/core/operations/PGPDecrypt.mjs b/src/core/operations/PGPDecrypt.mjs new file mode 100644 index 00000000..d69b88ea --- /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.mjs"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey } from "../lib/PGP.mjs"; +import OperationError from "../errors/OperationError.mjs"; +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..119ccf16 --- /dev/null +++ b/src/core/operations/PGPDecryptAndVerify.mjs @@ -0,0 +1,124 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP.mjs"; +import OperationError from "../errors/OperationError.mjs"; +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 key ID: ${km.get_pgp_short_key_id()}`, + `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`, + `Signed on ${new Date(ds.sig.when_generated() * 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..2f41cc0b --- /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.mjs"; +import kbpgp from "kbpgp"; +import { ASP, importPublicKey } from "../lib/PGP.mjs"; +import OperationError from "../errors/OperationError.mjs"; +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..8fdbe4c3 --- /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.mjs"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP.mjs"; +import OperationError from "../errors/OperationError.mjs"; +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/PGPVerify.mjs b/src/core/operations/PGPVerify.mjs new file mode 100644 index 00000000..ee346916 --- /dev/null +++ b/src/core/operations/PGPVerify.mjs @@ -0,0 +1,111 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +import kbpgp from "kbpgp"; +import { ASP, importPublicKey } from "../lib/PGP.mjs"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Verify operation + */ +class PGPVerify extends Operation { + + /** + * PGPVerify constructor + */ + constructor() { + super(); + + this.name = "PGP Verify"; + this.module = "PGP"; + this.description = [ + "Input: the ASCII-armoured encrypted PGP message you want to verify.", + "

", + "Argument: the ASCII-armoured PGP public key of the signer", + "

", + "This operation uses PGP to decrypt a clearsigned message.", + "

", + "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": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const signedMessage = input, + [publicKey] = args, + keyring = new kbpgp.keyring.KeyRing(); + let unboxedLiterals; + + if (!publicKey) throw new OperationError("Enter the public key of the signer."); + const pubKey = await importPublicKey(publicKey); + 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 key ID: ${km.get_pgp_short_key_id()}`, + `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`, + `Signed on ${new Date(ds.sig.when_generated() * 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 PGPVerify; diff --git a/src/core/operations/PHPDeserialize.mjs b/src/core/operations/PHPDeserialize.mjs new file mode 100644 index 00000000..77d18bc2 --- /dev/null +++ b/src/core/operations/PHPDeserialize.mjs @@ -0,0 +1,171 @@ +/** + * @author Jarmo van Lenthe [github.com/jarmovanlenthe] + * @copyright Jarmo van Lenthe + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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 "null"; + 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, '\\"') + '"'; // lgtm [js/incomplete-sanitization] + } 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/PHPSerialize.mjs b/src/core/operations/PHPSerialize.mjs new file mode 100644 index 00000000..00fb1380 --- /dev/null +++ b/src/core/operations/PHPSerialize.mjs @@ -0,0 +1,126 @@ +/** + * @author brun0ne [brunonblok@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * PHP Serialize operation + */ +class PHPSerialize extends Operation { + + /** + * PHPSerialize constructor + */ + constructor() { + super(); + + this.name = "PHP Serialize"; + this.module = "Default"; + this.description = "Performs PHP serialization on JSON data.

This function does not support object tags.

Since PHP doesn't distinguish dicts and arrays, this operation is not always symmetric to PHP Deserialize.

Example:
[5,"abc",true]
becomes
a:3:{i:0;i:5;i:1;s:3:"abc";i:2;b:1;}"; + this.infoURL = "https://www.phpinternalsbook.com/php5/classes_objects/serialization.html"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + /** + * Determines if a number is an integer + * @param {number} value + * @returns {boolean} + */ + function isInteger(value) { + return typeof value === "number" && parseInt(value.toString(), 10) === value; + } + + /** + * Serialize basic types + * @param {string | number | boolean} content + * @returns {string} + */ + function serializeBasicTypes(content) { + const basicTypes = { + "string": "s", + "integer": "i", + "float": "d", + "boolean": "b" + }; + /** + * Booleans + * cast to 0 or 1 + */ + if (typeof content === "boolean") { + return `${basicTypes.boolean}:${content ? 1 : 0}`; + } + /* Numbers */ + if (typeof content === "number") { + if (isInteger(content)) { + return `${basicTypes.integer}:${content.toString()}`; + } else { + return `${basicTypes.float}:${content.toString()}`; + } + } + /* Strings */ + if (typeof content === "string") + return `${basicTypes.string}:${content.length}:"${content}"`; + + /** This should be unreachable */ + throw new OperationError(`Encountered a non-implemented type: ${typeof content}`); + } + + /** + * Recursively serialize + * @param {*} object + * @returns {string} + */ + function serialize(object) { + /* Null */ + if (object == null) { + return `N;`; + } + + if (typeof object !== "object") { + /* Basic types */ + return `${serializeBasicTypes(object)};`; + } else if (object instanceof Array) { + /* Arrays */ + const serializedElements = []; + + for (let i = 0; i < object.length; i++) { + serializedElements.push(`${serialize(i)}${serialize(object[i])}`); + } + + return `a:${object.length}:{${serializedElements.join("")}}`; + } else if (object instanceof Object) { + /** + * Objects + * Note: the output cannot be guaranteed to be in the same order as the input + */ + const serializedElements = []; + const keys = Object.keys(object); + + for (const key of keys) { + serializedElements.push(`${serialize(key)}${serialize(object[key])}`); + } + + return `a:${keys.length}:{${serializedElements.join("")}}`; + } + + /** This should be unreachable */ + throw new OperationError(`Encountered a non-implemented type: ${typeof object}`); + } + + return serialize(input); + } +} + +export default PHPSerialize; diff --git a/src/core/operations/PLISTViewer.mjs b/src/core/operations/PLISTViewer.mjs new file mode 100644 index 00000000..9b4aada4 --- /dev/null +++ b/src/core/operations/PLISTViewer.mjs @@ -0,0 +1,133 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * P-list Viewer operation + */ +class PlistViewer extends Operation { + + /** + * PlistViewer constructor + */ + constructor() { + super(); + + this.name = "P-list Viewer"; + this.module = "Default"; + this.description = "In the macOS, iOS, NeXTSTEP, and GNUstep programming frameworks, property list files are files that store serialized objects. Property list files use the filename extension .plist, and thus are often referred to as p-list files.

This operation displays plist files in a human readable format."; + this.infoURL = "https://wikipedia.org/wiki/Property_list"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + // Regexes are designed to transform the xml format into a more readable string format. + input = input.slice(input.indexOf("/g, "plist => ") + .replace(//g, "{") + .replace(/<\/dict>/g, "}") + .replace(//g, "[") + .replace(/<\/array>/g, "]") + .replace(/.+<\/key>/g, m => `${m.slice(5, m.indexOf(/<\/key>/g)-5)}\t=> `) + .replace(/.+<\/real>/g, m => `${m.slice(6, m.indexOf(/<\/real>/g)-6)}\n`) + .replace(/.+<\/string>/g, m => `"${m.slice(8, m.indexOf(/<\/string>/g)-8)}"\n`) + .replace(/.+<\/integer>/g, m => `${m.slice(9, m.indexOf(/<\/integer>/g)-9)}\n`) + .replace(//g, m => "false") + .replace(//g, m => "true") + .replace(/<\/plist>/g, "/plist") + .replace(/.+<\/date>/g, m => `${m.slice(6, m.indexOf(/<\/integer>/g)-6)}`) + .replace(/[\s\S]+?<\/data>/g, m => `${m.slice(6, m.indexOf(/<\/data>/g)-6)}`) + .replace(/[ \t\r\f\v]/g, ""); + + /** + * Depending on the type of brace, it will increment the depth and amount of arrays accordingly. + * + * @param {string} elem + * @param {array} vals + * @param {number} offset + */ + function braces(elem, vals, offset) { + const temp = vals.indexOf(elem); + if (temp !== -1) { + depthCount += offset; + if (temp === 1) + arrCount += offset; + } + } + + let result = ""; + let arrCount = 0; + let depthCount = 0; + + /** + * Formats the input after the regex has replaced all of the relevant parts. + * + * @param {array} input + * @param {number} index + */ + function printIt(input, index) { + if (!(input.length)) + return; + + let temp = ""; + const origArr = arrCount; + let currElem = input[0]; + + // If the current position points at a larger dynamic structure. + if (currElem.indexOf("=>") !== -1) { + + // If the LHS also points at a larger structure (nested plists in a dictionary). + if (input[1].indexOf("=>") !== -1) + temp = currElem.slice(0, -2) + " => " + input[1].slice(0, -2) + " =>\n"; + else + temp = currElem.slice(0, -2) + " => " + input[1] + "\n"; + + input = input.slice(1); + } else { + // Controls the tab depth for how many closing braces there have been. + + braces(currElem, ["}", "]"], -1); + + // Has to be here since the formatting breaks otherwise. + temp = currElem + "\n"; + } + + currElem = input[0]; + + // Tab out to the correct distance. + result += ("\t".repeat(depthCount)); + + // If it is enclosed in an array show index. + if (arrCount > 0 && currElem !== "]") + result += index.toString() + " => "; + + result += temp; + + // Controls the tab depth for how many opening braces there have been. + braces(currElem, ["{", "["], 1); + + // If there has been a new array then reset index. + if (arrCount > origArr) + return printIt(input.slice(1), 0); + return printIt(input.slice(1), ++index); + } + + input = input.split("\n").filter(e => e !== ""); + printIt(input, 0); + return result; + } +} + +export default PlistViewer; diff --git a/src/core/operations/PadLines.mjs b/src/core/operations/PadLines.mjs new file mode 100644 index 00000000..c1464cce --- /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.mjs"; + +/** + * 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..35fd5ba4 --- /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.mjs"; + +/** + * 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 (encoded as an hex string: use the 'To Hex' operation if necessary) 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, "").toLowerCase(), { + "ommit_long_octet": truncateLen + }, index); + } + +} + +export default ParseASN1HexString; diff --git a/src/core/operations/ParseCSR.mjs b/src/core/operations/ParseCSR.mjs new file mode 100644 index 00000000..d3b3c364 --- /dev/null +++ b/src/core/operations/ParseCSR.mjs @@ -0,0 +1,390 @@ +/** + * @author jkataja + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import { formatDnObj } from "../lib/PublicKey.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Parse CSR operation + */ +class ParseCSR extends Operation { + + /** + * ParseCSR constructor + */ + constructor() { + super(); + + this.name = "Parse CSR"; + this.module = "PublicKey"; + this.description = "Parse Certificate Signing Request (CSR) for an X.509 certificate"; + this.infoURL = "https://wikipedia.org/wiki/Certificate_signing_request"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["PEM"] + } + ]; + this.checks = [ + { + "pattern": "^-+BEGIN CERTIFICATE REQUEST-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE REQUEST-+\\r?\\n?$", + "flags": "i", + "args": ["PEM"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} Human-readable description of a Certificate Signing Request (CSR). + */ + run(input, args) { + if (!input.length) { + return "No input"; + } + + // Parse the CSR into JSON parameters + const csrParam = new r.KJUR.asn1.csr.CSRUtil.getParam(input); + + return `Subject\n${formatDnObj(csrParam.subject, 2)} +Public Key${formatSubjectPublicKey(csrParam.sbjpubkey)} +Signature${formatSignature(csrParam.sigalg, csrParam.sighex)} +Requested Extensions${formatRequestedExtensions(csrParam)}`; + } +} + +/** + * Format signature of a CSR + * @param {*} sigAlg string + * @param {*} sigHex string + * @returns Multi-line string describing CSR Signature + */ +function formatSignature(sigAlg, sigHex) { + let out = `\n`; + + out += ` Algorithm: ${sigAlg}\n`; + + if (new RegExp("withdsa", "i").test(sigAlg)) { + const d = new r.KJUR.crypto.DSA(); + const sigParam = d.parseASN1Signature(sigHex); + out += ` Signature: + R: ${formatHexOntoMultiLine(absBigIntToHex(sigParam[0]))} + S: ${formatHexOntoMultiLine(absBigIntToHex(sigParam[1]))}\n`; + } else if (new RegExp("withrsa", "i").test(sigAlg)) { + out += ` Signature: ${formatHexOntoMultiLine(sigHex)}\n`; + } else { + out += ` Signature: ${formatHexOntoMultiLine(ensureHexIsPositiveInTwosComplement(sigHex))}\n`; + } + + return chop(out); +} + +/** + * Format Subject Public Key from PEM encoded public key string + * @param {*} publicKeyPEM string + * @returns Multi-line string describing Subject Public Key Info + */ +function formatSubjectPublicKey(publicKeyPEM) { + let out = "\n"; + + const publicKey = r.KEYUTIL.getKey(publicKeyPEM); + if (publicKey instanceof r.RSAKey) { + out += ` Algorithm: RSA + Length: ${publicKey.n.bitLength()} bits + Modulus: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.n))} + Exponent: ${publicKey.e} (0x${Utils.hex(publicKey.e)})\n`; + } else if (publicKey instanceof r.KJUR.crypto.ECDSA) { + out += ` Algorithm: ECDSA + Length: ${publicKey.ecparams.keylen} bits + Pub: ${formatHexOntoMultiLine(publicKey.pubKeyHex)} + ASN1 OID: ${r.KJUR.crypto.ECDSA.getName(publicKey.getShortNISTPCurveName())} + NIST CURVE: ${publicKey.getShortNISTPCurveName()}\n`; + } else if (publicKey instanceof r.KJUR.crypto.DSA) { + out += ` Algorithm: DSA + Length: ${publicKey.p.toString(16).length * 4} bits + Pub: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.y))} + P: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.p))} + Q: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.q))} + G: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.g))}\n`; + } else { + out += `unsupported public key algorithm\n`; + } + + return chop(out); +} + +/** + * Format known extensions of a CSR + * @param {*} csrParam object + * @returns Multi-line string describing CSR Requested Extensions + */ +function formatRequestedExtensions(csrParam) { + const formattedExtensions = new Array(4).fill(""); + + if (Object.hasOwn(csrParam, "extreq")) { + for (const extension of csrParam.extreq) { + let parts = []; + switch (extension.extname) { + case "basicConstraints" : + parts = describeBasicConstraints(extension); + formattedExtensions[0] = ` Basic Constraints:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; + break; + case "keyUsage" : + parts = describeKeyUsage(extension); + formattedExtensions[1] = ` Key Usage:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; + break; + case "extKeyUsage" : + parts = describeExtendedKeyUsage(extension); + formattedExtensions[2] = ` Extended Key Usage:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; + break; + case "subjectAltName" : + parts = describeSubjectAlternativeName(extension); + formattedExtensions[3] = ` Subject Alternative Name:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`; + break; + default : + parts = ["(unsuported extension)"]; + formattedExtensions.push(` ${extension.extname}:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`); + } + } + } + + let out = "\n"; + + formattedExtensions.forEach((formattedExtension) => { + if (formattedExtension !== undefined && formattedExtension !== null && formattedExtension.length !== 0) { + out += formattedExtension; + } + }); + + return chop(out); +} + +/** + * Format extension critical tag + * @param {*} extension Object + * @returns String describing whether the extension is critical or not + */ +function formatExtensionCriticalTag(extension) { + return Object.hasOwn(extension, "critical") && extension.critical ? " critical" : ""; +} + +/** + * Format string input as a comma separated hex string on multiple lines + * @param {*} hex String + * @returns Multi-line string describing the Hex input + */ +function formatHexOntoMultiLine(hex) { + if (hex.length % 2 !== 0) { + hex = "0" + hex; + } + + return formatMultiLine(chop(hex.replace(/(..)/g, "$&:"))); +} + +/** + * Convert BigInt to abs value in Hex + * @param {*} int BigInt + * @returns String representing absolute value in Hex + */ +function absBigIntToHex(int) { + int = int < 0n ? -int : int; + + return ensureHexIsPositiveInTwosComplement(int.toString(16)); +} + +/** + * Ensure Hex String remains positive in 2's complement + * @param {*} hex String + * @returns Hex String ensuring value remains positive in 2's complement + */ +function ensureHexIsPositiveInTwosComplement(hex) { + if (hex.length % 2 !== 0) { + return "0" + hex; + } + + // prepend 00 if most significant bit is 1 (sign bit) + if (hex.length >=2 && (parseInt(hex.substring(0, 2), 16) & 128)) { + hex = "00" + hex; + } + + return hex; +} + +/** + * Format string onto multiple lines + * @param {*} longStr + * @returns String as a multi-line string + */ +function formatMultiLine(longStr) { + const lines = []; + + for (let remain = longStr ; remain !== "" ; remain = remain.substring(48)) { + lines.push(remain.substring(0, 48)); + } + + return lines.join("\n "); +} + +/** + * Describe Basic Constraints + * @see RFC 5280 4.2.1.9. Basic Constraints https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension CSR extension with the name `basicConstraints` + * @returns Array of strings describing Basic Constraints + */ +function describeBasicConstraints(extension) { + const constraints = []; + + constraints.push(`CA = ${Object.hasOwn(extension, "cA") && extension.cA ? "true" : "false"}`); + if (Object.hasOwn(extension, "pathLen")) constraints.push(`PathLenConstraint = ${extension.pathLen}`); + + return constraints; +} + +/** + * Describe Key Usage extension permitted use cases + * @see RFC 5280 4.2.1.3. Key Usage https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension CSR extension with the name `keyUsage` + * @returns Array of strings describing Key Usage extension permitted use cases + */ +function describeKeyUsage(extension) { + const usage = []; + + const kuIdentifierToName = { + digitalSignature: "Digital Signature", + nonRepudiation: "Non-repudiation", + keyEncipherment: "Key encipherment", + dataEncipherment: "Data encipherment", + keyAgreement: "Key agreement", + keyCertSign: "Key certificate signing", + cRLSign: "CRL signing", + encipherOnly: "Encipher Only", + decipherOnly: "Decipher Only", + }; + + if (Object.hasOwn(extension, "names")) { + extension.names.forEach((ku) => { + if (Object.hasOwn(kuIdentifierToName, ku)) { + usage.push(kuIdentifierToName[ku]); + } else { + usage.push(`unknown key usage (${ku})`); + } + }); + } + + if (usage.length === 0) usage.push("(none)"); + + return usage; +} + +/** + * Describe Extended Key Usage extension permitted use cases + * @see RFC 5280 4.2.1.12. Extended Key Usage https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension CSR extension with the name `extendedKeyUsage` + * @returns Array of strings describing Extended Key Usage extension permitted use cases + */ +function describeExtendedKeyUsage(extension) { + const usage = []; + + const ekuIdentifierToName = { + "serverAuth": "TLS Web Server Authentication", + "clientAuth": "TLS Web Client Authentication", + "codeSigning": "Code signing", + "emailProtection": "E-mail Protection (S/MIME)", + "timeStamping": "Trusted Timestamping", + "1.3.6.1.4.1.311.2.1.21": "Microsoft Individual Code Signing", // msCodeInd + "1.3.6.1.4.1.311.2.1.22": "Microsoft Commercial Code Signing", // msCodeCom + "1.3.6.1.4.1.311.10.3.1": "Microsoft Trust List Signing", // msCTLSign + "1.3.6.1.4.1.311.10.3.3": "Microsoft Server Gated Crypto", // msSGC + "1.3.6.1.4.1.311.10.3.4": "Microsoft Encrypted File System", // msEFS + "1.3.6.1.4.1.311.20.2.2": "Microsoft Smartcard Login", // msSmartcardLogin + "2.16.840.1.113730.4.1": "Netscape Server Gated Crypto", // nsSGC + }; + + if (Object.hasOwn(extension, "array")) { + extension.array.forEach((eku) => { + if (Object.hasOwn(ekuIdentifierToName, eku)) { + usage.push(ekuIdentifierToName[eku]); + } else { + usage.push(eku); + } + }); + } + + if (usage.length === 0) usage.push("(none)"); + + return usage; +} + +/** + * Format Subject Alternative Names from the name `subjectAltName` extension + * @see RFC 5280 4.2.1.6. Subject Alternative Name https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension object + * @returns Array of strings describing Subject Alternative Name extension + */ +function describeSubjectAlternativeName(extension) { + const names = []; + + if (Object.hasOwn(extension, "extname") && extension.extname === "subjectAltName") { + if (Object.hasOwn(extension, "array")) { + for (const altName of extension.array) { + Object.keys(altName).forEach((key) => { + switch (key) { + case "rfc822": + names.push(`EMAIL: ${altName[key]}`); + break; + case "dns": + names.push(`DNS: ${altName[key]}`); + break; + case "uri": + names.push(`URI: ${altName[key]}`); + break; + case "ip": + names.push(`IP: ${altName[key]}`); + break; + case "dn": + names.push(`DIR: ${altName[key].str}`); + break; + case "other" : + names.push(`Other: ${altName[key].oid}::${altName[key].value.utf8str.str}`); + break; + default: + names.push(`(unable to format SAN '${key}':${altName[key]})\n`); + } + }); + } + } + } + + return names; +} + +/** + * Join an array of strings and add leading spaces to each line. + * @param {*} n How many leading spaces + * @param {*} parts Array of strings + * @returns Joined and indented string. + */ +function indent(n, parts) { + const fluff = " ".repeat(n); + return fluff + parts.join("\n" + fluff) + "\n"; +} + +/** + * Remove last character from a string. + * @param {*} s String + * @returns Chopped string. + */ +function chop(s) { + return s.substring(0, s.length - 1); +} + +export default ParseCSR; diff --git a/src/core/operations/ParseColourCode.mjs b/src/core/operations/ParseColourCode.mjs new file mode 100644 index 00000000..31e575a1 --- /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.mjs"; + +/** + * 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..1fb6fb5e --- /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.mjs"; +import moment from "moment-timezone"; +import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime.mjs"; + +/** + * 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.week() + + "\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..2c59c015 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ipv4CidrRange, ipv4HyphenatedRange, ipv4ListedRange, ipv6CidrRange, ipv6HyphenatedRange, ipv6ListedRange} from "../lib/IP.mjs"; + +/** + * 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..84351cdc --- /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.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {fromHex, toHex} from "../lib/Hex.mjs"; +import {ipv4ToStr, protocolLookup} from "../lib/IP.mjs"; +import TCPIPChecksum from "./TCPIPChecksum.mjs"; + +/** + * 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 = new Uint8Array(Utils.strToArrayBuffer(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..f2141e01 --- /dev/null +++ b/src/core/operations/ParseIPv6Address.mjs @@ -0,0 +1,276 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {strToIpv6, ipv6ToStr, ipv4ToStr, IPV6_REGEX} from "../lib/IP.mjs"; +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"; + + switch (ipv6[0]) { + case 0xff01: + output += "\n\nReserved Multicast Block for Interface Local Scope"; + break; + case 0xff02: + output += "\n\nReserved Multicast Block for Link Local Scope"; + break; + case 0xff03: + output += "\n\nReserved Multicast Block for Realm Local Scope"; + break; + case 0xff04: + output += "\n\nReserved Multicast Block for Admin Local Scope"; + break; + case 0xff05: + output += "\n\nReserved Multicast Block for Site Local Scope"; + break; + case 0xff08: + output += "\n\nReserved Multicast Block for Organisation Local Scope"; + break; + case 0xff0e: + output += "\n\nReserved Multicast Block for Global Scope"; + break; + } + + if (ipv6[6] === 1) { + if (ipv6[7] === 2) { + output += "\nReserved Multicast Address for 'All DHCP Servers and Relay Agents (defined in RFC3315)'"; + } else if (ipv6[7] === 3) { + output += "\nReserved Multicast Address for 'All LLMNR Hosts (defined in RFC4795)'"; + } + } else { + switch (ipv6[7]) { + case 1: + output += "\nReserved Multicast Address for 'All nodes'"; + break; + case 2: + output += "\nReserved Multicast Address for 'All routers'"; + break; + case 5: + output += "\nReserved Multicast Address for 'OSPFv3 - All OSPF routers'"; + break; + case 6: + output += "\nReserved Multicast Address for 'OSPFv3 - All Designated Routers'"; + break; + case 8: + output += "\nReserved Multicast Address for 'IS-IS for IPv6 Routers'"; + break; + case 9: + output += "\nReserved Multicast Address for 'RIP Routers'"; + break; + case 0xa: + output += "\nReserved Multicast Address for 'EIGRP Routers'"; + break; + case 0xc: + output += "\nReserved Multicast Address for 'Simple Service Discovery Protocol'"; + break; + case 0xd: + output += "\nReserved Multicast Address for 'PIM Routers'"; + break; + case 0x16: + output += "\nReserved Multicast Address for 'MLDv2 Reports (defined in RFC3810)'"; + break; + case 0x6b: + output += "\nReserved Multicast Address for 'Precision Time Protocol v2 Peer Delay Measurement Messages'"; + break; + case 0xfb: + output += "\nReserved Multicast Address for 'Multicast DNS'"; + break; + case 0x101: + output += "\nReserved Multicast Address for 'Network Time Protocol'"; + break; + case 0x108: + output += "\nReserved Multicast Address for 'Network Information Service'"; + break; + case 0x114: + output += "\nReserved Multicast Address for 'Experiments'"; + break; + case 0x181: + output += "\nReserved Multicast Address for 'Precision Time Protocol v2 Messages (exc. Peer Delay)'"; + break; + } + } + } + + + // 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/ParseObjectIDTimestamp.mjs b/src/core/operations/ParseObjectIDTimestamp.mjs new file mode 100644 index 00000000..f86c098e --- /dev/null +++ b/src/core/operations/ParseObjectIDTimestamp.mjs @@ -0,0 +1,47 @@ +/** + * @author dmfj [dominic@dmfj.io] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import BSON from "bson"; + +/** + * Parse ObjectID timestamp operation + */ +class ParseObjectIDTimestamp extends Operation { + + /** + * ParseObjectIDTimestamp constructor + */ + constructor() { + super(); + + this.name = "Parse ObjectID timestamp"; + this.module = "Serialise"; + this.description = "Parse timestamp from MongoDB/BSON ObjectID hex string."; + this.infoURL = "https://docs.mongodb.com/manual/reference/method/ObjectId.getTimestamp/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + try { + const objectId = new BSON.ObjectID(input); + return objectId.getTimestamp().toISOString(); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default ParseObjectIDTimestamp; diff --git a/src/core/operations/ParseQRCode.mjs b/src/core/operations/ParseQRCode.mjs new file mode 100644 index 00000000..77ab7d21 --- /dev/null +++ b/src/core/operations/ParseQRCode.mjs @@ -0,0 +1,62 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { parseQrCode } from "../lib/QRCode.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Normalise image", + "type": "boolean", + "value": false + } + ]; + this.checks = [ + { + "pattern": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)", + "flags": "", + "args": [false], + "useful": true + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [normalise] = args; + + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + return await parseQrCode(input, normalise); + } + +} + +export default ParseQRCode; diff --git a/src/core/operations/ParseSSHHostKey.mjs b/src/core/operations/ParseSSHHostKey.mjs new file mode 100644 index 00000000..f1a1f58c --- /dev/null +++ b/src/core/operations/ParseSSHHostKey.mjs @@ -0,0 +1,159 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { fromBase64 } from "../lib/Base64.mjs"; +import { fromHex, toHexFast } from "../lib/Hex.mjs"; + +/** + * Parse SSH Host Key operation + */ +class ParseSSHHostKey extends Operation { + + /** + * ParseSSHHostKey constructor + */ + constructor() { + super(); + + this.name = "Parse SSH Host Key"; + this.module = "Default"; + this.description = "Parses a SSH host key and extracts fields from it.
The key type can be:
  • ssh-rsa
  • ssh-dss
  • ecdsa-sha2
  • ssh-ed25519
The key format can be either Hex or Base64."; + this.infoURL = "https://wikipedia.org/wiki/Secure_Shell"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input Format", + type: "option", + value: [ + "Auto", + "Base64", + "Hex" + ] + } + ]; + this.checks = [ + { + pattern: "^\\s*([A-F\\d]{2}[,;:]){15,}[A-F\\d]{2}\\s*$", + flags: "i", + args: ["Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat] = args, + inputKey = this.convertKeyToBinary(input.trim(), inputFormat), + fields = this.parseKey(inputKey), + keyType = Utils.byteArrayToChars(fromHex(fields[0]), ""); + + let output = `Key type: ${keyType}`; + + if (keyType === "ssh-rsa") { + output += `\nExponent: 0x${fields[1]}`; + output += `\nModulus: 0x${fields[2]}`; + } else if (keyType === "ssh-dss") { + output += `\np: 0x${fields[1]}`; + output += `\nq: 0x${fields[2]}`; + output += `\ng: 0x${fields[3]}`; + output += `\ny: 0x${fields[4]}`; + } else if (keyType.startsWith("ecdsa-sha2")) { + output += `\nCurve: ${Utils.byteArrayToChars(fromHex(fields[1]))}`; + output += `\nPoint: 0x${fields.slice(2)}`; + } else if (keyType === "ssh-ed25519") { + output += `\nx: 0x${fields[1]}`; + } else { + output += "\nUnsupported key type."; + output += `\nParameters: ${fields.slice(1)}`; + } + + return output; + } + + /** + * Converts the key to binary format from either hex or base64 + * + * @param {string} inputKey + * @param {string} inputFormat + * @returns {byteArray} + */ + convertKeyToBinary(inputKey, inputFormat) { + const keyPattern = new RegExp(/^(?:ssh|ecdsa-sha2)\S+\s+(\S*)/), + keyMatch = inputKey.match(keyPattern); + + if (keyMatch) { + inputKey = keyMatch[1]; + } + + if (inputFormat === "Auto") { + inputFormat = this.detectKeyFormat(inputKey); + } + if (inputFormat === "Hex") { + return fromHex(inputKey); + } else if (inputFormat === "Base64") { + return fromBase64(inputKey, null, "byteArray"); + } else { + throw new OperationError("Invalid input format."); + } + } + + + /** + * Detects if the key is base64 or hex encoded + * + * @param {string} inputKey + * @returns {string} + */ + detectKeyFormat(inputKey) { + const hexPattern = new RegExp(/^(?:[\dA-Fa-f]{2}[ ,;:]?)+$/); + const b64Pattern = new RegExp(/^\s*(?:[A-Za-z\d+/]{4})+(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?\s*$/); + + if (hexPattern.test(inputKey)) { + return "Hex"; + } else if (b64Pattern.test(inputKey)) { + return "Base64"; + } else { + throw new OperationError("Unable to detect input key format."); + } + } + + + /** + * Parses fields from the key + * + * @param {byteArray} key + */ + parseKey(key) { + const fields = []; + while (key.length > 0) { + const lengthField = key.slice(0, 4); + let decodedLength = 0; + for (let i = 0; i < lengthField.length; i++) { + decodedLength += lengthField[i]; + decodedLength = decodedLength << 8; + } + decodedLength = decodedLength >> 8; + // Break if length wasn't decoded correctly + if (decodedLength <= 0) break; + + fields.push(toHexFast(key.slice(4, 4 + decodedLength))); + key = key.slice(4 + decodedLength); + } + + return fields; + } + +} + +export default ParseSSHHostKey; diff --git a/src/core/operations/ParseTCP.mjs b/src/core/operations/ParseTCP.mjs new file mode 100644 index 00000000..7adb37e0 --- /dev/null +++ b/src/core/operations/ParseTCP.mjs @@ -0,0 +1,245 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Stream from "../lib/Stream.mjs"; +import {toHexFast, fromHex} from "../lib/Hex.mjs"; +import {toBinary} from "../lib/Binary.mjs"; +import {objToTable, bytesToLargeNumber} from "../lib/Protocol.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import BigNumber from "bignumber.js"; + +/** + * Parse TCP operation + */ +class ParseTCP extends Operation { + + /** + * ParseTCP constructor + */ + constructor() { + super(); + + this.name = "Parse TCP"; + this.module = "Default"; + this.description = "Parses a TCP header and payload (if present)."; + this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol"; + this.inputType = "string"; + this.outputType = "json"; + this.presentType = "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]; + + if (format === "Hex") { + input = fromHex(input); + } else if (format === "Raw") { + input = Utils.strToArrayBuffer(input); + } else { + throw new OperationError("Unrecognised input format."); + } + + const s = new Stream(new Uint8Array(input)); + if (s.length < 20) { + throw new OperationError("Need at least 20 bytes for a TCP Header"); + } + + // Parse Header + const TCPPacket = { + "Source port": s.readInt(2), + "Destination port": s.readInt(2), + "Sequence number": bytesToLargeNumber(s.getBytes(4)), + "Acknowledgement number": s.readInt(4), + "Data offset": s.readBits(4), + "Flags": { + "Reserved": toBinary(s.readBits(3), "", 3), + "NS": s.readBits(1), + "CWR": s.readBits(1), + "ECE": s.readBits(1), + "URG": s.readBits(1), + "ACK": s.readBits(1), + "PSH": s.readBits(1), + "RST": s.readBits(1), + "SYN": s.readBits(1), + "FIN": s.readBits(1), + }, + "Window size": s.readInt(2), + "Checksum": "0x" + toHexFast(s.getBytes(2)), + "Urgent pointer": "0x" + toHexFast(s.getBytes(2)) + }; + + // Parse options if present + let windowScaleShift = 0; + if (TCPPacket["Data offset"] > 5) { + let remainingLength = TCPPacket["Data offset"] * 4 - 20; + + const options = {}; + while (remainingLength > 0) { + const option = { + "Kind": s.readInt(1) + }; + + let opt = { name: "Reserved", length: true }; + if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) { + opt = TCP_OPTION_KIND_LOOKUP[option.Kind]; + } + + // Add Length and Value fields + if (opt.length) { + option.Length = s.readInt(1); + + if (option.Length > 2) { + if (Object.prototype.hasOwnProperty.call(opt, "parser")) { + option.Value = opt.parser(s.getBytes(option.Length - 2)); + } else { + option.Value = option.Length <= 6 ? + s.readInt(option.Length - 2): + "0x" + toHexFast(s.getBytes(option.Length - 2)); + } + + // Store Window Scale shift for later + if (option.Kind === 3 && option.Value) { + windowScaleShift = option.Value["Shift count"]; + } + } + } + options[opt.name] = option; + + const length = option.Length || 1; + remainingLength -= length; + } + TCPPacket.Options = options; + } + + if (s.hasMore()) { + TCPPacket.Data = "0x" + toHexFast(s.getBytes()); + } + + // Improve values + TCPPacket["Data offset"] = `${TCPPacket["Data offset"]} (${TCPPacket["Data offset"] * 4} bytes)`; + const trueWndSize = BigNumber(TCPPacket["Window size"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift))); + TCPPacket["Window size"] = `${TCPPacket["Window size"]} (Scaled: ${trueWndSize})`; + + return TCPPacket; + } + + /** + * Displays the TCP Packet in a tabular style + * @param {Object} data + * @returns {html} + */ + present(data) { + return objToTable(data); + } + +} + +// Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml +// on 2022-05-30 +const TCP_OPTION_KIND_LOOKUP = { + 0: { name: "End of Option List", length: false }, + 1: { name: "No-Operation", length: false }, + 2: { name: "Maximum Segment Size", length: true }, + 3: { name: "Window Scale", length: true, parser: windowScaleParser }, + 4: { name: "SACK Permitted", length: true }, + 5: { name: "SACK", length: true }, + 6: { name: "Echo (obsoleted by option 8)", length: true }, + 7: { name: "Echo Reply (obsoleted by option 8)", length: true }, + 8: { name: "Timestamps", length: true, parser: tcpTimestampParser }, + 9: { name: "Partial Order Connection Permitted (obsolete)", length: true }, + 10: { name: "Partial Order Service Profile (obsolete)", length: true }, + 11: { name: "CC (obsolete)", length: true }, + 12: { name: "CC.NEW (obsolete)", length: true }, + 13: { name: "CC.ECHO (obsolete)", length: true }, + 14: { name: "TCP Alternate Checksum Request (obsolete)", length: true, parser: tcpAlternateChecksumParser }, + 15: { name: "TCP Alternate Checksum Data (obsolete)", length: true }, + 16: { name: "Skeeter", length: true }, + 17: { name: "Bubba", length: true }, + 18: { name: "Trailer Checksum Option", length: true }, + 19: { name: "MD5 Signature Option (obsoleted by option 29)", length: true }, + 20: { name: "SCPS Capabilities", length: true }, + 21: { name: "Selective Negative Acknowledgements", length: true }, + 22: { name: "Record Boundaries", length: true }, + 23: { name: "Corruption experienced", length: true }, + 24: { name: "SNAP", length: true }, + 25: { name: "Unassigned (released 2000-12-18)", length: true }, + 26: { name: "TCP Compression Filter", length: true }, + 27: { name: "Quick-Start Response", length: true }, + 28: { name: "User Timeout Option (also, other known unauthorized use)", length: true }, + 29: { name: "TCP Authentication Option (TCP-AO)", length: true }, + 30: { name: "Multipath TCP (MPTCP)", length: true }, + 69: { name: "Encryption Negotiation (TCP-ENO)", length: true }, + 70: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 76: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 77: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 78: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 253: { name: "RFC3692-style Experiment 1 (also improperly used for shipping products) ", length: true }, + 254: { name: "RFC3692-style Experiment 2 (also improperly used for shipping products) ", length: true } +}; + +/** + * Parses the TCP Alternate Checksum Request field + * @param {Uint8Array} data + */ +function tcpAlternateChecksumParser(data) { + const lookup = { + 0: "TCP Checksum", + 1: "8-bit Fletchers's algorithm", + 2: "16-bit Fletchers's algorithm", + 3: "Redundant Checksum Avoidance" + }[data[0]]; + + return `${lookup} (0x${toHexFast(data)})`; +} + +/** + * Parses the TCP Timestamp field + * @param {Uint8Array} data + */ +function tcpTimestampParser(data) { + const s = new Stream(data); + + if (s.length !== 8) + return `Error: Timestamp field should be 8 bytes long (received 0x${toHexFast(data)})`; + + const tsval = bytesToLargeNumber(s.getBytes(4)), + tsecr = bytesToLargeNumber(s.getBytes(4)); + + return { + "Current Timestamp": tsval, + "Echo Reply": tsecr + }; +} + +/** + * Parses the Window Scale field + * @param {Uint8Array} data + */ +function windowScaleParser(data) { + if (data.length !== 1) + return `Error: Window Scale should be one byte long (received 0x${toHexFast(data)})`; + + return { + "Shift count": data[0], + "Multiplier": 1 << data[0] + }; +} + +export default ParseTCP; diff --git a/src/core/operations/ParseTLSRecord.mjs b/src/core/operations/ParseTLSRecord.mjs new file mode 100644 index 00000000..57a339a8 --- /dev/null +++ b/src/core/operations/ParseTLSRecord.mjs @@ -0,0 +1,884 @@ +/** + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {toHexFast} from "../lib/Hex.mjs"; +import {objToTable} from "../lib/Protocol.mjs"; +import Stream from "../lib/Stream.mjs"; + +/** + * Parse TLS record operation. + */ +class ParseTLSRecord extends Operation { + + /** + * ParseTLSRecord constructor. + */ + constructor() { + super(); + + this.name = "Parse TLS record"; + this.module = "Default"; + this.description = "Parses one or more TLS records"; + this.infoURL = "https://wikipedia.org/wiki/Transport_Layer_Security"; + this.inputType = "ArrayBuffer"; + this.outputType = "json"; + this.presentType = "html"; + this.args = []; + this._handshakeParser = new HandshakeParser(); + this._contentTypes = new Map(); + + for (const key in ContentType) { + this._contentTypes[ContentType[key]] = key.toString().toLocaleLowerCase(); + } + } + + /** + * @param {ArrayBuffer} input - Stream, containing one or more raw TLS Records. + * @param {Object[]} args + * @returns {Object[]} Array of Object representations of TLS Records contained within input. + */ + run(input, args) { + const s = new Stream(new Uint8Array(input)); + + const output = []; + + while (s.hasMore()) { + const record = this._readRecord(s); + if (record) { + output.push(record); + } + } + + return output; + } + + /** + * Reads a TLS Record from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw TLS Record. + * @returns {Object} Object representation of TLS Record. + */ + _readRecord(input) { + const RECORD_HEADER_LEN = 5; + + if (input.position + RECORD_HEADER_LEN > input.length) { + input.moveTo(input.length); + + return null; + } + + const type = input.readInt(1); + const typeString = this._contentTypes[type] ?? type.toString(); + const version = "0x" + toHexFast(input.getBytes(2)); + const length = input.readInt(2); + const content = input.getBytes(length); + const truncated = content.length < length; + + const recordHeader = new RecordHeader(typeString, version, length, truncated); + + if (!content.length) { + return {...recordHeader}; + } + + if (type === ContentType.HANDSHAKE) { + return this._handshakeParser.parse(new Stream(content), recordHeader); + } + + const record = {...recordHeader}; + record.value = "0x" + toHexFast(content); + + return record; + } + + /** + * Displays the parsed TLS Records in a tabular style. + * + * @param {Object[]} data - Array of Object representations of the TLS Records. + * @returns {html} HTML representation of TLS Records contained within data. + */ + present(data) { + return data.map(r => objToTable(r)).join("\n\n"); + } +} + +export default ParseTLSRecord; + +/** + * Repesents the known values of type field of a TLS Record header. + */ +const ContentType = Object.freeze({ + CHANGE_CIPHER_SPEC: 20, + ALERT: 21, + HANDSHAKE: 22, + APPLICATION_DATA: 23, +}); + +/** + * Represents a TLS Record header + */ +class RecordHeader { + /** + * RecordHeader cosntructor. + * + * @param {string} type - String representation of TLS Record type field. + * @param {string} version - Hex representation of TLS Record version field. + * @param {int} length - Length of TLS Record. + * @param {bool} truncated - Is TLS Record truncated. + */ + constructor(type, version, length, truncated) { + this.type = type; + this.version = version; + this.length = length; + + if (truncated) { + this.truncated = true; + } + } +} + +/** + * Parses TLS Handshake messages. + */ +class HandshakeParser { + + /** + * HandshakeParser constructor. + */ + constructor() { + this._clientHelloParser = new ClientHelloParser(); + this._serverHelloParser = new ServerHelloParser(); + this._newSessionTicketParser = new NewSessionTicketParser(); + this._certificateParser = new CertificateParser(); + this._certificateRequestParser = new CertificateRequestParser(); + this._certificateVerifyParser = new CertificateVerifyParser(); + this._handshakeTypes = new Map(); + + for (const key in HandshakeType) { + this._handshakeTypes[HandshakeType[key]] = key.toString().toLowerCase(); + } + } + + /** + * Parses a single TLS handshake message. + * + * @param {Stream} input - Stream, containing a raw Handshake message. + * @param {RecordHeader} recordHeader - TLS Record header. + * @returns {Object} Object representation of Handshake. + */ + parse(input, recordHeader) { + const output = {...recordHeader}; + + if (!input.hasMore()) { + return output; + } + + const handshakeType = input.readInt(1); + output.handshakeType = this._handshakeTypes[handshakeType] ?? handshakeType.toString(); + + if (input.position + 3 > input.length) { + input.moveTo(input.length); + + return output; + } + + const handshakeLength = input.readInt(3); + + if (handshakeLength + 4 !== recordHeader.length) { + input.moveTo(0); + + output.handshakeType = this._handshakeTypes[HandshakeType.FINISHED]; + output.handshakeValue = "0x" + toHexFast(input.bytes); + + return output; + } + + const content = input.getBytes(handshakeLength); + if (!content.length) { + return output; + } + + switch (handshakeType) { + case HandshakeType.CLIENT_HELLO: + return {...output, ...this._clientHelloParser.parse(new Stream(content))}; + case HandshakeType.SERVER_HELLO: + return {...output, ...this._serverHelloParser.parse(new Stream(content))}; + case HandshakeType.NEW_SESSION_TICKET: + return {...output, ...this._newSessionTicketParser.parse(new Stream(content))}; + case HandshakeType.CERTIFICATE: + return {...output, ...this._certificateParser.parse(new Stream(content))}; + case HandshakeType.CERTIFICATE_REQUEST: + return {...output, ...this._certificateRequestParser.parse(new Stream(content))}; + case HandshakeType.CERTIFICATE_VERIFY: + return {...output, ...this._certificateVerifyParser.parse(new Stream(content))}; + default: + output.handshakeValue = "0x" + toHexFast(content); + } + + return output; + } +} + +/** + * Represents the known values of the msg_type field of a TLS Handshake message. + */ +const HandshakeType = Object.freeze({ + HELLO_REQUEST: 0, + CLIENT_HELLO: 1, + SERVER_HELLO: 2, + NEW_SESSION_TICKET: 4, + CERTIFICATE: 11, + SERVER_KEY_EXCHANGE: 12, + CERTIFICATE_REQUEST: 13, + SERVER_HELLO_DONE: 14, + CERTIFICATE_VERIFY: 15, + CLIENT_KEY_EXCHANGE: 16, + FINISHED: 20, +}); + +/** + * Parses TLS Handshake ClientHello messages. + */ +class ClientHelloParser { + + /** + * ClientHelloParser constructor. + */ + constructor() { + this._extensionsParser = new ExtensionsParser(); + } + + /** + * Parses a single TLS Handshake ClientHello message. + * + * @param {Stream} input - Stream, containing a raw ClientHello message. + * @returns {Object} Object representation of ClientHello. + */ + parse(input) { + const output = {}; + + output.clientVersion = this._readClientVersion(input); + output.random = this._readRandom(input); + + const sessionID = this._readSessionID(input); + if (sessionID) { + output.sessionID = sessionID; + } + + output.cipherSuites = this._readCipherSuites(input); + output.compressionMethods = this._readCompressionMethods(input); + output.extensions = this._readExtensions(input); + + return output; + } + + /** + * Reads the client_version field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before client_version field. + * @returns {string} Hex representation of client_version. + */ + _readClientVersion(input) { + return readBytesAsHex(input, 2); + } + + /** + * Reads the random field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before random field. + * @returns {string} Hex representation of random. + */ + _readRandom(input) { + return readBytesAsHex(input, 32); + } + + /** + * Reads the session_id field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before session_id length field. + * @returns {string} Hex representation of session_id, or empty string if session_id not present. + */ + _readSessionID(input) { + return readSizePrefixedBytesAsHex(input, 1); + } + + /** + * Reads the cipher_suites field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before cipher_suites length field. + * @returns {Object} Object represention of cipher_suites field. + */ + _readCipherSuites(input) { + const output = {}; + + output.length = input.readInt(2); + if (!output.length) { + return {}; + } + + const cipherSuites = new Stream(input.getBytes(output.length)); + if (cipherSuites.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (cipherSuites.hasMore()) { + const cipherSuite = readBytesAsHex(cipherSuites, 2); + if (cipherSuite) { + output.values.push(cipherSuite); + } + } + + return output; + } + + /** + * Reads the compression_methods field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before compression_methods length field. + * @returns {Object} Object representation of compression_methods field. + */ + _readCompressionMethods(input) { + const output = {}; + + output.length = input.readInt(1); + if (!output.length) { + return {}; + } + + const compressionMethods = new Stream(input.getBytes(output.length)); + if (compressionMethods.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (compressionMethods.hasMore()) { + const compressionMethod = readBytesAsHex(compressionMethods, 1); + if (compressionMethod) { + output.values.push(compressionMethod); + } + } + + return output; + } + + /** + * Reads the extensions field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ClientHello message, with position before extensions length field. + * @returns {Object} Object representations of extensions field. + */ + _readExtensions(input) { + const output = {}; + + output.length = input.readInt(2); + if (!output.length) { + return {}; + } + + const extensions = new Stream(input.getBytes(output.length)); + if (extensions.length < output.length) { + output.truncated = true; + } + + output.values = this._extensionsParser.parse(extensions); + + return output; + } +} + +/** + * Parses TLS Handshake ServeHello messages. + */ +class ServerHelloParser { + + /** + * ServerHelloParser constructor. + */ + constructor() { + this._extensionsParser = new ExtensionsParser(); + } + + /** + * Parses a single TLS Handshake ServerHello message. + * + * @param {Stream} input - Stream, containing a raw ServerHello message. + * @return {Object} Object representation of ServerHello. + */ + parse(input) { + const output = {}; + + output.serverVersion = this._readServerVersion(input); + output.random = this._readRandom(input); + + const sessionID = this._readSessionID(input); + if (sessionID) { + output.sessionID = sessionID; + } + + output.cipherSuite = this._readCipherSuite(input); + output.compressionMethod = this._readCompressionMethod(input); + output.extensions = this._readExtensions(input); + + return output; + } + + /** + * Reads the server_version field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServerHello message, with position before server_version field. + * @returns {string} Hex representation of server_version. + */ + _readServerVersion(input) { + return readBytesAsHex(input, 2); + } + + /** + * Reads the random field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServerHello message, with position before random field. + * @returns {string} Hex representation of random. + */ + _readRandom(input) { + return readBytesAsHex(input, 32); + } + + /** + * Reads the session_id field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServertHello message, with position before session_id length field. + * @returns {string} Hex representation of session_id, or empty string if session_id not present. + */ + _readSessionID(input) { + return readSizePrefixedBytesAsHex(input, 1); + } + + /** + * Reads the cipher_suite field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServerHello message, with position before cipher_suite field. + * @returns {string} Hex represention of cipher_suite. + */ + _readCipherSuite(input) { + return readBytesAsHex(input, 2); + } + + /** + * Reads the compression_method field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServerHello message, with position before compression_method field. + * @returns {string} Hex represention of compression_method. + */ + _readCompressionMethod(input) { + return readBytesAsHex(input, 1); + } + + /** + * Reads the extensions field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw ServerHello message, with position before extensions length field. + * @returns {Object} Object representation of extensions field. + */ + _readExtensions(input) { + const output = {}; + + output.length = input.readInt(2); + if (!output.length) { + return {}; + } + + const extensions = new Stream(input.getBytes(output.length)); + if (extensions.length < output.length) { + output.truncated = true; + } + + output.values = this._extensionsParser.parse(extensions); + + return output; + } +} + +/** + * Parses TLS Handshake Hello Extensions. + */ +class ExtensionsParser { + + /** + * Parses a stream of TLS Handshake Hello Extensions. + * + * @param {Stream} input - Stream, containing multiple raw Extensions, with position before first extension length field. + * @returns {Object[]} Array of Object representations of Extensions contained within input. + */ + parse(input) { + const output = []; + + while (input.hasMore()) { + const extension = this._readExtension(input); + if (extension) { + output.push(extension); + } + } + + return output; + } + + /** + * Reads a single Extension from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a list of Extensions, with position before the length field of the next Extension. + * @returns {Object} Object representation of Extension. + */ + _readExtension(input) { + const output = {}; + + if (input.position + 4 > input.length) { + input.moveTo(input.length); + return null; + } + + output.type = "0x" + toHexFast(input.getBytes(2)); + output.length = input.readInt(2); + if (!output.length) { + return output; + } + + const value = input.getBytes(output.length); + if (!value || value.length !== output.length) { + output.truncated = true; + } + + if (value && value.length) { + output.value = "0x" + toHexFast(value); + } + + return output; + } +} + +/** + * Parses TLS Handshake NewSessionTicket messages. + */ +class NewSessionTicketParser { + + /** + * Parses a single TLS Handshake NewSessionTicket message. + * + * @param {Stream} input - Stream, containing a raw NewSessionTicket message. + * @returns {Object} Object representation of NewSessionTicket. + */ + parse(input) { + return { + ticketLifetimeHint: this._readTicketLifetimeHint(input), + ticket: this._readTicket(input), + }; + } + + /** + * Reads the ticket_lifetime_hint field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket_lifetime_hint field. + * @returns {string} Lifetime hint, in seconds. + */ + _readTicketLifetimeHint(input) { + if (input.position + 4 > input.length) { + input.moveTo(input.length); + return ""; + } + + return input.readInt(4) + "s"; + } + + /** + * Reads the ticket field fromt the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket length field. + * @returns {string} Hex representation of ticket. + */ + _readTicket(input) { + return readSizePrefixedBytesAsHex(input, 2); + } +} + +/** + * Parses TLS Handshake Certificate messages. + */ +class CertificateParser { + + /** + * Parses a single TLS Handshake Certificate message. + * + * @param {Stream} input - Stream, containing a raw Certificate message. + * @returns {Object} Object representation of Certificate. + */ + parse(input) { + const output = {}; + + output.certificateList = this._readCertificateList(input); + + return output; + } + + /** + * Reads the certificate_list field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw Certificate message, with position before certificate_list length field. + * @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_list field. + */ + _readCertificateList(input) { + const output = {}; + + if (input.position + 3 > input.length) { + input.moveTo(input.length); + return output; + } + + output.length = input.readInt(3); + if (!output.length) { + return output; + } + + const certificates = new Stream(input.getBytes(output.length)); + if (certificates.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (certificates.hasMore()) { + const certificate = this._readCertificate(certificates); + if (certificate) { + output.values.push(certificate); + } + } + + return output; + } + + /** + * Reads a single certificate from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a list of certificicates, with position before the length field of the next certificate. + * @returns {string} Hex representation of certificate. + */ + _readCertificate(input) { + return readSizePrefixedBytesAsHex(input, 3); + } +} + +/** + * Parses TLS Handshake CertificateRequest messages. + */ +class CertificateRequestParser { + + /** + * Parses a single TLS Handshake CertificateRequest message. + * + * @param {Stream} input - Stream, containing a raw CertificateRequest message. + * @return {Object} Object representation of CertificateRequest. + */ + parse(input) { + const output = {}; + + output.certificateTypes = this._readCertificateTypes(input); + output.supportedSignatureAlgorithms = this._readSupportedSignatureAlgorithms(input); + + const certificateAuthorities = this._readCertificateAuthorities(input); + if (certificateAuthorities.length) { + output.certificateAuthorities = certificateAuthorities; + } + + return output; + } + + /** + * Reads the certificate_types field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_types length field. + * @return {string[]} Array of strings, each containing a hex representation of a value within the certificate_types field. + */ + _readCertificateTypes(input) { + const output = {}; + + output.length = input.readInt(1); + if (!output.length) { + return {}; + } + + const certificateTypes = new Stream(input.getBytes(output.length)); + if (certificateTypes.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (certificateTypes.hasMore()) { + const certificateType = readBytesAsHex(certificateTypes, 1); + if (certificateType) { + output.values.push(certificateType); + } + } + + return output; + } + + /** + * Reads the supported_signature_algorithms field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before supported_signature_algorithms length field. + * @returns {string[]} Array of strings, each containing a hex representation of a value within the supported_signature_algorithms field. + */ + _readSupportedSignatureAlgorithms(input) { + const output = {}; + + output.length = input.readInt(2); + if (!output.length) { + return {}; + } + + const signatureAlgorithms = new Stream(input.getBytes(output.length)); + if (signatureAlgorithms.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (signatureAlgorithms.hasMore()) { + const signatureAlgorithm = readBytesAsHex(signatureAlgorithms, 2); + if (signatureAlgorithm) { + output.values.push(signatureAlgorithm); + } + } + + return output; + } + + /** + * Reads the certificate_authorities field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_authorities length field. + * @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_authorities field. + */ + _readCertificateAuthorities(input) { + const output = {}; + + output.length = input.readInt(2); + if (!output.length) { + return {}; + } + + const certificateAuthorities = new Stream(input.getBytes(output.length)); + if (certificateAuthorities.length < output.length) { + output.truncated = true; + } + + output.values = []; + + while (certificateAuthorities.hasMore()) { + const certificateAuthority = this._readCertificateAuthority(certificateAuthorities); + if (certificateAuthority) { + output.values.push(certificateAuthority); + } + } + + return output; + } + + /** + * Reads a single certificate authority from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a list of raw certificate authorities, with position before the length field of the next certificate authority. + * @returns {string} Hex representation of certificate authority. + */ + _readCertificateAuthority(input) { + return readSizePrefixedBytesAsHex(input, 2); + } +} + +/** + * Parses TLS Handshake CertificateVerify messages. + */ +class CertificateVerifyParser { + + /** + * Parses a single CertificateVerify Message. + * + * @param {Stream} input - Stream, containing a raw CertificateVerify message. + * @returns {Object} Object representation of CertificateVerify. + */ + parse(input) { + return { + algorithmHash: this._readAlgorithmHash(input), + algorithmSignature: this._readAlgorithmSignature(input), + signature: this._readSignature(input), + }; + } + + /** + * Reads the algorithm.hash field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.hash field. + * @return {string} Hex representation of hash algorithm. + */ + _readAlgorithmHash(input) { + return readBytesAsHex(input, 1); + } + + /** + * Reads the algorithm.signature field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.signature field. + * @return {string} Hex representation of signature algorithm. + */ + _readAlgorithmSignature(input) { + return readBytesAsHex(input, 1); + } + + /** + * Reads the signature field from the following bytes in the provided Stream. + * + * @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before signature field. + * @return {string} Hex representation of signature. + */ + _readSignature(input) { + return readSizePrefixedBytesAsHex(input, 2); + } +} + +/** + * Read the following size prefixed bytes from the provided Stream, and reuturn as a hex string. + * + * @param {Stream} input - Stream to read from. + * @param {int} sizePrefixLength - Length of the size prefix field. + * @returns {string} Hex representation of bytes read from Stream, empty string is returned if + * field cannot be read in full. + */ +function readSizePrefixedBytesAsHex(input, sizePrefixLength) { + const length = input.readInt(sizePrefixLength); + if (!length) { + return ""; + } + + return readBytesAsHex(input, length); +} + +/** + * Read n bytes from the provided Stream, and return as a hex string. + * + * @param {Stream} input - Stream to read from. + * @param {int} n - Number of bytes to read. + * @returns {string} Hex representation of bytes read from Stream, or empty string if field cannot + * be read in full. + */ +function readBytesAsHex(input, n) { + const bytes = input.getBytes(n); + if (!bytes || bytes.length !== n) { + return ""; + } + + return "0x" + toHexFast(bytes); +} diff --git a/src/core/operations/ParseTLV.mjs b/src/core/operations/ParseTLV.mjs new file mode 100644 index 00000000..d4c4e11c --- /dev/null +++ b/src/core/operations/ParseTLV.mjs @@ -0,0 +1,77 @@ +/** + * @author gchq77703 [] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import TLVParser from "../lib/TLVParser.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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 = "ArrayBuffer"; + 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 {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [bytesInKey, bytesInLength, basicEncodingRules] = args; + input = new Uint8Array(input); + + if (bytesInKey <= 0 && bytesInLength <= 0) + throw new OperationError("Type or Length size must be greater than 0"); + + 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/ParseUDP.mjs b/src/core/operations/ParseUDP.mjs new file mode 100644 index 00000000..2aa762ae --- /dev/null +++ b/src/core/operations/ParseUDP.mjs @@ -0,0 +1,89 @@ +/** + * @author h345983745 [] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Stream from "../lib/Stream.mjs"; +import {toHexFast, fromHex} from "../lib/Hex.mjs"; +import {objToTable} from "../lib/Protocol.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Parse UDP operation + */ +class ParseUDP extends Operation { + + /** + * ParseUDP constructor + */ + constructor() { + super(); + + this.name = "Parse UDP"; + this.module = "Default"; + this.description = "Parses a UDP header and payload (if present)."; + this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol"; + this.inputType = "string"; + this.outputType = "json"; + this.presentType = "html"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {Object} + */ + run(input, args) { + const format = args[0]; + + if (format === "Hex") { + input = fromHex(input); + } else if (format === "Raw") { + input = Utils.strToArrayBuffer(input); + } else { + throw new OperationError("Unrecognised input format."); + } + + const s = new Stream(new Uint8Array(input)); + if (s.length < 8) { + throw new OperationError("Need 8 bytes for a UDP Header"); + } + + // Parse Header + const UDPPacket = { + "Source port": s.readInt(2), + "Destination port": s.readInt(2), + "Length": s.readInt(2), + "Checksum": "0x" + toHexFast(s.getBytes(2)) + }; + // Parse data if present + if (s.hasMore()) { + UDPPacket.Data = "0x" + toHexFast(s.getBytes(UDPPacket.Length - 8)); + } + + return UDPPacket; + } + + /** + * Displays the UDP Packet in a tabular style + * @param {Object} data + * @returns {html} + */ + present(data) { + return objToTable(data); + } + +} + + +export default ParseUDP; diff --git a/src/core/operations/ParseUNIXFilePermissions.mjs b/src/core/operations/ParseUNIXFilePermissions.mjs new file mode 100644 index 00000000..ae472a83 --- /dev/null +++ b/src/core/operations/ParseUNIXFilePermissions.mjs @@ -0,0 +1,332 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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 = []; + this.checks = [ + { + pattern: "^\\s*d[rxw-]{9}\\s*$", + flags: "", + 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..17ca90db --- /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.mjs"; +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..57ac9312 --- /dev/null +++ b/src/core/operations/ParseUserAgent.mjs @@ -0,0 +1,63 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +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 = []; + this.checks = [ + { + pattern: "^(User-Agent:|Mozilla\\/)[^\\n\\r]+\\s*$", + flags: "i", + 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/ParseX509CRL.mjs b/src/core/operations/ParseX509CRL.mjs new file mode 100644 index 00000000..f498375d --- /dev/null +++ b/src/core/operations/ParseX509CRL.mjs @@ -0,0 +1,391 @@ +/** + * @author robinsandhu + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import { fromBase64 } from "../lib/Base64.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { formatDnObj } from "../lib/PublicKey.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Parse X.509 CRL operation + */ +class ParseX509CRL extends Operation { + + /** + * ParseX509CRL constructor + */ + constructor() { + super(); + + this.name = "Parse X.509 CRL"; + this.module = "PublicKey"; + this.description = "Parse Certificate Revocation List (CRL)"; + this.infoURL = "https://wikipedia.org/wiki/Certificate_revocation_list"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["PEM", "DER Hex", "Base64", "Raw"] + } + ]; + this.checks = [ + { + "pattern": "^-+BEGIN X509 CRL-+\\r?\\n[\\da-z+/\\n\\r]+-+END X509 CRL-+\\r?\\n?$", + "flags": "i", + "args": ["PEM"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} Human-readable description of a Certificate Revocation List (CRL). + */ + run(input, args) { + if (!input.length) { + return "No input"; + } + + const inputFormat = args[0]; + + let undefinedInputFormat = false; + try { + switch (inputFormat) { + case "DER Hex": + input = input.replace(/\s/g, "").toLowerCase(); + break; + case "PEM": + break; + case "Base64": + input = toHex(fromBase64(input, null, "byteArray"), ""); + break; + case "Raw": + input = toHex(Utils.strToArrayBuffer(input), ""); + break; + default: + undefinedInputFormat = true; + } + } catch (e) { + throw "Certificate load error (non-certificate input?)"; + } + if (undefinedInputFormat) throw "Undefined input format"; + + const crl = new r.X509CRL(input); + + let out = `Certificate Revocation List (CRL): + Version: ${crl.getVersion() === null ? "1 (0x0)" : "2 (0x1)"} + Signature Algorithm: ${crl.getSignatureAlgorithmField()} + Issuer:\n${formatDnObj(crl.getIssuer(), 8)} + Last Update: ${generalizedDateTimeToUTC(crl.getThisUpdate())} + Next Update: ${generalizedDateTimeToUTC(crl.getNextUpdate())}\n`; + + if (crl.getParam().ext !== undefined) { + out += `\tCRL extensions:\n${formatCRLExtensions(crl.getParam().ext, 8)}\n`; + } + + out += `Revoked Certificates:\n${formatRevokedCertificates(crl.getRevCertArray(), 4)} +Signature Value:\n${formatCRLSignature(crl.getSignatureValueHex(), 8)}`; + + return out; + } +} + +/** + * Generalized date time string to UTC. + * @param {string} datetime + * @returns UTC datetime string. + */ +function generalizedDateTimeToUTC(datetime) { + // Ensure the string is in the correct format + if (!/^\d{12,14}Z$/.test(datetime)) { + throw new OperationError(`failed to format datetime string ${datetime}`); + } + + // Extract components + let centuary = "20"; + if (datetime.length === 15) { + centuary = datetime.substring(0, 2); + datetime = datetime.slice(2); + } + const year = centuary + datetime.substring(0, 2); + const month = datetime.substring(2, 4); + const day = datetime.substring(4, 6); + const hour = datetime.substring(6, 8); + const minute = datetime.substring(8, 10); + const second = datetime.substring(10, 12); + + // Construct ISO 8601 format string + const isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`; + + // Parse using standard Date object + const isoDateTime = new Date(isoString); + + return isoDateTime.toUTCString(); +} + +/** + * Format CRL extensions. + * @param {r.ExtParam[] | undefined} extensions + * @param {Number} indent + * @returns Formatted string detailing CRL extensions. + */ +function formatCRLExtensions(extensions, indent) { + if (Array.isArray(extensions) === false || extensions.length === 0) { + return indentString(`No CRL extensions.`, indent); + } + + let out = ``; + + extensions.sort((a, b) => { + if (!Object.hasOwn(a, "extname") || !Object.hasOwn(b, "extname")) { + return 0; + } + if (a.extname < b.extname) { + return -1; + } else if (a.extname === b.extname) { + return 0; + } else { + return 1; + } + }); + + extensions.forEach((ext) => { + if (!Object.hasOwn(ext, "extname")) { + throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`); + } + switch (ext.extname) { + case "authorityKeyIdentifier": + out += `X509v3 Authority Key Identifier:\n`; + if (Object.hasOwn(ext, "kid")) { + out += `\tkeyid:${colonDelimitedHexFormatString(ext.kid.hex.toUpperCase())}\n`; + } + if (Object.hasOwn(ext, "issuer")) { + out += `\tDirName:${ext.issuer.str}\n`; + } + if (Object.hasOwn(ext, "sn")) { + out += `\tserial:${colonDelimitedHexFormatString(ext.sn.hex.toUpperCase())}\n`; + } + break; + case "cRLDistributionPoints": + out += `X509v3 CRL Distribution Points:\n`; + ext.array.forEach((distPoint) => { + const fullName = `Full Name:\n${formatGeneralNames(distPoint.dpname.full, 4)}`; + out += indentString(fullName, 4) + "\n"; + }); + break; + case "cRLNumber": + if (!Object.hasOwn(ext, "num")) { + throw new OperationError(`'cRLNumber' CRL entry extension missing 'num' key: ${ext}`); + } + out += `X509v3 CRL Number:\n\t${ext.num.hex.toUpperCase()}\n`; + break; + case "issuerAltName": + out += `X509v3 Issuer Alternative Name:\n${formatGeneralNames(ext.array, 4)}\n`; + break; + default: + out += `${ext.extname}:\n`; + out += `\tUnsupported CRL extension. Try openssl CLI.\n`; + break; + } + }); + + return indentString(chop(out), indent); +} + +/** + * Format general names array. + * @param {Object[]} names + * @returns Multi-line formatted string describing all supported general name types. + */ +function formatGeneralNames(names, indent) { + let out = ``; + + names.forEach((name) => { + const key = Object.keys(name)[0]; + + switch (key) { + case "ip": + out += `IP:${name.ip}\n`; + break; + case "dns": + out += `DNS:${name.dns}\n`; + break; + case "uri": + out += `URI:${name.uri}\n`; + break; + case "rfc822": + out += `EMAIL:${name.rfc822}\n`; + break; + case "dn": + out += `DIR:${name.dn.str}\n`; + break; + case "other": + out += `OtherName:${name.other.oid}::${Object.values(name.other.value)[0].str}\n`; + break; + default: + out += `${key}: unsupported general name type`; + break; + } + }); + + return indentString(chop(out), indent); +} + +/** + * Colon-delimited hex formatted output. + * @param {string} hexString Hex String + * @returns String representing input hex string with colon delimiter. + */ +function colonDelimitedHexFormatString(hexString) { + if (hexString.length % 2 !== 0) { + hexString = "0" + hexString; + } + + return chop(hexString.replace(/(..)/g, "$&:")); +} + +/** + * Format revoked certificates array + * @param {r.RevokedCertificate[] | null} revokedCertificates + * @param {Number} indent + * @returns Multi-line formatted string output of revoked certificates array + */ +function formatRevokedCertificates(revokedCertificates, indent) { + if (Array.isArray(revokedCertificates) === false || revokedCertificates.length === 0) { + return indentString("No Revoked Certificates.", indent); + } + + let out=``; + + revokedCertificates.forEach((revCert) => { + if (!Object.hasOwn(revCert, "sn") || !Object.hasOwn(revCert, "date")) { + throw new OperationError("invalid revoked certificate object, missing either serial number or date"); + } + + out += `Serial Number: ${revCert.sn.hex.toUpperCase()} + Revocation Date: ${generalizedDateTimeToUTC(revCert.date)}\n`; + if (Object.hasOwn(revCert, "ext") && Array.isArray(revCert.ext) && revCert.ext.length !== 0) { + out += `\tCRL entry extensions:\n${indentString(formatCRLEntryExtensions(revCert.ext), 2*indent)}\n`; + } + }); + + return indentString(chop(out), indent); +} + +/** + * Format CRL entry extensions. + * @param {Object[]} exts + * @returns Formatted multi-line string describing CRL entry extensions. + */ +function formatCRLEntryExtensions(exts) { + let out = ``; + + const crlReasonCodeToReasonMessage = { + 0: "Unspecified", + 1: "Key Compromise", + 2: "CA Compromise", + 3: "Affiliation Changed", + 4: "Superseded", + 5: "Cessation Of Operation", + 6: "Certificate Hold", + 8: "Remove From CRL", + 9: "Privilege Withdrawn", + 10: "AA Compromise", + }; + + const holdInstructionOIDToName = { + "1.2.840.10040.2.1": "Hold Instruction None", + "1.2.840.10040.2.2": "Hold Instruction Call Issuer", + "1.2.840.10040.2.3": "Hold Instruction Reject", + }; + + exts.forEach((ext) => { + if (!Object.hasOwn(ext, "extname")) { + throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`); + } + switch (ext.extname) { + case "cRLReason": + if (!Object.hasOwn(ext, "code")) { + throw new OperationError(`'cRLReason' CRL entry extension missing 'code' key: ${ext}`); + } + out += `X509v3 CRL Reason Code: + ${Object.hasOwn(crlReasonCodeToReasonMessage, ext.code) ? crlReasonCodeToReasonMessage[ext.code] : `invalid reason code: ${ext.code}`}\n`; + break; + case "2.5.29.23": // Hold instruction + out += `Hold Instruction Code:\n\t${Object.hasOwn(holdInstructionOIDToName, ext.extn.oid) ? holdInstructionOIDToName[ext.extn.oid] : `${ext.extn.oid}: unknown hold instruction OID`}\n`; + break; + case "2.5.29.24": // Invalidity Date + out += `Invalidity Date:\n\t${generalizedDateTimeToUTC(ext.extn.gentime.str)}\n`; + break; + default: + out += `${ext.extname}:\n`; + out += `\tUnsupported CRL entry extension. Try openssl CLI.\n`; + break; + } + }); + + return chop(out); +} + +/** + * Format CRL signature. + * @param {String} sigHex + * @param {Number} indent + * @returns String representing hex signature value formatted on multiple lines. + */ +function formatCRLSignature(sigHex, indent) { + if (sigHex.length % 2 !== 0) { + sigHex = "0" + sigHex; + } + + return indentString(formatMultiLine(chop(sigHex.replace(/(..)/g, "$&:"))), indent); +} + +/** + * Format string onto multiple lines. + * @param {string} longStr + * @returns String as a multi-line string. + */ +function formatMultiLine(longStr) { + const lines = []; + + for (let remain = longStr ; remain !== "" ; remain = remain.substring(54)) { + lines.push(remain.substring(0, 54)); + } + + return lines.join("\n"); +} + +/** + * Indent a multi-line string by n spaces. + * @param {string} input String + * @param {number} spaces How many leading spaces + * @returns Indented string. + */ +function indentString(input, spaces) { + const indent = " ".repeat(spaces); + return input.replace(/^/gm, indent); +} + +/** + * Remove last character from a string. + * @param {string} s String + * @returns Chopped string. + */ +function chop(s) { + if (s.length < 1) { + return s; + } + return s.substring(0, s.length - 1); +} + +export default ParseX509CRL; diff --git a/src/core/operations/ParseX509Certificate.mjs b/src/core/operations/ParseX509Certificate.mjs new file mode 100644 index 00000000..cdd1e9c7 --- /dev/null +++ b/src/core/operations/ParseX509Certificate.mjs @@ -0,0 +1,230 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import { fromBase64 } from "../lib/Base64.mjs"; +import { runHash } from "../lib/Hash.mjs"; +import { fromHex, toHex } from "../lib/Hex.mjs"; +import { formatByteStr, formatDnObj } from "../lib/PublicKey.mjs"; +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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.checks = [ + { + "pattern": "^-+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]; + + let undefinedInputFormat = false; + try { + switch (inputFormat) { + case "DER Hex": + input = input.replace(/\s/g, "").toLowerCase(); + 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.strToArrayBuffer(input), "")); + break; + default: + undefinedInputFormat = true; + } + } catch (e) { + throw "Certificate load error (non-certificate input?)"; + } + if (undefinedInputFormat) throw "Undefined input format"; + + const hex = Utils.strToArrayBuffer(Utils.byteArrayToChars(fromHex(cert.hex))), + sn = cert.getSerialNumberHex(), + issuer = cert.getIssuer(), + subject = cert.getSubject(), + 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 = formatDnObj(issuer, 2), + nbDate = formatDate(cert.getNotBefore()), + naDate = formatDate(cert.getNotAfter()), + subjectStr = formatDnObj(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} +Fingerprints + MD5: ${runHash("md5", hex)} + SHA1: ${runHash("sha1", hex)} + SHA256: ${runHash("sha256", hex)} +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..22b7d8a2 --- /dev/null +++ b/src/core/operations/PlayMedia.mjs @@ -0,0 +1,101 @@ +/** + * @author anthony-arnold [anthony.arnold@uqconnect.edu.au] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import { fromBase64, toBase64 } from "../lib/Base64.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { isType, detectFileType } from "../lib/FileType.mjs"; + +/** + * 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 + if (!isType(/^(audio|video)/, input)) { + 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 {byteArray} data Data containing an audio or video file. + * @returns {string} Markup to display a media player. + */ + async present(data) { + if (!data.length) return ""; + + const types = detectFileType(data); + const matches = /^audio|video/.exec(types[0].mime); + if (!matches) { + throw new OperationError("Invalid file type"); + } + const dataURI = `data:${types[0].mime};base64,${toBase64(data)}`; + const element = matches[0]; + + let html = `<${element} src='${dataURI}' type='${types[0].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..a05dd783 --- /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.mjs"; + +/** + * 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/ProtobufDecode.mjs b/src/core/operations/ProtobufDecode.mjs new file mode 100644 index 00000000..fbc16dc4 --- /dev/null +++ b/src/core/operations/ProtobufDecode.mjs @@ -0,0 +1,65 @@ +/** + * @author GCHQ Contributor [3] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Protobuf from "../lib/Protobuf.mjs"; + +/** + * Protobuf Decode operation + */ +class ProtobufDecode extends Operation { + + /** + * ProtobufDecode constructor + */ + constructor() { + super(); + + this.name = "Protobuf Decode"; + this.module = "Protobuf"; + this.description = "Decodes any Protobuf encoded data to a JSON representation of the data using the field number as the field key.

If a .proto schema is defined, the encoded data will be decoded with reference to the schema. Only one message instance will be decoded.

Show Unknown Fields
When a schema is used, this option shows fields that are present in the input data but not defined in the schema.

Show Types
Show the type of a field next to its name. For undefined fields, the wiretype and example types are shown instead."; + this.infoURL = "https://wikipedia.org/wiki/Protocol_Buffers"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.args = [ + { + name: "Schema (.proto text)", + type: "text", + value: "", + rows: 8, + hint: "Drag and drop is enabled on this ingredient" + }, + { + name: "Show Unknown Fields", + type: "boolean", + value: false + }, + { + name: "Show Types", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + input = new Uint8Array(input); + try { + return Protobuf.decode(input, args); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default ProtobufDecode; diff --git a/src/core/operations/ProtobufEncode.mjs b/src/core/operations/ProtobufEncode.mjs new file mode 100644 index 00000000..eaf4d6c4 --- /dev/null +++ b/src/core/operations/ProtobufEncode.mjs @@ -0,0 +1,54 @@ +/** + * @author GCHQ Contributor [3] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Protobuf from "../lib/Protobuf.mjs"; + +/** + * Protobuf Encode operation + */ +class ProtobufEncode extends Operation { + + /** + * ProtobufEncode constructor + */ + constructor() { + super(); + + this.name = "Protobuf Encode"; + this.module = "Protobuf"; + this.description = "Encodes a valid JSON object into a protobuf byte array using the input .proto schema."; + this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding"; + this.inputType = "JSON"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Schema (.proto text)", + type: "text", + value: "", + rows: 8, + hint: "Drag and drop is enabled on this ingredient" + } + ]; + } + + /** + * @param {Object} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + try { + return Protobuf.encode(input, args); + } catch (error) { + throw new OperationError(error); + } + } + +} + +export default ProtobufEncode; diff --git a/src/core/operations/PseudoRandomNumberGenerator.mjs b/src/core/operations/PseudoRandomNumberGenerator.mjs new file mode 100644 index 00000000..53150566 --- /dev/null +++ b/src/core/operations/PseudoRandomNumberGenerator.mjs @@ -0,0 +1,86 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; +import BigNumber from "bignumber.js"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * 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 (isWorkerEnvironment() && self.crypto) { + bytes = new ArrayBuffer(numBytes); + const CHUNK_SIZE = 65536; + for (let i = 0; i < numBytes; i += CHUNK_SIZE) { + self.crypto.getRandomValues(new Uint8Array(bytes, i, Math.min(numBytes - i, CHUNK_SIZE))); + } + bytes = Utils.arrayBufferToStr(bytes); + } 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/PubKeyFromCert.mjs b/src/core/operations/PubKeyFromCert.mjs new file mode 100644 index 00000000..0233b04a --- /dev/null +++ b/src/core/operations/PubKeyFromCert.mjs @@ -0,0 +1,68 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Public Key from Certificate operation + */ +class PubKeyFromCert extends Operation { + + /** + * PubKeyFromCert constructor + */ + constructor() { + super(); + + this.name = "Public Key from Certificate"; + this.module = "PublicKey"; + this.description = "Extracts the Public Key from a Certificate."; + this.infoURL = "https://en.wikipedia.org/wiki/X.509"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output = ""; + let match; + const regex = /-----BEGIN CERTIFICATE-----/g; + while ((match = regex.exec(input)) !== null) { + // find corresponding end tag + const indexBase64 = match.index + match[0].length; + const footer = "-----END CERTIFICATE-----"; + const indexFooter = input.indexOf(footer, indexBase64); + if (indexFooter === -1) { + throw new OperationError(`PEM footer '${footer}' not found`); + } + + const certPem = input.substring(match.index, indexFooter + footer.length); + const cert = new r.X509(); + cert.readCertPEM(certPem); + let pubKey; + try { + pubKey = cert.getPublicKey(); + } catch { + throw new OperationError("Unsupported public key type"); + } + const pubKeyPem = r.KEYUTIL.getPEM(pubKey); + + // PEM ends with '\n', so a new key always starts on a new line + output += pubKeyPem; + } + return output; + } +} + +export default PubKeyFromCert; diff --git a/src/core/operations/PubKeyFromPrivKey.mjs b/src/core/operations/PubKeyFromPrivKey.mjs new file mode 100644 index 00000000..5a08882b --- /dev/null +++ b/src/core/operations/PubKeyFromPrivKey.mjs @@ -0,0 +1,82 @@ +/** + * @author cplussharp + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Public Key from Private Key operation + */ +class PubKeyFromPrivKey extends Operation { + + /** + * PubKeyFromPrivKey constructor + */ + constructor() { + super(); + + this.name = "Public Key from Private Key"; + this.module = "PublicKey"; + this.description = "Extracts the Public Key from a Private Key."; + this.infoURL = "https://en.wikipedia.org/wiki/PKCS_8"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.checks = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output = ""; + let match; + const regex = /-----BEGIN ((RSA |EC |DSA )?PRIVATE KEY)-----/g; + while ((match = regex.exec(input)) !== null) { + // find corresponding end tag + const indexBase64 = match.index + match[0].length; + const footer = `-----END ${match[1]}-----`; + const indexFooter = input.indexOf(footer, indexBase64); + if (indexFooter === -1) { + throw new OperationError(`PEM footer '${footer}' not found`); + } + + const privKeyPem = input.substring(match.index, indexFooter + footer.length); + let privKey; + try { + privKey = r.KEYUTIL.getKey(privKeyPem); + } catch (err) { + throw new OperationError(`Unsupported key type: ${err}`); + } + let pubKey; + if (privKey.type && privKey.type === "EC") { + pubKey = new r.KJUR.crypto.ECDSA({ curve: privKey.curve }); + pubKey.setPublicKeyHex(privKey.generatePublicKeyHex()); + } else if (privKey.type && privKey.type === "DSA") { + if (!privKey.y) { + throw new OperationError(`DSA Private Key in PKCS#8 is not supported`); + } + pubKey = new r.KJUR.crypto.DSA(); + pubKey.setPublic(privKey.p, privKey.q, privKey.g, privKey.y); + } else if (privKey.n && privKey.e) { + pubKey = new r.RSAKey(); + pubKey.setPublic(privKey.n, privKey.e); + } else { + throw new OperationError(`Unsupported key type`); + } + const pubKeyPem = r.KEYUTIL.getPEM(pubKey); + + // PEM ends with '\n', so a new key always starts on a new line + output += pubKeyPem; + } + return output; + } +} + +export default PubKeyFromPrivKey; diff --git a/src/core/operations/PublicKey.js b/src/core/operations/PublicKey.js deleted file mode 100755 index 199c06f2..00000000 --- a/src/core/operations/PublicKey.js +++ /dev/null @@ -1,1056 +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) { - var 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"; - } - - var 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 - if (r.ASN1HEX.dump(certSig).indexOf("SEQUENCE") === 0) { // 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 - sigStr = " Signature: " + PublicKey._formatByteStr(certSig, 16, 18) + "\n"; - } - - // Format Public Key fields - for (var 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, - " " - ); - } - - var issuerStr = PublicKey._formatDnStr(issuer, 2), - nbDate = PublicKey._formatDate(notBefore), - naDate = PublicKey._formatDate(notAfter), - subjectStr = PublicKey._formatDnStr(subject, 2); - - var 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) { - var 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) { - var output = "", - fields = dnStr.split(",/|"), - maxKeyLen = 0, - key, - value, - str; - - for (var 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; - var output = ""; - - for (var i = 0; i < byteStr.length; i += length) { - var 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) { - var s = ""; - var a = r.ASN1HEX.getPosArrayOfChildren_AtObj(hDN, 0); - for (var i = 0; i < a.length; i++) { - var 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 b03758e3..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) { - var 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) { - var idn = args[0]; - - if (idn) { - return punycode.toUnicode(input); - } else { - return punycode.decode(input); - } - }, - -}; - -export default Punycode; diff --git a/src/core/operations/RAKE.mjs b/src/core/operations/RAKE.mjs new file mode 100644 index 00000000..1470f5f0 --- /dev/null +++ b/src/core/operations/RAKE.mjs @@ -0,0 +1,144 @@ +/** + * @author sw5678 + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * RAKE operation + */ +class RAKE extends Operation { + + /** + * RAKE constructor + */ + constructor() { + super(); + + this.name = "RAKE"; + this.module = "Default"; + this.description = [ + "Rapid Keyword Extraction (RAKE)", + "

", + "RAKE is a domain-independent keyword extraction algorithm in Natural Language Processing.", + "

", + "The list of stop words are from the NLTK python package", + ].join("\n"); + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Word Delimiter (Regex)", + type: "text", + value: "\\s" + }, + { + name: "Sentence Delimiter (Regex)", + type: "text", + value: "\\.\\s|\\n" + }, + { + name: "Stop Words", + type: "text", + value: "i,me,my,myself,we,our,ours,ourselves,you,you're,you've,you'll,you'd,your,yours,yourself,yourselves,he,him,his,himself,she,she's,her,hers,herself,it,it's,its,itsef,they,them,their,theirs,themselves,what,which,who,whom,this,that,that'll,these,those,am,is,are,was,were,be,been,being,have,has,had,having,do,does',did,doing,a,an,the,and,but,if,or,because,as,until,while,of,at,by,for,with,about,against,between,into,through,during,before,after,above,below,to,from,up,down,in,out,on,off,over,under,again,further,then,once,here,there,when,where,why,how,all,any,both,each,few,more,most,other,some,such,no,nor,not,only,own,same,so,than,too,very,s,t,can,will,just,don,don't,should,should've,now,d,ll,m,o,re,ve,y,ain,aren,aren't,couldn,couldn't,didn,didn't,doesn,doesn't,hadn,hadn't,hasn,hasn't,haven,haven't,isn,isn't,ma,mightn,mightn't,mustn,mustn't,needn,needn't,shan,shan't,shouldn,shouldn't,wasn,wasn't,weren,weren't,won,won't,wouldn,wouldn't" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + // Get delimiter regexs + const wordDelim = new RegExp(args[0], "g"); + const sentDelim = new RegExp(args[1], "g"); + + // Deduplicate the stop words and add the empty string + const stopWords = args[2].toLowerCase().replace(/ /g, "").split(",").unique(); + stopWords.push(""); + + // Lower case input and remove start and ending whitespace + input = input.toLowerCase().trim(); + + // Get tokens, token count, and phrases + const tokens = []; + const wordFrequencies = []; + let phrases = []; + + // Build up list of phrases and token counts + const sentences = input.split(sentDelim); + for (const sent of sentences) { + + // Split sentence into words + const splitSent = sent.split(wordDelim); + let startIndex = 0; + + for (let i = 0; i < splitSent.length; i++) { + const token = splitSent[i]; + if (stopWords.includes(token)) { + // If token is stop word then split to create phrase + phrases.push(splitSent.slice(startIndex, i)); + startIndex = i + 1; + } else { + // If token is not a stop word add to the count of the list of words + if (tokens.includes(token)) { + wordFrequencies[tokens.indexOf(token)]+=1; + } else { + tokens.push(token); + wordFrequencies.push(1); + } + } + } + phrases.push(splitSent.slice(startIndex)); + } + + // remove empty phrases + phrases = phrases.filter(subArray => subArray.length > 0); + + // Remove duplicate phrases + phrases = phrases.unique(); + + // Generate word_degree_matrix and populate + const wordDegreeMatrix = Array(tokens.length).fill().map(() => Array(tokens.length).fill(0)); + for (const phrase of phrases) { + for (const word1 of phrase) { + for (const word2 of phrase) { + wordDegreeMatrix[tokens.indexOf(word1)][tokens.indexOf(word2)]++; + } + } + } + + // Calculate degree score for each token + const degreeScores = Array(tokens.length).fill(0); + for (let i=0; i b[0] - a[0]); + scores.unshift(new Array("Scores: ", "Keywords: ")); + + // Output works with the 'To Table' functionality already built into CC + return scores.map(function (score) { + return score.join(", "); + }).join("\n"); + } +} + +export default RAKE; diff --git a/src/core/operations/RC2Decrypt.mjs b/src/core/operations/RC2Decrypt.mjs new file mode 100644 index 00000000..c9ff1bf4 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; + +/** + * 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..88dae5b1 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; + + +/** + * 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..183db742 --- /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.mjs"; +import CryptoJS from "crypto-js"; +import { format } from "../lib/Ciphers.mjs"; + +/** + * 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..c6bb536a --- /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.mjs"; +import { format } from "../lib/Ciphers.mjs"; +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 dwords to drop", + "type": "number", + "value": 192 + } + ]; + } + + /** + * @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..f326b694 --- /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.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * 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..beec94a4 --- /dev/null +++ b/src/core/operations/ROT13.mjs @@ -0,0 +1,114 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + + +/** + * 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: "Rotate numbers", + type: "boolean", + value: false + }, + { + 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], + rotNumbers = args[2]; + let amount = args[3], + amountNumbers = args[3]; + + if (amount) { + if (amount < 0) { + amount = 26 - (Math.abs(amount) % 26); + amountNumbers = 10 - (Math.abs(amountNumbers) % 10); + } + + for (let i = 0; i < input.length; i++) { + let 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; + } else if (rotNumbers && chr >= 48 && chr <= 57) { // Numbers + chr = (chr - 48 + amountNumbers) % 10; + output[i] = chr + 48; + } + } + } + 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/ROT13BruteForce.mjs b/src/core/operations/ROT13BruteForce.mjs new file mode 100644 index 00000000..7468ee11 --- /dev/null +++ b/src/core/operations/ROT13BruteForce.mjs @@ -0,0 +1,102 @@ +/** + * @author MikeCAT + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * ROT13 Brute Force operation. + */ +class ROT13BruteForce extends Operation { + + /** + * ROT13BruteForce constructor + */ + constructor() { + super(); + + this.name = "ROT13 Brute Force"; + this.module = "Default"; + this.description = "Try all meaningful amounts for ROT13.

Optionally you can enter your known plaintext (crib) to filter the result."; + this.infoURL = "https://wikipedia.org/wiki/ROT13"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + name: "Rotate lower case chars", + type: "boolean", + value: true + }, + { + name: "Rotate upper case chars", + type: "boolean", + value: true + }, + { + name: "Rotate numbers", + type: "boolean", + value: false + }, + { + name: "Sample length", + type: "number", + value: 100 + }, + { + name: "Sample offset", + type: "number", + value: 0 + }, + { + name: "Print amount", + type: "boolean", + value: true + }, + { + name: "Crib (known plaintext string)", + type: "string", + value: "" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [rotateLower, rotateUpper, rotateNum, sampleLength, sampleOffset, printAmount, crib] = args; + const sample = input.slice(sampleOffset, sampleOffset + sampleLength); + const cribLower = crib.toLowerCase(); + const lowerStart = "a".charCodeAt(0), upperStart = "A".charCodeAt(0), numStart = "0".charCodeAt(0); + const result = []; + for (let amount = 1; amount < 26; amount++) { + const rotated = sample.slice(); + for (let i = 0; i < rotated.length; i++) { + if (rotateLower && lowerStart <= rotated[i] && rotated[i] < lowerStart + 26) { + rotated[i] = (rotated[i] - lowerStart + amount) % 26 + lowerStart; + } else if (rotateUpper && upperStart <= rotated[i] && rotated[i] < upperStart + 26) { + rotated[i] = (rotated[i] - upperStart + amount) % 26 + upperStart; + } else if (rotateNum && numStart <= rotated[i] && rotated[i] < numStart + 10) { + rotated[i] = (rotated[i] - numStart + amount) % 10 + numStart; + } + } + const rotatedString = Utils.byteArrayToUtf8(rotated); + if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { + const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString); + if (printAmount) { + const amountStr = "Amount = " + (" " + amount).slice(-2) + ": "; + result.push(amountStr + rotatedStringEscaped); + } else { + result.push(rotatedStringEscaped); + } + } + } + return result.join("\n"); + } +} + +export default ROT13BruteForce; diff --git a/src/core/operations/ROT47.mjs b/src/core/operations/ROT47.mjs new file mode 100644 index 00000000..39bf79a2 --- /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.mjs"; + + +/** + * 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/ROT47BruteForce.mjs b/src/core/operations/ROT47BruteForce.mjs new file mode 100644 index 00000000..fa1e90dc --- /dev/null +++ b/src/core/operations/ROT47BruteForce.mjs @@ -0,0 +1,82 @@ +/** + * @author MikeCAT + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * ROT47 Brute Force operation. + */ +class ROT47BruteForce extends Operation { + + /** + * ROT47BruteForce constructor + */ + constructor() { + super(); + + this.name = "ROT47 Brute Force"; + this.module = "Default"; + this.description = "Try all meaningful amounts for ROT47.

Optionally you can enter your known plaintext (crib) to filter the result."; + this.infoURL = "https://wikipedia.org/wiki/ROT13#Variants"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + name: "Sample length", + type: "number", + value: 100 + }, + { + name: "Sample offset", + type: "number", + value: 0 + }, + { + name: "Print amount", + type: "boolean", + value: true + }, + { + name: "Crib (known plaintext string)", + type: "string", + value: "" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [sampleLength, sampleOffset, printAmount, crib] = args; + const sample = input.slice(sampleOffset, sampleOffset + sampleLength); + const cribLower = crib.toLowerCase(); + const result = []; + for (let amount = 1; amount < 94; amount++) { + const rotated = sample.slice(); + for (let i = 0; i < rotated.length; i++) { + if (33 <= rotated[i] && rotated[i] <= 126) { + rotated[i] = (rotated[i] - 33 + amount) % 94 + 33; + } + } + const rotatedString = Utils.byteArrayToUtf8(rotated); + if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { + const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString); + if (printAmount) { + const amountStr = "Amount = " + (" " + amount).slice(-2) + ": "; + result.push(amountStr + rotatedStringEscaped); + } else { + result.push(rotatedStringEscaped); + } + } + } + return result.join("\n"); + } +} + +export default ROT47BruteForce; diff --git a/src/core/operations/ROT8000.mjs b/src/core/operations/ROT8000.mjs new file mode 100644 index 00000000..322ceaa3 --- /dev/null +++ b/src/core/operations/ROT8000.mjs @@ -0,0 +1,123 @@ +/** + * @author Daniel Temkin [http://danieltemkin.com] + * @author Thomas Leplus [https://www.leplus.org] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * ROT8000 operation. + */ +class ROT8000 extends Operation { + + /** + * ROT8000 constructor + */ + constructor() { + super(); + this.name = "ROT8000"; + this.module = "Default"; + this.description = "The simple Caesar-cypher encryption that replaces each Unicode character with the one 0x8000 places forward or back along the alphabet."; + this.infoURL = "https://rot8000.com/info"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + // Inspired from https://github.com/rottytooth/rot8000/blob/main/rot8000.js + // these come from the valid-code-point-transitions.json file generated from the c# proj + // this is done bc: 1) don't trust JS's understanging of surrogate pairs and 2) consistency with original rot8000 + const validCodePoints = { + "33": true, + "127": false, + "161": true, + "5760": false, + "5761": true, + "8192": false, + "8203": true, + "8232": false, + "8234": true, + "8239": false, + "8240": true, + "8287": false, + "8288": true, + "12288": false, + "12289": true, + "55296": false, + "57344": true + }; + const bmpSize = 0x10000; + const rotList = {}; // the mapping of char to rotated char + const hiddenBlocks = []; + let startBlock = 0; + for (const key in validCodePoints) { + if (Object.prototype.hasOwnProperty.call(validCodePoints, key)) { + if (validCodePoints[key] === true) + hiddenBlocks.push({ start: startBlock, end: parseInt(key, 10) - 1 }); + else + startBlock = parseInt(key, 10); + } + } + const validIntList = []; // list of all valid chars + let currValid = false; + for (let i = 0; i < bmpSize; i++) { + if (validCodePoints[i] !== undefined) { + currValid = validCodePoints[i]; + } + if (currValid) validIntList.push(i); + } + const rotateNum = Object.keys(validIntList).length / 2; + // go through every valid char and find its match + for (let i = 0; i < validIntList.length; i++) { + rotList[String.fromCharCode(validIntList[i])] = + String.fromCharCode(validIntList[(i + rotateNum) % (rotateNum * 2)]); + } + let output = ""; + for (let count = 0; count < input.length; count++) { + // if it is not in the mappings list, just add it directly (no rotation) + if (rotList[input[count]] === undefined) { + output += input[count]; + continue; + } + // otherwise, rotate it and add it to the string + output += rotList[input[count]]; + } + return output; + } + + /** + * Highlight ROT8000 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight ROT8000 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 ROT8000; diff --git a/src/core/operations/RSADecrypt.mjs b/src/core/operations/RSADecrypt.mjs new file mode 100644 index 00000000..5b32b790 --- /dev/null +++ b/src/core/operations/RSADecrypt.mjs @@ -0,0 +1,86 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; + +/** + * RSA Decrypt operation + */ +class RSADecrypt extends Operation { + + /** + * RSADecrypt constructor + */ + constructor() { + super(); + + this.name = "RSA Decrypt"; + this.module = "Ciphers"; + this.description = "Decrypt an RSA encrypted message with a PEM encoded private key."; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA Private Key (PEM)", + type: "text", + value: "-----BEGIN RSA PRIVATE KEY-----" + }, + { + name: "Key Password", + type: "text", + value: "" + }, + { + name: "Encryption Scheme", + type: "argSelector", + value: [ + { + name: "RSA-OAEP", + on: [3] + }, + { + name: "RSAES-PKCS1-V1_5", + off: [3] + }, + { + name: "RAW", + off: [3] + }] + }, + { + name: "Message Digest Algorithm", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pemKey, password, scheme, md] = args; + if (pemKey.replace("-----BEGIN RSA PRIVATE KEY-----", "").length === 0) { + throw new OperationError("Please enter a private key."); + } + try { + const privKey = forge.pki.decryptRsaPrivateKey(pemKey, password); + const dMsg = privKey.decrypt(input, scheme, {md: MD_ALGORITHMS[md].create()}); + return forge.util.decodeUtf8(dMsg); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default RSADecrypt; diff --git a/src/core/operations/RSAEncrypt.mjs b/src/core/operations/RSAEncrypt.mjs new file mode 100644 index 00000000..859ce132 --- /dev/null +++ b/src/core/operations/RSAEncrypt.mjs @@ -0,0 +1,89 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; + +/** + * RSA Encrypt operation + */ +class RSAEncrypt extends Operation { + + /** + * RSAEncrypt constructor + */ + constructor() { + super(); + + this.name = "RSA Encrypt"; + this.module = "Ciphers"; + this.description = "Encrypt a message with a PEM encoded RSA public key."; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA Public Key (PEM)", + type: "text", + value: "-----BEGIN RSA PUBLIC KEY-----" + }, + { + name: "Encryption Scheme", + type: "argSelector", + value: [ + { + name: "RSA-OAEP", + on: [2] + }, + { + name: "RSAES-PKCS1-V1_5", + off: [2] + }, + { + name: "RAW", + off: [2] + }] + }, + { + name: "Message Digest Algorithm", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pemKey, scheme, md] = args; + + if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) { + throw new OperationError("Please enter a public key."); + } + try { + // Load public key + const pubKey = forge.pki.publicKeyFromPem(pemKey); + // https://github.com/digitalbazaar/forge/issues/465#issuecomment-271097600 + const plaintextBytes = forge.util.encodeUtf8(input); + // Encrypt message + const eMsg = pubKey.encrypt(plaintextBytes, scheme, {md: MD_ALGORITHMS[md].create()}); + return eMsg; + } catch (err) { + if (err.message === "RSAES-OAEP input message length is too long.") { + throw new OperationError(`RSAES-OAEP input message length (${err.length}) is longer than the maximum allowed length (${err.maxLength}).`); + } + throw new OperationError(err); + } + } + +} + +export default RSAEncrypt; diff --git a/src/core/operations/RSASign.mjs b/src/core/operations/RSASign.mjs new file mode 100644 index 00000000..5091549f --- /dev/null +++ b/src/core/operations/RSASign.mjs @@ -0,0 +1,74 @@ +/** + * @author Matt C [me@mitt.dev] + * @author gchq77703 [] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; + +/** + * RSA Sign operation + */ +class RSASign extends Operation { + + /** + * RSASign constructor + */ + constructor() { + super(); + + this.name = "RSA Sign"; + this.module = "Ciphers"; + this.description = "Sign a plaintext message with a PEM encoded RSA key."; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA Private Key (PEM)", + type: "text", + value: "-----BEGIN RSA PRIVATE KEY-----" + }, + { + name: "Key Password", + type: "text", + value: "" + }, + { + name: "Message Digest Algorithm", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key, password, mdAlgo] = args; + if (key.replace("-----BEGIN RSA PRIVATE KEY-----", "").length === 0) { + throw new OperationError("Please enter a private key."); + } + try { + const privateKey = forge.pki.decryptRsaPrivateKey(key, password); + // Generate message hash + const md = MD_ALGORITHMS[mdAlgo].create(); + md.update(input, "raw"); + // Sign message hash + const sig = privateKey.sign(md); + return sig; + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default RSASign; diff --git a/src/core/operations/RSAVerify.mjs b/src/core/operations/RSAVerify.mjs new file mode 100644 index 00000000..8160438c --- /dev/null +++ b/src/core/operations/RSAVerify.mjs @@ -0,0 +1,84 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; +import Utils from "../Utils.mjs"; + +/** + * RSA Verify operation + */ +class RSAVerify extends Operation { + + /** + * RSAVerify constructor + */ + constructor() { + super(); + + this.name = "RSA Verify"; + this.module = "Ciphers"; + this.description = "Verify a message against a signature and a public PEM encoded RSA key."; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA Public Key (PEM)", + type: "text", + value: "-----BEGIN RSA PUBLIC KEY-----" + }, + { + name: "Message", + type: "text", + value: "" + }, + { + name: "Message format", + type: "option", + value: ["Raw", "Hex", "Base64"] + }, + { + name: "Message Digest Algorithm", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pemKey, message, format, mdAlgo] = args; + if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) { + throw new OperationError("Please enter a public key."); + } + try { + // Load public key + const pubKey = forge.pki.publicKeyFromPem(pemKey); + // Generate message digest + const md = MD_ALGORITHMS[mdAlgo].create(); + const messageStr = Utils.convertToByteString(message, format); + md.update(messageStr, "raw"); + // Compare signed message digest and generated message digest + const result = pubKey.verify(md.digest().bytes(), input); + return result ? "Verified OK" : "Verification Failure"; + } catch (err) { + if (err.message === "Encrypted message length is invalid.") { + throw new OperationError(`Signature length (${err.length}) does not match expected length based on key (${err.expected}).`); + } + throw new OperationError(err); + } + } + +} + +export default RSAVerify; diff --git a/src/core/operations/Rabbit.mjs b/src/core/operations/Rabbit.mjs new file mode 100644 index 00000000..91ff24a3 --- /dev/null +++ b/src/core/operations/Rabbit.mjs @@ -0,0 +1,247 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Rabbit operation + */ +class Rabbit extends Operation { + + /** + * Rabbit constructor + */ + constructor() { + super(); + + this.name = "Rabbit"; + this.module = "Ciphers"; + this.description = "Rabbit is a high-speed stream cipher introduced in 2003 and defined in RFC 4503.

The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).

big-endian: based on RFC4503 and RFC3447
little-endian: compatible with Crypto++"; + this.infoURL = "https://wikipedia.org/wiki/Rabbit_(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": "Endianness", + "type": "option", + "value": ["Big", "Little"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + endianness = args[2], + inputType = args[3], + outputType = args[4]; + + const littleEndian = endianness === "Little"; + + if (key.length !== 16) { + throw new OperationError(`Invalid key length: ${key.length} bytes (expected: 16)`); + } + if (iv.length !== 0 && iv.length !== 8) { + throw new OperationError(`Invalid IV length: ${iv.length} bytes (expected: 0 or 8)`); + } + + // Inner State + const X = new Uint32Array(8), C = new Uint32Array(8); + let b = 0; + + // Counter System + const A = [ + 0x4d34d34d, 0xd34d34d3, 0x34d34d34, 0x4d34d34d, + 0xd34d34d3, 0x34d34d34, 0x4d34d34d, 0xd34d34d3 + ]; + const counterUpdate = function() { + for (let j = 0; j < 8; j++) { + const temp = C[j] + A[j] + b; + b = (temp / ((1 << 30) * 4)) >>> 0; + C[j] = temp; + } + }; + + // Next-State Function + const g = function(u, v) { + const uv = (u + v) >>> 0; + const upper = uv >>> 16, lower = uv & 0xffff; + const upperUpper = upper * upper; + const upperLower2 = 2 * upper * lower; + const lowerLower = lower * lower; + const mswTemp = upperUpper + ((upperLower2 / (1 << 16)) >>> 0); + const lswTemp = lowerLower + (upperLower2 & 0xffff) * (1 << 16); + const msw = mswTemp + ((lswTemp / ((1 << 30) * 4)) >>> 0); + const lsw = lswTemp >>> 0; + return lsw ^ msw; + }; + const leftRotate = function(value, width) { + return (value << width) | (value >>> (32 - width)); + }; + const nextStateHelper1 = function(v0, v1, v2) { + return v0 + leftRotate(v1, 16) + leftRotate(v2, 16); + }; + const nextStateHelper2 = function(v0, v1, v2) { + return v0 + leftRotate(v1, 8) + v2; + }; + const G = new Uint32Array(8); + const nextState = function() { + for (let j = 0; j < 8; j++) { + G[j] = g(X[j], C[j]); + } + X[0] = nextStateHelper1(G[0], G[7], G[6]); + X[1] = nextStateHelper2(G[1], G[0], G[7]); + X[2] = nextStateHelper1(G[2], G[1], G[0]); + X[3] = nextStateHelper2(G[3], G[2], G[1]); + X[4] = nextStateHelper1(G[4], G[3], G[2]); + X[5] = nextStateHelper2(G[5], G[4], G[3]); + X[6] = nextStateHelper1(G[6], G[5], G[4]); + X[7] = nextStateHelper2(G[7], G[6], G[5]); + }; + + // Key Setup Scheme + const K = new Uint16Array(8); + if (littleEndian) { + for (let i = 0; i < 8; i++) { + K[i] = (key[1 + 2 * i] << 8) | key[2 * i]; + } + } else { + for (let i = 0; i < 8; i++) { + K[i] = (key[14 - 2 * i] << 8) | key[15 - 2 * i]; + } + } + for (let j = 0; j < 8; j++) { + if (j % 2 === 0) { + X[j] = (K[(j + 1) % 8] << 16) | K[j]; + C[j] = (K[(j + 4) % 8] << 16) | K[(j + 5) % 8]; + } else { + X[j] = (K[(j + 5) % 8] << 16) | K[(j + 4) % 8]; + C[j] = (K[j] << 16) | K[(j + 1) % 8]; + } + } + for (let i = 0; i < 4; i++) { + counterUpdate(); + nextState(); + } + for (let j = 0; j < 8; j++) { + C[j] = C[j] ^ X[(j + 4) % 8]; + } + + // IV Setup Scheme + if (iv.length === 8) { + const getIVValue = function(a, b, c, d) { + if (littleEndian) { + return (iv[a] << 24) | (iv[b] << 16) | + (iv[c] << 8) | iv[d]; + } else { + return (iv[7 - a] << 24) | (iv[7 - b] << 16) | + (iv[7 - c] << 8) | iv[7 - d]; + } + }; + C[0] = C[0] ^ getIVValue(3, 2, 1, 0); + C[1] = C[1] ^ getIVValue(7, 6, 3, 2); + C[2] = C[2] ^ getIVValue(7, 6, 5, 4); + C[3] = C[3] ^ getIVValue(5, 4, 1, 0); + C[4] = C[4] ^ getIVValue(3, 2, 1, 0); + C[5] = C[5] ^ getIVValue(7, 6, 3, 2); + C[6] = C[6] ^ getIVValue(7, 6, 5, 4); + C[7] = C[7] ^ getIVValue(5, 4, 1, 0); + for (let i = 0; i < 4; i++) { + counterUpdate(); + nextState(); + } + } + + // Extraction Scheme + const S = new Uint8Array(16); + const extract = function() { + let pos = 0; + const addPart = function(value) { + S[pos++] = value >>> 8; + S[pos++] = value & 0xff; + }; + counterUpdate(); + nextState(); + addPart((X[6] >>> 16) ^ (X[1] & 0xffff)); + addPart((X[6] & 0xffff) ^ (X[3] >>> 16)); + addPart((X[4] >>> 16) ^ (X[7] & 0xffff)); + addPart((X[4] & 0xffff) ^ (X[1] >>> 16)); + addPart((X[2] >>> 16) ^ (X[5] & 0xffff)); + addPart((X[2] & 0xffff) ^ (X[7] >>> 16)); + addPart((X[0] >>> 16) ^ (X[3] & 0xffff)); + addPart((X[0] & 0xffff) ^ (X[5] >>> 16)); + if (littleEndian) { + for (let i = 0, j = S.length - 1; i < j;) { + const temp = S[i]; + S[i] = S[j]; + S[j] = temp; + i++; + j--; + } + } + }; + + const data = Utils.convertToByteString(input, inputType); + const result = new Uint8Array(data.length); + for (let i = 0; i <= data.length - 16; i += 16) { + extract(); + for (let j = 0; j < 16; j++) { + result[i + j] = data.charCodeAt(i + j) ^ S[j]; + } + } + if (data.length % 16 !== 0) { + const offset = data.length - data.length % 16; + const length = data.length - offset; + extract(); + if (littleEndian) { + for (let j = 0; j < length; j++) { + result[offset + j] = data.charCodeAt(offset + j) ^ S[j]; + } + } else { + for (let j = 0; j < length; j++) { + result[offset + j] = data.charCodeAt(offset + j) ^ S[16 - length + j]; + } + } + } + if (outputType === "Hex") { + return toHexFast(result); + } + return Utils.byteArrayToChars(result); + } + +} + +export default Rabbit; diff --git a/src/core/operations/RailFenceCipherDecode.mjs b/src/core/operations/RailFenceCipherDecode.mjs new file mode 100644 index 00000000..be54ee12 --- /dev/null +++ b/src/core/operations/RailFenceCipherDecode.mjs @@ -0,0 +1,81 @@ +/** + * @author Flavio Diez [flaviofdiez+cyberchef@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Rail Fence Cipher Decode operation + */ +class RailFenceCipherDecode extends Operation { + + /** + * RailFenceCipherDecode constructor + */ + constructor() { + super(); + + this.name = "Rail Fence Cipher Decode"; + this.module = "Ciphers"; + this.description = "Decodes Strings that were created using the Rail fence Cipher provided a key and an offset"; + this.infoURL = "https://wikipedia.org/wiki/Rail_fence_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "number", + value: 2 + }, + { + name: "Offset", + type: "number", + value: 0 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key, offset] = args; + + const cipher = input; + + if (key < 2) { + throw new OperationError("Key has to be bigger than 2"); + } else if (key > cipher.length) { + throw new OperationError("Key should be smaller than the cipher's length"); + } + + if (offset < 0) { + throw new OperationError("Offset has to be a positive integer"); + } + + const cycle = (key - 1) * 2; + const plaintext = new Array(cipher.length); + + let j = 0; + let x, y; + + for (y = 0; y < key; y++) { + for (x = 0; x < cipher.length; x++) { + if ((y + x + offset) % cycle === 0 || (y - x - offset) % cycle === 0) { + plaintext[x] = cipher[j++]; + } + } + } + + return plaintext.join("").trim(); + } + +} + + +export default RailFenceCipherDecode; diff --git a/src/core/operations/RailFenceCipherEncode.mjs b/src/core/operations/RailFenceCipherEncode.mjs new file mode 100644 index 00000000..03651f85 --- /dev/null +++ b/src/core/operations/RailFenceCipherEncode.mjs @@ -0,0 +1,74 @@ +/** + * @author Flavio Diez [flaviofdiez+cyberchef@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Rail Fence Cipher Encode operation + */ +class RailFenceCipherEncode extends Operation { + + /** + * RailFenceCipherEncode constructor + */ + constructor() { + super(); + + this.name = "Rail Fence Cipher Encode"; + this.module = "Ciphers"; + this.description = "Encodes Strings using the Rail fence Cipher provided a key and an offset"; + this.infoURL = "https://wikipedia.org/wiki/Rail_fence_cipher"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "number", + value: 2 + }, + { + name: "Offset", + type: "number", + value: 0 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key, offset] = args; + + const plaintext = input; + if (key < 2) { + throw new OperationError("Key has to be bigger than 2"); + } else if (key > plaintext.length) { + throw new OperationError("Key should be smaller than the plain text's length"); + } + + if (offset < 0) { + throw new OperationError("Offset has to be a positive integer"); + } + + const cycle = (key - 1) * 2; + const rows = new Array(key).fill(""); + + for (let pos = 0; pos < plaintext.length; pos++) { + const rowIdx = key - 1 - Math.abs(cycle / 2 - (pos + offset) % cycle); + + rows[rowIdx] += plaintext[pos]; + } + + return rows.join("").trim(); + } + +} + +export default RailFenceCipherEncode; diff --git a/src/core/operations/RandomizeColourPalette.mjs b/src/core/operations/RandomizeColourPalette.mjs new file mode 100644 index 00000000..fa8fa59e --- /dev/null +++ b/src/core/operations/RandomizeColourPalette.mjs @@ -0,0 +1,83 @@ +/** + * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { runHash } from "../lib/Hash.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Randomize Colour Palette operation + */ +class RandomizeColourPalette extends Operation { + + /** + * RandomizeColourPalette constructor + */ + constructor() { + super(); + + this.name = "Randomize Colour Palette"; + this.module = "Image"; + this.description = "Randomizes each colour in an image's colour palette. This can often reveal text or symbols that were previously a very similar colour to their surroundings, a technique sometimes used in Steganography."; + this.infoURL = "https://wikipedia.org/wiki/Indexed_color"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Seed", + type: "string", + value: "" + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + if (!isImage(input)) throw new OperationError("Please enter a valid image file."); + + const seed = args[0] || (Math.random().toString().substr(2)), + parsedImage = await Jimp.read(input), + width = parsedImage.bitmap.width, + height = parsedImage.bitmap.height; + + let rgbString, rgbHash, rgbHex; + + parsedImage.scan(0, 0, width, height, function(x, y, idx) { + rgbString = this.bitmap.data.slice(idx, idx+3).join("."); + rgbHash = runHash("md5", Utils.strToArrayBuffer(seed + rgbString)); + rgbHex = rgbHash.substr(0, 6) + "ff"; + parsedImage.setPixelColor(parseInt(rgbHex, 16), x, y); + }); + + const imageBuffer = await parsedImage.getBufferAsync(Jimp.AUTO); + + return new Uint8Array(imageBuffer).buffer; + } + + /** + * Displays the extracted data as an image for web apps. + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const type = isImage(data); + + return ``; + } + +} + +export default RandomizeColourPalette; diff --git a/src/core/operations/RawDeflate.mjs b/src/core/operations/RawDeflate.mjs new file mode 100644 index 00000000..0df243f4 --- /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.mjs"; +import {COMPRESSION_TYPE} from "../lib/Zlib.mjs"; +import rawdeflate from "zlibjs/bin/rawdeflate.min.js"; + +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..f5b57bde --- /dev/null +++ b/src/core/operations/RawInflate.mjs @@ -0,0 +1,90 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {INFLATE_BUFFER_TYPE} from "../lib/Zlib.mjs"; +import rawinflate from "zlibjs/bin/rawinflate.min.js"; + +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 + } + ]; + this.checks = [ + { + entropyRange: [7.5, 8], + args: [0, 0, INFLATE_BUFFER_TYPE, false, 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()); + + 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..2f696edd --- /dev/null +++ b/src/core/operations/Register.mjs @@ -0,0 +1,120 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Dish from "../Dish.mjs"; +import XRegExp from "xregexp"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * 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 (isWorkerEnvironment()) { + 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" && Object.prototype.hasOwnProperty.call(arg, "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..9ea17e83 --- /dev/null +++ b/src/core/operations/RegularExpression.mjs @@ -0,0 +1,272 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import XRegExp from "xregexp"; +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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: "(?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9](?:[\\u00A0-\\uD7FF\\uE000-\\uFFFF-a-z0-9-]*[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9])?\\.)+[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9](?:[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9-]*[\\u00A0-\\uD7FF\\uE000-\\uFFFFa-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}\\])" + }, + { + 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: "UUID", + value: "[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}" + }, + { + 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: + throw new OperationError("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; + const captureGroups = []; + + 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; + + // Store highlighted match and replace with a placeholder + captureGroups.push(`${Utils.escapeHtml(match)}`); + return `[cc_capture_group_${total++}]`; + }); + + // Safely escape all remaining text, then replace placeholders + output = Utils.escapeHtml(output); + output = output.replace(/\[cc_capture_group_(\d+)\]/g, (_, i) => { + return captureGroups[i]; + }); + + 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..859d86d7 --- /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.mjs"; + +/** + * 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. Accented characters are made up of Unicode combining characters, so unicode text formatting such as strikethroughs and underlines will also be removed."; + 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..fff4f6b5 --- /dev/null +++ b/src/core/operations/RemoveEXIF.mjs @@ -0,0 +1,56 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import { removeEXIF } from "../vendor/remove-exif.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + input = new Uint8Array(input); + // Do nothing if input is empty + if (input.length === 0) return input; + + 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..0fbf7115 --- /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.mjs"; + +/** + * 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..b633e82d --- /dev/null +++ b/src/core/operations/RemoveNullBytes.mjs @@ -0,0 +1,44 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + input = new Uint8Array(input); + const output = []; + for (let i = 0; i < input.length; i++) { + if (input[i] !== 0) output.push(input[i]); + } + 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..0689f766 --- /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.mjs"; + +/** + * 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, + removeCarriageReturns, + removeLineFeeds, + removeTabs, + removeFormFeeds, + removeFullStops + ] = args; + let data = input; + + if (removeSpaces) data = data.replace(/ /g, ""); + if (removeCarriageReturns) 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..5dee6d3c --- /dev/null +++ b/src/core/operations/RenderImage.mjs @@ -0,0 +1,112 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import { fromBase64, toBase64 } from "../lib/Base64.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {isImage} from "../lib/FileType.mjs"; + +/** + * 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.checks = [ + { + pattern: "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)", + flags: "", + args: ["Raw"], + useful: true, + output: { + mime: "image" + } + } + ]; + } + + /** + * @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 + if (!isImage(input)) { + 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 mime = isImage(data); + if (mime) { + dataURI += 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/RenderMarkdown.mjs b/src/core/operations/RenderMarkdown.mjs new file mode 100644 index 00000000..c656bf5b --- /dev/null +++ b/src/core/operations/RenderMarkdown.mjs @@ -0,0 +1,69 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import MarkdownIt from "markdown-it"; +import hljs from "highlight.js"; + +/** + * Render Markdown operation + */ +class RenderMarkdown extends Operation { + + /** + * RenderMarkdown constructor + */ + constructor() { + super(); + + this.name = "Render Markdown"; + this.module = "Code"; + this.description = "Renders input Markdown as HTML. HTML rendering is disabled to avoid XSS."; + this.infoURL = "https://wikipedia.org/wiki/Markdown"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "Autoconvert URLs to links", + type: "boolean", + value: false + }, + { + name: "Enable syntax highlighting", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [convertLinks, enableHighlighting] = args, + md = new MarkdownIt({ + linkify: convertLinks, + html: false, // Explicitly disable HTML rendering + highlight: function(str, lang) { + if (lang && hljs.getLanguage(lang) && enableHighlighting) { + try { + return hljs.highlight(lang, str).value; + } catch (__) {} + } + + return ""; + } + }), + rendered = md.render(input); + + return `
${rendered}
`; + } + +} + +export default RenderMarkdown; diff --git a/src/core/operations/ResizeImage.mjs b/src/core/operations/ResizeImage.mjs new file mode 100644 index 00000000..2d2af045 --- /dev/null +++ b/src/core/operations/ResizeImage.mjs @@ -0,0 +1,145 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Resize Image operation + */ +class ResizeImage extends Operation { + + /** + * ResizeImage constructor + */ + constructor() { + super(); + + this.name = "Resize Image"; + this.module = "Image"; + this.description = "Resizes an image to the specified width and height values."; + this.infoURL = "https://wikipedia.org/wiki/Image_scaling"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Width", + type: "number", + value: 100, + min: 1 + }, + { + name: "Height", + type: "number", + value: 100, + min: 1 + }, + { + name: "Unit type", + type: "option", + value: ["Pixels", "Percent"] + }, + { + name: "Maintain aspect ratio", + type: "boolean", + value: false + }, + { + name: "Resizing algorithm", + type: "option", + value: [ + "Nearest Neighbour", + "Bilinear", + "Bicubic", + "Hermite", + "Bezier" + ], + defaultIndex: 1 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + let width = args[0], + height = args[1]; + const unit = args[2], + aspect = args[3], + resizeAlg = args[4]; + + const resizeMap = { + "Nearest Neighbour": Jimp.RESIZE_NEAREST_NEIGHBOR, + "Bilinear": Jimp.RESIZE_BILINEAR, + "Bicubic": Jimp.RESIZE_BICUBIC, + "Hermite": Jimp.RESIZE_HERMITE, + "Bezier": Jimp.RESIZE_BEZIER + }; + + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (unit === "Percent") { + width = image.getWidth() * (width / 100); + height = image.getHeight() * (height / 100); + } + + if (isWorkerEnvironment()) + self.sendStatusMessage("Resizing image..."); + if (aspect) { + image.scaleToFit(width, height, resizeMap[resizeAlg]); + } else { + image.resize(width, height, resizeMap[resizeAlg]); + } + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error resizing image. (${err})`); + } + } + + /** + * Displays the resized image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default ResizeImage; diff --git a/src/core/operations/Return.mjs b/src/core/operations/Return.mjs new file mode 100644 index 00000000..2a1c6a38 --- /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.mjs"; + +/** + * 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..49c752a8 --- /dev/null +++ b/src/core/operations/Reverse.mjs @@ -0,0 +1,87 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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": ["Byte", "Character", "Line"], + "defaultIndex": 1 + } + ]; + } + + /** + * @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 if (args[0] === "Character") { + const inputString = Utils.byteArrayToUtf8(input); + let result = ""; + for (let i = inputString.length - 1; i >= 0; i--) { + const c = inputString.charCodeAt(i); + if (i > 0 && 0xdc00 <= c && c <= 0xdfff) { + const c2 = inputString.charCodeAt(i - 1); + if (0xd800 <= c2 && c2 <= 0xdbff) { + // surrogates + result += inputString.charAt(i - 1); + result += inputString.charAt(i); + i--; + continue; + } + } + result += inputString.charAt(i); + } + return Utils.strToUtf8ByteArray(result); + } else { + return input.reverse(); + } + } + +} + +export default Reverse; diff --git a/src/core/operations/RisonDecode.mjs b/src/core/operations/RisonDecode.mjs new file mode 100644 index 00000000..d4e36f80 --- /dev/null +++ b/src/core/operations/RisonDecode.mjs @@ -0,0 +1,57 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import rison from "rison"; + +/** + * Rison Decode operation + */ +class RisonDecode extends Operation { + + /** + * RisonDecode constructor + */ + constructor() { + super(); + + this.name = "Rison Decode"; + this.module = "Encodings"; + this.description = "Rison, a data serialization format optimized for compactness in URIs. Rison is a slight variation of JSON that looks vastly superior after URI encoding. Rison still expresses exactly the same set of data structures as JSON, so data can be translated back and forth without loss or guesswork."; + this.infoURL = "https://github.com/Nanonid/rison"; + this.inputType = "string"; + this.outputType = "Object"; + this.args = [ + { + name: "Decode Option", + type: "editableOption", + value: ["Decode", "Decode Object", "Decode Array"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {Object} + */ + run(input, args) { + const [decodeOption] = args; + switch (decodeOption) { + case "Decode": + return rison.decode(input); + case "Decode Object": + return rison.decode_object(input); + case "Decode Array": + return rison.decode_array(input); + default: + throw new OperationError("Invalid Decode option"); + } + } +} + +export default RisonDecode; diff --git a/src/core/operations/RisonEncode.mjs b/src/core/operations/RisonEncode.mjs new file mode 100644 index 00000000..12b13b66 --- /dev/null +++ b/src/core/operations/RisonEncode.mjs @@ -0,0 +1,59 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import rison from "rison"; + +/** + * Rison Encode operation + */ +class RisonEncode extends Operation { + + /** + * RisonEncode constructor + */ + constructor() { + super(); + + this.name = "Rison Encode"; + this.module = "Encodings"; + this.description = "Rison, a data serialization format optimized for compactness in URIs. Rison is a slight variation of JSON that looks vastly superior after URI encoding. Rison still expresses exactly the same set of data structures as JSON, so data can be translated back and forth without loss or guesswork."; + this.infoURL = "https://github.com/Nanonid/rison"; + this.inputType = "Object"; + this.outputType = "string"; + this.args = [ + { + name: "Encode Option", + type: "option", + value: ["Encode", "Encode Object", "Encode Array", "Encode URI"] + }, + ]; + } + + /** + * @param {Object} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [encodeOption] = args; + switch (encodeOption) { + case "Encode": + return rison.encode(input); + case "Encode Object": + return rison.encode_object(input); + case "Encode Array": + return rison.encode_array(input); + case "Encode URI": + return rison.encode_uri(input); + default: + throw new OperationError("Invalid encode option"); + } + } +} + +export default RisonEncode; diff --git a/src/core/operations/Rotate.js b/src/core/operations/Rotate.js deleted file mode 100755 index 2ea02308..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) { - var result = []; - for (var i = 0; i < data.length; i++) { - var b = data[i]; - for (var 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) { - var amount = args[2], - output = input, - chr, - rot13Lowercase = args[0], - rot13Upperacse = args[1]; - - if (amount) { - if (amount < 0) { - amount = 26 - (Math.abs(amount) % 26); - } - - for (var 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) { - var amount = args[0], - output = input, - chr; - - if (amount) { - if (amount < 0) { - amount = 94 - (Math.abs(amount) % 94); - } - - for (var 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) { - var bit = (b & 1) << 7; - return (b >> 1) | bit; - }, - - - /** - * Rotate left bitwise op. - * - * @private - * @param {byte} b - * @returns {byte} - */ - _rotl: function(b) { - var 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) { - var carryBits = 0, - newByte, - result = []; - - amount = amount % 8; - for (var i = 0; i < data.length; i++) { - var 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) { - var carryBits = 0, - newByte, - result = []; - - amount = amount % 8; - for (var i = data.length-1; i >= 0; i--) { - var 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/RotateImage.mjs b/src/core/operations/RotateImage.mjs new file mode 100644 index 00000000..894ec785 --- /dev/null +++ b/src/core/operations/RotateImage.mjs @@ -0,0 +1,95 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Rotate Image operation + */ +class RotateImage extends Operation { + + /** + * RotateImage constructor + */ + constructor() { + super(); + + this.name = "Rotate Image"; + this.module = "Image"; + this.description = "Rotates an image by the specified number of degrees."; + this.infoURL = ""; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Rotation amount (degrees)", + type: "number", + value: 90 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [degrees] = args; + + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("Rotating image..."); + image.rotate(degrees); + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error rotating image. (${err})`); + } + } + + /** + * Displays the rotated image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default RotateImage; diff --git a/src/core/operations/RotateLeft.mjs b/src/core/operations/RotateLeft.mjs new file mode 100644 index 00000000..e9ef8329 --- /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.mjs"; +import {rot, rotl, rotlCarry} from "../lib/Rotate.mjs"; + + +/** + * 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..4e9451fc --- /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.mjs"; +import {rot, rotr, rotrCarry} from "../lib/Rotate.mjs"; + + +/** + * 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..7f712ad9 --- /dev/null +++ b/src/core/operations/SHA0.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * 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. The message digest algorithm consists, by default, of 80 rounds."; + this.infoURL = "https://wikipedia.org/wiki/SHA-1#SHA-0"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Rounds", + type: "number", + value: 80, + min: 16 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("sha0", input, {rounds: args[0]}); + } + +} + +export default SHA0; diff --git a/src/core/operations/SHA1.mjs b/src/core/operations/SHA1.mjs new file mode 100644 index 00000000..8c081f2f --- /dev/null +++ b/src/core/operations/SHA1.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * 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. The message digest algorithm consists, by default, of 80 rounds."; + this.infoURL = "https://wikipedia.org/wiki/SHA-1"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Rounds", + type: "number", + value: 80, + min: 16 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("sha1", input, {rounds: args[0]}); + } + +} + +export default SHA1; diff --git a/src/core/operations/SHA2.mjs b/src/core/operations/SHA2.mjs new file mode 100644 index 00000000..ecdc4cc5 --- /dev/null +++ b/src/core/operations/SHA2.mjs @@ -0,0 +1,92 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * 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.
The message digest algorithm for SHA256 variants consists, by default, of 64 rounds, and for SHA512 variants, it is, by default, 160."; + this.infoURL = "https://wikipedia.org/wiki/SHA-2"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Size", + type: "argSelector", + value: [ + { + name: "512", + on: [2], + off: [1] + }, + { + name: "384", + on: [2], + off: [1] + }, + { + name: "256", + on: [1], + off: [2] + }, + { + name: "224", + on: [1], + off: [2] + }, + { + name: "512/256", + on: [2], + off: [1] + }, + { + name: "512/224", + on: [2], + off: [1] + } + ] + }, + { + name: "Rounds", // For SHA256 variants + type: "number", + value: 64, + min: 16 + }, + { + name: "Rounds", // For SHA512 variants + type: "number", + value: 160, + min: 32 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = args[0]; + const rounds = (size === "256" || size === "224") ? args[1] : args[2]; + return runHash("sha" + size, input, {rounds: rounds}); + } + +} + +export default SHA2; diff --git a/src/core/operations/SHA3.mjs b/src/core/operations/SHA3.mjs new file mode 100644 index 00000000..0f3cdef7 --- /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.mjs"; +import JSSHA3 from "js-sha3"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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/SIGABA.mjs b/src/core/operations/SIGABA.mjs new file mode 100644 index 00000000..e3a9b82e --- /dev/null +++ b/src/core/operations/SIGABA.mjs @@ -0,0 +1,287 @@ +/** + * Emulation of the SIGABA machine. + * + * @author hettysymes + * @copyright hettysymes 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {LETTERS} from "../lib/Enigma.mjs"; +import {NUMBERS, CR_ROTORS, I_ROTORS, SigabaMachine, CRRotor, IRotor} from "../lib/SIGABA.mjs"; + +/** + * Sigaba operation + */ +class Sigaba extends Operation { + + /** + * Sigaba constructor + */ + constructor() { + super(); + + this.name = "SIGABA"; + this.module = "Bletchley"; + this.description = "Encipher/decipher with the WW2 SIGABA machine.

SIGABA, otherwise known as ECM Mark II, was used by the United States for message encryption during WW2 up to the 1950s. It was developed in the 1930s by the US Army and Navy, and has up to this day never been broken. Consisting of 15 rotors: 5 cipher rotors and 10 rotors (5 control rotors and 5 index rotors) controlling the stepping of the cipher rotors, the rotor stepping for SIGABA is much more complex than other rotor machines of its time, such as Enigma. All example rotor wirings are random example sets.

To configure rotor wirings, for the cipher and control rotors enter a string of letters which map from A to Z, and for the index rotors enter a sequence of numbers which map from 0 to 9. Note that encryption is not the same as decryption, so first choose the desired mode.

Note: Whilst this has been tested against other software emulators, it has not been tested against hardware."; + this.infoURL = "https://wikipedia.org/wiki/SIGABA"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "1st (left-hand) cipher rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "1st cipher rotor reversed", + type: "boolean", + value: false + }, + { + name: "1st cipher rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "2nd cipher rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "2nd cipher rotor reversed", + type: "boolean", + value: false + }, + { + name: "2nd cipher rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "3rd (middle) cipher rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "3rd cipher rotor reversed", + type: "boolean", + value: false + }, + { + name: "3rd cipher rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "4th cipher rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "4th cipher rotor reversed", + type: "boolean", + value: false + }, + { + name: "4th cipher rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "5th (right-hand) cipher rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "5th cipher rotor reversed", + type: "boolean", + value: false + }, + { + name: "5th cipher rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "1st (left-hand) control rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "1st control rotor reversed", + type: "boolean", + value: false + }, + { + name: "1st control rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "2nd control rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "2nd control rotor reversed", + type: "boolean", + value: false + }, + { + name: "2nd control rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "3rd (middle) control rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "3rd control rotor reversed", + type: "boolean", + value: false + }, + { + name: "3rd control rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "4th control rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "4th control rotor reversed", + type: "boolean", + value: false + }, + { + name: "4th control rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "5th (right-hand) control rotor", + type: "editableOption", + value: CR_ROTORS, + defaultIndex: 0 + }, + { + name: "5th control rotor reversed", + type: "boolean", + value: false + }, + { + name: "5th control rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "1st (left-hand) index rotor", + type: "editableOption", + value: I_ROTORS, + defaultIndex: 0 + }, + { + name: "1st index rotor initial value", + type: "option", + value: NUMBERS + }, + { + name: "2nd index rotor", + type: "editableOption", + value: I_ROTORS, + defaultIndex: 0 + }, + { + name: "2nd index rotor initial value", + type: "option", + value: NUMBERS + }, + { + name: "3rd (middle) index rotor", + type: "editableOption", + value: I_ROTORS, + defaultIndex: 0 + }, + { + name: "3rd index rotor initial value", + type: "option", + value: NUMBERS + }, + { + name: "4th index rotor", + type: "editableOption", + value: I_ROTORS, + defaultIndex: 0 + }, + { + name: "4th index rotor initial value", + type: "option", + value: NUMBERS + }, + { + name: "5th (right-hand) index rotor", + type: "editableOption", + value: I_ROTORS, + defaultIndex: 0 + }, + { + name: "5th index rotor initial value", + type: "option", + value: NUMBERS + }, + { + name: "SIGABA mode", + type: "option", + value: ["Encrypt", "Decrypt"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const sigabaSwitch = args[40]; + const cipherRotors = []; + const controlRotors = []; + const indexRotors = []; + for (let i=0; i<5; i++) { + const rotorWiring = args[i*3]; + cipherRotors.push(new CRRotor(rotorWiring, args[i*3+2], args[i*3+1])); + } + for (let i=5; i<10; i++) { + const rotorWiring = args[i*3]; + controlRotors.push(new CRRotor(rotorWiring, args[i*3+2], args[i*3+1])); + } + for (let i=15; i<20; i++) { + const rotorWiring = args[i*2]; + indexRotors.push(new IRotor(rotorWiring, args[i*2+1])); + } + const sigaba = new SigabaMachine(cipherRotors, controlRotors, indexRotors); + let result; + if (sigabaSwitch === "Encrypt") { + result = sigaba.encrypt(input); + } else if (sigabaSwitch === "Decrypt") { + result = sigaba.decrypt(input); + } + return result; + } + +} +export default Sigaba; diff --git a/src/core/operations/SM2Decrypt.mjs b/src/core/operations/SM2Decrypt.mjs new file mode 100644 index 00000000..39657110 --- /dev/null +++ b/src/core/operations/SM2Decrypt.mjs @@ -0,0 +1,71 @@ +/** + * @author flakjacket95 [dflack95@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; +import Operation from "../Operation.mjs"; + +import { SM2 } from "../lib/SM2.mjs"; + +/** + * SM2Decrypt operation + */ +class SM2Decrypt extends Operation { + + /** + * SM2Decrypt constructor + */ + constructor() { + super(); + + this.name = "SM2 Decrypt"; + this.module = "Crypto"; + this.description = "Decrypts a message utilizing the SM2 standard"; + this.infoURL = ""; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc) + this.inputType = "string"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Private Key", + type: "string", + value: "DEADBEEF" + }, + { + "name": "Input Format", + "type": "option", + "value": ["C1C3C2", "C1C2C3"], + "defaultIndex": 0 + }, + { + name: "Curve", + type: "option", + "value": ["sm2p256v1"], + "defaultIndex": 0 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const [privateKey, inputFormat, curveName] = args; + + if (privateKey.length !== 64) { + throw new OperationError("Input private key must be in hex; and should be 32 bytes"); + } + + const sm2 = new SM2(curveName, inputFormat); + sm2.setPrivateKey(privateKey); + + const result = sm2.decrypt(input); + return result; + } + +} + +export default SM2Decrypt; diff --git a/src/core/operations/SM2Encrypt.mjs b/src/core/operations/SM2Encrypt.mjs new file mode 100644 index 00000000..b1e5f901 --- /dev/null +++ b/src/core/operations/SM2Encrypt.mjs @@ -0,0 +1,77 @@ +/** + * @author flakjacket95 [dflack95@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; +import Operation from "../Operation.mjs"; + +import { SM2 } from "../lib/SM2.mjs"; + +/** + * SM2 Encrypt operation + */ +class SM2Encrypt extends Operation { + + /** + * SM2Encrypt constructor + */ + constructor() { + super(); + + this.name = "SM2 Encrypt"; + this.module = "Crypto"; + this.description = "Encrypts a message utilizing the SM2 standard"; + this.infoURL = ""; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc) + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + + this.args = [ + { + name: "Public Key X", + type: "string", + value: "DEADBEEF" + }, + { + name: "Public Key Y", + type: "string", + value: "DEADBEEF" + }, + { + "name": "Output Format", + "type": "option", + "value": ["C1C3C2", "C1C2C3"], + "defaultIndex": 0 + }, + { + name: "Curve", + type: "option", + "value": ["sm2p256v1"], + "defaultIndex": 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const [publicKeyX, publicKeyY, outputFormat, curveName] = args; + this.outputFormat = outputFormat; + + if (publicKeyX.length !== 64 || publicKeyY.length !== 64) { + throw new OperationError("Invalid Public Key - Ensure each component is 32 bytes in size and in hex"); + } + + const sm2 = new SM2(curveName, outputFormat); + sm2.setPublicKey(publicKeyX, publicKeyY); + + const result = sm2.encrypt(new Uint8Array(input)); + return result; + } +} + +export default SM2Encrypt; diff --git a/src/core/operations/SM3.mjs b/src/core/operations/SM3.mjs new file mode 100644 index 00000000..1441476a --- /dev/null +++ b/src/core/operations/SM3.mjs @@ -0,0 +1,57 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import Sm3 from "crypto-api/src/hasher/sm3.mjs"; +import {toHex} from "crypto-api/src/encoder/hex.mjs"; + +/** + * SM3 operation + */ +class SM3 extends Operation { + + /** + * SM3 constructor + */ + constructor() { + super(); + + this.name = "SM3"; + this.module = "Crypto"; + this.description = "SM3 is a cryptographic hash function used in the Chinese National Standard. SM3 is mainly used in digital signatures, message authentication codes, and pseudorandom number generators. The message digest algorithm consists, by default, of 64 rounds and length of 256."; + this.infoURL = "https://wikipedia.org/wiki/SM3_(hash_function)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Length", + type: "number", + value: 256 + }, + { + name: "Rounds", + type: "number", + value: 64, + min: 16 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const msg = Utils.arrayBufferToStr(input, false); + const hasher = new Sm3({length: args[0], rounds: args[1]}); + hasher.update(msg); + return toHex(hasher.finalize()); + } +} + +export default SM3; diff --git a/src/core/operations/SM4Decrypt.mjs b/src/core/operations/SM4Decrypt.mjs new file mode 100644 index 00000000..a0853021 --- /dev/null +++ b/src/core/operations/SM4Decrypt.mjs @@ -0,0 +1,88 @@ +/** + * @author swesven + * @copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { decryptSM4 } from "../lib/SM4.mjs"; + +/** + * SM4 Decrypt operation + */ +class SM4Decrypt extends Operation { + + /** + * SM4Encrypt constructor + */ + constructor() { + super(); + + this.name = "SM4 Decrypt"; + this.module = "Ciphers"; + this.description = "SM4 is a 128-bit block cipher, currently established as a national standard (GB/T 32907-2016) of China."; + this.infoURL = "https://wikipedia.org/wiki/SM4_(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", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"] + }, + { + "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.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length !== 16) + throw new OperationError(`Invalid key length: ${key.length} bytes + +SM4 uses a key length of 16 bytes (128 bits).`); + if (iv.length !== 16 && !mode.startsWith("ECB")) + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +SM4 uses an IV length of 16 bytes (128 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + input = Utils.convertToByteArray(input, inputType); + const output = decryptSM4(input, key, iv, mode.substring(0, 3), mode.endsWith("NoPadding")); + return outputType === "Hex" ? toHex(output) : Utils.byteArrayToUtf8(output); + } + +} + +export default SM4Decrypt; diff --git a/src/core/operations/SM4Encrypt.mjs b/src/core/operations/SM4Encrypt.mjs new file mode 100644 index 00000000..0a58dfb9 --- /dev/null +++ b/src/core/operations/SM4Encrypt.mjs @@ -0,0 +1,88 @@ +/** + * @author swesven + * @copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { encryptSM4 } from "../lib/SM4.mjs"; + +/** + * SM4 Encrypt operation + */ +class SM4Encrypt extends Operation { + + /** + * SM4Encrypt constructor + */ + constructor() { + super(); + + this.name = "SM4 Encrypt"; + this.module = "Ciphers"; + this.description = "SM4 is a 128-bit block cipher, currently established as a national standard (GB/T 32907-2016) of China. Multiple block cipher modes are supported. When using CBC or ECB mode, the PKCS#7 padding scheme is used."; + this.infoURL = "https://wikipedia.org/wiki/SM4_(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", "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.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length !== 16) + throw new OperationError(`Invalid key length: ${key.length} bytes + +SM4 uses a key length of 16 bytes (128 bits).`); + if (iv.length !== 16 && !mode.startsWith("ECB")) + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +SM4 uses an IV length of 16 bytes (128 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + input = Utils.convertToByteArray(input, inputType); + const output = encryptSM4(input, key, iv, mode.substring(0, 3), mode.endsWith("NoPadding")); + return outputType === "Hex" ? toHex(output) : Utils.byteArrayToUtf8(output); + } + +} + +export default SM4Encrypt; diff --git a/src/core/operations/SQLBeautify.mjs b/src/core/operations/SQLBeautify.mjs new file mode 100644 index 00000000..0f3d2e3c --- /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.mjs"; + +/** + * 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..582ad43d --- /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.mjs"; + +/** + * 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..6a76a68b --- /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.mjs"; +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://forensics.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..ef9777c6 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import { bitOp, sub, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs"; + +/** + * 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/Salsa20.mjs b/src/core/operations/Salsa20.mjs new file mode 100644 index 00000000..7a76cf26 --- /dev/null +++ b/src/core/operations/Salsa20.mjs @@ -0,0 +1,154 @@ +/** + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { salsa20Block } from "../lib/Salsa20.mjs"; + +/** + * Salsa20 operation + */ +class Salsa20 extends Operation { + + /** + * Salsa20 constructor + */ + constructor() { + super(); + + this.name = "Salsa20"; + this.module = "Ciphers"; + this.description = "Salsa20 is a stream cipher designed by Daniel J. Bernstein and submitted to the eSTREAM project; Salsa20/8 and Salsa20/12 are round-reduced variants. It is closely related to the ChaCha stream cipher.

Key: Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).

Nonce: Salsa20 uses a nonce of 8 bytes (64 bits).

Counter: Salsa uses a counter of 8 bytes (64 bits). The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes."; + this.infoURL = "https://wikipedia.org/wiki/Salsa20"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Nonce", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"] + }, + { + "name": "Counter", + "type": "number", + "value": 0, + "min": 0 + }, + { + "name": "Rounds", + "type": "option", + "value": ["20", "12", "8"] + }, + { + "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.convertToByteArray(args[0].string, args[0].option), + nonceType = args[1].option, + rounds = parseInt(args[3], 10), + inputType = args[4], + outputType = args[5]; + + if (key.length !== 16 && key.length !== 32) { + throw new OperationError(`Invalid key length: ${key.length} bytes. + +Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`); + } + + let counter, nonce; + if (nonceType === "Integer") { + nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little"); + } else { + nonce = Utils.convertToByteArray(args[1].string, args[1].option); + if (!(nonce.length === 8)) { + throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. + +Salsa20 uses a nonce of 8 bytes (64 bits).`); + } + } + counter = Utils.intToByteArray(args[2], 8, "little"); + + const output = []; + input = Utils.convertToByteArray(input, inputType); + + let counterAsInt = Utils.byteArrayToInt(counter, "little"); + for (let i = 0; i < input.length; i += 64) { + counter = Utils.intToByteArray(counterAsInt, 8, "little"); + const stream = salsa20Block(key, nonce, counter, rounds); + for (let j = 0; j < 64 && i + j < input.length; j++) { + output.push(input[i + j] ^ stream[j]); + } + counterAsInt++; + } + + if (outputType === "Hex") { + return toHex(output); + } else { + return Utils.arrayBufferToStr(Uint8Array.from(output).buffer); + } + } + + /** + * Highlight Salsa20 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + + /** + * Highlight Salsa20 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + +} + +export default Salsa20; diff --git a/src/core/operations/ScanForEmbeddedFiles.mjs b/src/core/operations/ScanForEmbeddedFiles.mjs new file mode 100644 index 00000000..2e83fcb9 --- /dev/null +++ b/src/core/operations/ScanForEmbeddedFiles.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { scanForFileTypes } from "../lib/FileType.mjs"; +import { FILE_SIGNATURES } from "../lib/FileSignatures.mjs"; + +/** + * 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 = Object.keys(FILE_SIGNATURES).map(cat => { + return { + name: cat, + type: "boolean", + value: cat === "Miscellaneous" ? false : 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 treated as reliable. Any sufficiently long file is likely to contain these magic bytes coincidentally.\n", + numFound = 0; + const categories = [], + data = new Uint8Array(input); + + args.forEach((cat, i) => { + if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]); + }); + + const types = scanForFileTypes(data, categories); + + if (types.length) { + types.forEach(type => { + numFound++; + output += `\nOffset ${type.offset} (0x${Utils.hex(type.offset)}): + File type: ${type.fileDetails.name} + Extension: ${type.fileDetails.extension} + MIME type: ${type.fileDetails.mime}\n`; + + if (type?.fileDetails?.description?.length) { + output += ` Description: ${type.fileDetails.description}\n`; + } + }); + } + + if (numFound === 0) { + output += "\nNo embedded files were found."; + } + + return output; + } + +} + +export default ScanForEmbeddedFiles; diff --git a/src/core/operations/ScatterChart.mjs b/src/core/operations/ScatterChart.mjs new file mode 100644 index 00000000..fc0caf03 --- /dev/null +++ b/src/core/operations/ScatterChart.mjs @@ -0,0 +1,199 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import * as d3temp from "d3"; +import * as nodomtemp from "nodom"; +import { getScatterValues, getScatterValuesWithColour, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +const d3 = d3temp.default ? d3temp.default : d3temp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + +/** + * Scatter chart operation + */ +class ScatterChart extends Operation { + + /** + * ScatterChart constructor + */ + constructor() { + super(); + + this.name = "Scatter chart"; + this.module = "Charts"; + this.description = "Plots two-variable data as single points on a graph."; + this.infoURL = "https://wikipedia.org/wiki/Scatter_plot"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "Record delimiter", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "Use column headers as labels", + type: "boolean", + value: true, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Y label", + type: "string", + value: "", + }, + { + name: "Colour", + type: "string", + value: COLOURS.max, + }, + { + name: "Point radius", + type: "number", + value: 10, + }, + { + name: "Use colour from third column", + type: "boolean", + value: false, + } + ]; + } + + /** + * Scatter chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), + columnHeadingsAreIncluded = args[2], + fillColour = Utils.escapeHtml(args[5]), + radius = args[6], + colourInInput = args[7], + dimension = 500; + + let xLabel = args[3], + yLabel = args[4]; + + const dataFunction = colourInInput ? getScatterValuesWithColour : getScatterValues; + const { headings, values } = dataFunction( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + const margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + const xExtent = d3.extent(values, d => d[0]), + xDelta = xExtent[1] - xExtent[0], + yExtent = d3.extent(values, d => d[1]), + yDelta = yExtent[1] - yExtent[0], + xAxis = d3.scaleLinear() + .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)]) + .range([0, width]), + yAxis = d3.scaleLinear() + .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)]) + .range([height, 0]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + marginedSpace.append("g") + .attr("class", "points") + .attr("clip-path", "url(#clip)") + .selectAll("circle") + .data(values) + .enter() + .append("circle") + .attr("cx", (d) => xAxis(d[0])) + .attr("cy", (d) => yAxis(d[1])) + .attr("r", d => radius) + .attr("fill", d => { + return colourInInput ? d[2] : fillColour; + }) + .attr("stroke", "rgba(0, 0, 0, 0.5)") + .attr("stroke-width", "0.5") + .append("title") + .text(d => { + const x = d[0], + y = d[1], + tooltip = `X: ${x}\n + Y: ${y}\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + } + +} + +export default ScatterChart; diff --git a/src/core/operations/Scrypt.mjs b/src/core/operations/Scrypt.mjs new file mode 100644 index 00000000..9c4ba304 --- /dev/null +++ b/src/core/operations/Scrypt.mjs @@ -0,0 +1,90 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import scryptsy from "scryptsy"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * 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 (isWorkerEnvironment()) + 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 68a0d4c2..00000000 --- a/src/core/operations/SeqUtils.js +++ /dev/null @@ -1,225 +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) { - var 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) { - var 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) { - var search = args[0].string, - type = args[0].option; - - if (type === "Regex" && search) { - try { - var 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) { - if (args[0] === "Line") { - var lines = [], - line = [], - result = []; - for (var 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) { - var lines = input.split("\n"), - output = "", - width = lines.length.toString().length; - - for (var 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) { - var 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/SeriesChart.mjs b/src/core/operations/SeriesChart.mjs new file mode 100644 index 00000000..7baf594c --- /dev/null +++ b/src/core/operations/SeriesChart.mjs @@ -0,0 +1,230 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import * as d3temp from "d3"; +import * as nodomtemp from "nodom"; +import { getSeriesValues, RECORD_DELIMITER_OPTIONS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts.mjs"; + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +const d3 = d3temp.default ? d3temp.default : d3temp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + +/** + * Series chart operation + */ +class SeriesChart extends Operation { + + /** + * SeriesChart constructor + */ + constructor() { + super(); + + this.name = "Series chart"; + this.module = "Charts"; + this.description = "A time series graph is a line graph of repeated measurements taken over regular time intervals."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "Record delimiter", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Point radius", + type: "number", + value: 1, + }, + { + name: "Series colours", + type: "string", + value: "mediumseagreen, dodgerblue, tomato", + }, + ]; + } + + /** + * Series chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), + xLabel = args[2], + pipRadius = args[3], + // Escape HTML from all colours to prevent reflected XSS. See https://github.com/gchq/CyberChef/issues/1265 + seriesColours = args[4].split(",").map((colour) => { + return Utils.escapeHtml(colour); + }), + svgWidth = 500, + interSeriesPadding = 20, + xAxisHeight = 50, + seriesLabelWidth = 50, + seriesHeight = 100, + seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding; + + const { xValues, series } = getSeriesValues(input, recordDelimiter, fieldDelimiter), + allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight), + svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding; + + const document = new nodom.Document(); + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); + + const xAxis = d3.scalePoint() + .domain(xValues) + .range([0, seriesWidth]); + + svg.append("g") + .attr("class", "axis axis--x") + .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`) + .call( + d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => { + return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0; + })) + ); + + svg.append("text") + .attr("x", svgWidth / 2) + .attr("y", xAxisHeight / 2) + .style("text-anchor", "middle") + .text(xLabel); + + const tooltipText = {}, + tooltipAreaWidth = seriesWidth / xValues.length; + + xValues.forEach(x => { + const tooltip = []; + + series.forEach(serie => { + const y = serie.data[x]; + if (typeof y === "undefined") return; + + tooltip.push(`${serie.name}: ${y}`); + }); + + tooltipText[x] = tooltip.join("\n"); + }); + + const chartArea = svg.append("g") + .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`); + + chartArea + .append("g") + .selectAll("rect") + .data(xValues) + .enter() + .append("rect") + .attr("x", x => { + return xAxis(x) - (tooltipAreaWidth / 2); + }) + .attr("y", 0) + .attr("width", tooltipAreaWidth) + .attr("height", allSeriesHeight) + .attr("stroke", "none") + .attr("fill", "transparent") + .append("title") + .text(x => { + return `${x}\n + --\n + ${tooltipText[x]}\n + `.replace(/\s{2,}/g, "\n"); + }); + + const yAxesArea = svg.append("g") + .attr("transform", `translate(0, ${xAxisHeight})`); + + series.forEach((serie, seriesIndex) => { + const yExtent = d3.extent(Object.values(serie.data)), + yAxis = d3.scaleLinear() + .domain(yExtent) + .range([seriesHeight, 0]); + + const seriesGroup = chartArea + .append("g") + .attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`); + + let path = ""; + xValues.forEach((x, xIndex) => { + let nextX = xValues[xIndex + 1], + y = serie.data[x], + nextY= serie.data[nextX]; + + if (typeof y === "undefined" || typeof nextY === "undefined") return; + + x = xAxis(x); nextX = xAxis(nextX); + y = yAxis(y); nextY = yAxis(nextY); + + path += `M ${x} ${y} L ${nextX} ${nextY} z `; + }); + + seriesGroup + .append("path") + .attr("d", path) + .attr("fill", "none") + .attr("stroke", seriesColours[seriesIndex % seriesColours.length]) + .attr("stroke-width", "1"); + + xValues.forEach(x => { + const y = serie.data[x]; + if (typeof y === "undefined") return; + + seriesGroup + .append("circle") + .attr("cx", xAxis(x)) + .attr("cy", yAxis(y)) + .attr("r", pipRadius) + .attr("fill", seriesColours[seriesIndex % seriesColours.length]) + .append("title") + .text(d => { + return `${x}\n + --\n + ${tooltipText[x]}\n + `.replace(/\s{2,}/g, "\n"); + }); + }); + + yAxesArea + .append("g") + .attr("transform", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).ticks(5)); + + yAxesArea + .append("g") + .attr("transform", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) + .append("text") + .style("text-anchor", "middle") + .attr("transform", "rotate(-90)") + .text(serie.name); + }); + + return svg._groups[0][0].outerHTML; + } + +} + +export default SeriesChart; diff --git a/src/core/operations/SetDifference.mjs b/src/core/operations/SetDifference.mjs new file mode 100644 index 00000000..dc46c079 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..7e6dbe10 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..80913063 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..69e034e6 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; +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/SharpenImage.mjs b/src/core/operations/SharpenImage.mjs new file mode 100644 index 00000000..5cf6b606 --- /dev/null +++ b/src/core/operations/SharpenImage.mjs @@ -0,0 +1,169 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import { gaussianBlur } from "../lib/ImageManipulation.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * Sharpen Image operation + */ +class SharpenImage extends Operation { + + /** + * SharpenImage constructor + */ + constructor() { + super(); + + this.name = "Sharpen Image"; + this.module = "Image"; + this.description = "Sharpens an image (Unsharp mask)"; + this.infoURL = "https://wikipedia.org/wiki/Unsharp_masking"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Radius", + type: "number", + value: 2, + min: 1 + }, + { + name: "Amount", + type: "number", + value: 1, + min: 0, + step: 0.1 + }, + { + name: "Threshold", + type: "number", + value: 10, + min: 0, + max: 100 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + async run(input, args) { + const [radius, amount, threshold] = args; + + if (!isImage(input)) { + throw new OperationError("Invalid file type."); + } + + let image; + try { + image = await Jimp.read(input); + } catch (err) { + throw new OperationError(`Error loading image. (${err})`); + } + + try { + if (isWorkerEnvironment()) + self.sendStatusMessage("Sharpening image... (Cloning image)"); + const blurMask = image.clone(); + + if (isWorkerEnvironment()) + self.sendStatusMessage("Sharpening image... (Blurring cloned image)"); + const blurImage = gaussianBlur(image.clone(), radius); + + + if (isWorkerEnvironment()) + self.sendStatusMessage("Sharpening image... (Creating unsharp mask)"); + blurMask.scan(0, 0, blurMask.bitmap.width, blurMask.bitmap.height, function(x, y, idx) { + const blurRed = blurImage.bitmap.data[idx]; + const blurGreen = blurImage.bitmap.data[idx + 1]; + const blurBlue = blurImage.bitmap.data[idx + 2]; + + const normalRed = this.bitmap.data[idx]; + const normalGreen = this.bitmap.data[idx + 1]; + const normalBlue = this.bitmap.data[idx + 2]; + + // Subtract blurred pixel value from normal image + this.bitmap.data[idx] = (normalRed > blurRed) ? normalRed - blurRed : 0; + this.bitmap.data[idx + 1] = (normalGreen > blurGreen) ? normalGreen - blurGreen : 0; + this.bitmap.data[idx + 2] = (normalBlue > blurBlue) ? normalBlue - blurBlue : 0; + }); + + if (isWorkerEnvironment()) + self.sendStatusMessage("Sharpening image... (Merging with unsharp mask)"); + image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y, idx) { + let maskRed = blurMask.bitmap.data[idx]; + let maskGreen = blurMask.bitmap.data[idx + 1]; + let maskBlue = blurMask.bitmap.data[idx + 2]; + + const normalRed = this.bitmap.data[idx]; + const normalGreen = this.bitmap.data[idx + 1]; + const normalBlue = this.bitmap.data[idx + 2]; + + // Calculate luminance + const maskLuminance = (0.2126 * maskRed + 0.7152 * maskGreen + 0.0722 * maskBlue); + const normalLuminance = (0.2126 * normalRed + 0.7152 * normalGreen + 0.0722 * normalBlue); + + let luminanceDiff; + if (maskLuminance > normalLuminance) { + luminanceDiff = maskLuminance - normalLuminance; + } else { + luminanceDiff = normalLuminance - maskLuminance; + } + + // Scale mask colours by amount + maskRed = maskRed * amount; + maskGreen = maskGreen * amount; + maskBlue = maskBlue * amount; + + // Only change pixel value if the difference is higher than threshold + if ((luminanceDiff / 255) * 100 >= threshold) { + this.bitmap.data[idx] = (normalRed + maskRed) <= 255 ? normalRed + maskRed : 255; + this.bitmap.data[idx + 1] = (normalGreen + maskGreen) <= 255 ? normalGreen + maskGreen : 255; + this.bitmap.data[idx + 2] = (normalBlue + maskBlue) <= 255 ? normalBlue + maskBlue : 255; + } + }); + + let imageBuffer; + if (image.getMIME() === "image/gif") { + imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG); + } else { + imageBuffer = await image.getBufferAsync(Jimp.AUTO); + } + return imageBuffer.buffer; + } catch (err) { + throw new OperationError(`Error sharpening image. (${err})`); + } + } + + /** + * Displays the sharpened image using HTML for web apps + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const dataArray = new Uint8Array(data); + + const type = isImage(dataArray); + if (!type) { + throw new OperationError("Invalid file type."); + } + + return ``; + } + +} + +export default SharpenImage; diff --git a/src/core/operations/ShowBase64Offsets.mjs b/src/core/operations/ShowBase64Offsets.mjs new file mode 100644 index 00000000..37d8a6ce --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {fromBase64, toBase64} from "../lib/Base64.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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/ShowOnMap.mjs b/src/core/operations/ShowOnMap.mjs new file mode 100644 index 00000000..c2ac1c6e --- /dev/null +++ b/src/core/operations/ShowOnMap.mjs @@ -0,0 +1,120 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Show on map operation + */ +class ShowOnMap extends Operation { + + /** + * ShowOnMap constructor + */ + constructor() { + super(); + + this.name = "Show on map"; + this.module = "Hashing"; + this.description = "Displays co-ordinates on a slippy map.

Co-ordinates will be converted to decimal degrees before being shown on the map.

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)

This operation will not work offline."; + this.infoURL = "https://foundation.wikimedia.org/wiki/Maps_Terms_of_Use"; + this.inputType = "string"; + this.outputType = "string"; + this.presentType = "html"; + this.args = [ + { + name: "Zoom Level", + type: "number", + value: 13 + }, + { + name: "Input Format", + type: "option", + value: ["Auto"].concat(FORMATS) + }, + { + name: "Input Delimiter", + type: "option", + value: [ + "Auto", + "Direction Preceding", + "Direction Following", + "\\n", + "Comma", + "Semi-colon", + "Colon" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.replace(/\s+/g, "") !== "") { + const inFormat = args[1], + inDelim = args[2]; + let latLong; + try { + latLong = convertCoordinates(input, inFormat, inDelim, "Decimal Degrees", "Comma", "None", 5); + } catch (error) { + throw new OperationError(error); + } + latLong = latLong.replace(/[,]$/, ""); + latLong = latLong.replace(/°/g, ""); + return latLong; + } + return input; + } + + /** + * @param {string} data + * @param {Object[]} args + * @returns {string} + */ + async present(data, args) { + if (data.replace(/\s+/g, "") === "") { + data = "0, 0"; + } + const zoomLevel = args[0]; + const tileUrl = "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png", + tileAttribution = "Wikimedia maps | © OpenStreetMap contributors", + leafletUrl = "https://unpkg.com/leaflet@1.5.0/dist/leaflet.js", + leafletCssUrl = "https://unpkg.com/leaflet@1.5.0/dist/leaflet.css"; + return ` + +
+`; + } +} + +export default ShowOnMap; diff --git a/src/core/operations/Shuffle.mjs b/src/core/operations/Shuffle.mjs new file mode 100644 index 00000000..14c4ffab --- /dev/null +++ b/src/core/operations/Shuffle.mjs @@ -0,0 +1,78 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * Shuffle operation + */ +class Shuffle extends Operation { + + /** + * Shuffle constructor + */ + constructor() { + super(); + + this.name = "Shuffle"; + this.module = "Default"; + this.description = "Randomly reorders input elements."; + this.infoURL = "https://wikipedia.org/wiki/Shuffling"; + 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]); + if (input.length === 0) return input; + + // return a random number in [0, 1) + const rng = (typeof crypto) !== "undefined" && crypto.getRandomValues ? (function() { + const buf = new Uint32Array(2); + return function() { + // generate 53-bit random integer: 21 + 32 bits + crypto.getRandomValues(buf); + const value = (buf[0] >>> (32 - 21)) * ((1 << 30) * 4) + buf[1]; + return value / ((1 << 23) * (1 << 30)); + }; + })() : Math.random; + + // return a random integer in [0, max) + const randint = function(max) { + return Math.floor(rng() * max); + }; + + // Split input into shuffleable sections + const toShuffle = input.split(delim); + + // shuffle elements + for (let i = toShuffle.length - 1; i > 0; i--) { + const idx = randint(i + 1); + const tmp = toShuffle[idx]; + toShuffle[idx] = toShuffle[i]; + toShuffle[i] = tmp; + } + + return toShuffle.join(delim); + } + +} + +export default Shuffle; diff --git a/src/core/operations/Sleep.mjs b/src/core/operations/Sleep.mjs new file mode 100644 index 00000000..1e7b657a --- /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.mjs"; + +/** + * 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..ae5859b3 --- /dev/null +++ b/src/core/operations/Snefru.mjs @@ -0,0 +1,58 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * 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: "Size", + type: "number", + value: 128, + min: 32, + max: 480, + step: 32 + }, + { + name: "Rounds", + type: "option", + value: ["8", "4", "2"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("snefru", input, { + length: args[0], + rounds: 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..7a4714be --- /dev/null +++ b/src/core/operations/Sort.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort, lengthSort} from "../lib/Sort.mjs"; + +/** + * 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)", "Length"] + } + ]; + } + + /** + * @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(caseInsensitiveSort); + } else if (order === "IP address") { + sorted = sorted.sort(ipSort); + } else if (order === "Numeric") { + sorted = sorted.sort(numericSort); + } else if (order === "Numeric (hexadecimal)") { + sorted = sorted.sort(hexadecimalSort); + } else if (order === "Length") { + sorted = sorted.sort(lengthSort); + } + + if (sortReverse) sorted.reverse(); + return sorted.join(delim); + } + +} + +export default Sort; diff --git a/src/core/operations/Split.mjs b/src/core/operations/Split.mjs new file mode 100644 index 00000000..0da1f101 --- /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.mjs"; +import {SPLIT_DELIM_OPTIONS, JOIN_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * 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..d5a26a2d --- /dev/null +++ b/src/core/operations/SplitColourChannels.mjs @@ -0,0 +1,102 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {isImage} from "../lib/FileType.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "List"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {List} + */ + async run(input, args) { + input = new Uint8Array(input); + // Make sure that the input is an image + if (!isImage(input)) throw new OperationError("Invalid file type."); + + 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]); + } + + /** + * 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..d0509dcd --- /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.mjs"; +import { stdDev, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + + +/** + * 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 816d4abc..00000000 --- a/src/core/operations/StrUtils.js +++ /dev/null @@ -1,542 +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) { - var 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 { - var 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) { - var 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) { - var 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) { - var 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) { - var delim = Utils.charRep[args[0]], - reverse = args[2]; - - try { - var regex = new RegExp(args[1]); - } catch (err) { - return "Invalid regex. Details: " + err.message; - } - - var 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) { - var 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 (var 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) { - var sampleDelim = args[0], - samples = input.split(sampleDelim), - outputs = [], - 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 - for (s = 0; s < samples.length; s++) { - outputs[s] = ""; - } - - // 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); - }, - - - /** - * 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) { - var 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) { - var output = "", - total = 0, - match; - - while ((match = regex.exec(input))) { - total++; - if (matches) { - output += match[0] + "\n"; - } - if (captureGroups) { - for (var 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/Streebog.mjs b/src/core/operations/Streebog.mjs new file mode 100644 index 00000000..b65accf6 --- /dev/null +++ b/src/core/operations/Streebog.mjs @@ -0,0 +1,63 @@ +/** + * @author mshwed [m@ttshwed.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import GostDigest from "../vendor/gost/gostDigest.mjs"; +import {toHexFast} from "../lib/Hex.mjs"; + +/** + * Streebog operation + */ +class Streebog extends Operation { + + /** + * Streebog constructor + */ + constructor() { + super(); + + this.name = "Streebog"; + this.module = "Hashing"; + this.description = "Streebog is a cryptographic hash function defined in the Russian national standard GOST R 34.11-2012 Information Technology \u2013 Cryptographic Information Security \u2013 Hash Function. It was created to replace an obsolete GOST hash function defined in the old standard GOST R 34.11-94, and as an asymmetric reply to SHA-3 competition by the US National Institute of Standards and Technology."; + this.infoURL = "https://wikipedia.org/wiki/Streebog"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Digest length", + "type": "option", + "value": ["256", "512"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [length] = args; + + const algorithm = { + version: 2012, + mode: "HASH", + length: parseInt(length, 10) + }; + + try { + const gostDigest = new GostDigest(algorithm); + + return toHexFast(gostDigest.digest(input)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default Streebog; diff --git a/src/core/operations/Strings.mjs b/src/core/operations/Strings.mjs new file mode 100644 index 00000000..dca2433a --- /dev/null +++ b/src/core/operations/Strings.mjs @@ -0,0 +1,139 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import XRegExp from "xregexp"; +import { search } from "../lib/Extract.mjs"; +import { caseInsensitiveSort } from "../lib/Sort.mjs"; + +/** + * 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 + }, + { + name: "Sort", + type: "boolean", + value: false + }, + { + name: "Unique", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [encoding, minLen, matchType, displayTotal, sort, unique] = 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"); + const results = search( + input, + regex, + null, + sort ? caseInsensitiveSort : null, + unique + ); + + if (displayTotal) { + return `Total found: ${results.length}\n\n${results.join("\n")}`; + } else { + return results.join("\n"); + } + } + +} + +export default Strings; diff --git a/src/core/operations/StripHTMLTags.mjs b/src/core/operations/StripHTMLTags.mjs new file mode 100644 index 00000000..9670cc3d --- /dev/null +++ b/src/core/operations/StripHTMLTags.mjs @@ -0,0 +1,72 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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 + } + ]; + this.checks = [ + { + pattern: "(|
|)", + flags: "i", + args: [true, 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..2160e3e7 --- /dev/null +++ b/src/core/operations/StripHTTPHeaders.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * 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 = []; + this.checks = [ + { + pattern: "^HTTP(.|\\s)+?(\\r?\\n){2}", + flags: "", + 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/StripIPv4Header.mjs b/src/core/operations/StripIPv4Header.mjs new file mode 100644 index 00000000..4b6ef1af --- /dev/null +++ b/src/core/operations/StripIPv4Header.mjs @@ -0,0 +1,57 @@ +/** + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Stream from "../lib/Stream.mjs"; + +/** + * Strip IPv4 header operation + */ +class StripIPv4Header extends Operation { + + /** + * StripIPv4Header constructor + */ + constructor() { + super(); + + this.name = "Strip IPv4 header"; + this.module = "Default"; + this.description = "Strips the IPv4 header from an IPv4 packet, outputting the payload."; + this.infoURL = "https://wikipedia.org/wiki/IPv4"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const MIN_HEADER_LEN = 20; + + const s = new Stream(new Uint8Array(input)); + if (s.length < MIN_HEADER_LEN) { + throw new OperationError("Input length is less than minimum IPv4 header length"); + } + + const ihl = s.readInt(1) & 0x0f; + const dataOffsetBytes = ihl * 4; + if (s.length < dataOffsetBytes) { + throw new OperationError("Input length is less than IHL"); + } + + s.moveTo(dataOffsetBytes); + + return s.getBytes().buffer; + } + +} + +export default StripIPv4Header; diff --git a/src/core/operations/StripTCPHeader.mjs b/src/core/operations/StripTCPHeader.mjs new file mode 100644 index 00000000..747744b2 --- /dev/null +++ b/src/core/operations/StripTCPHeader.mjs @@ -0,0 +1,60 @@ +/** + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Stream from "../lib/Stream.mjs"; + +/** + * Strip TCP header operation + */ +class StripTCPHeader extends Operation { + + /** + * StripTCPHeader constructor + */ + constructor() { + super(); + + this.name = "Strip TCP header"; + this.module = "Default"; + this.description = "Strips the TCP header from a TCP segment, outputting the payload."; + this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const MIN_HEADER_LEN = 20; + const DATA_OFFSET_OFFSET = 12; + const DATA_OFFSET_LEN_BITS = 4; + + const s = new Stream(new Uint8Array(input)); + if (s.length < MIN_HEADER_LEN) { + throw new OperationError("Need at least 20 bytes for a TCP Header"); + } + + s.moveTo(DATA_OFFSET_OFFSET); + const dataOffsetWords = s.readBits(DATA_OFFSET_LEN_BITS); + const dataOffsetBytes = dataOffsetWords * 4; + if (s.length < dataOffsetBytes) { + throw new OperationError("Input length is less than data offset"); + } + + s.moveTo(dataOffsetBytes); + + return s.getBytes().buffer; + } + +} + +export default StripTCPHeader; diff --git a/src/core/operations/StripUDPHeader.mjs b/src/core/operations/StripUDPHeader.mjs new file mode 100644 index 00000000..0847c58f --- /dev/null +++ b/src/core/operations/StripUDPHeader.mjs @@ -0,0 +1,51 @@ +/** + * @author c65722 [] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Stream from "../lib/Stream.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Strip UDP header operation + */ +class StripUDPHeader extends Operation { + + /** + * StripUDPHeader constructor + */ + constructor() { + super(); + + this.name = "Strip UDP header"; + this.module = "Default"; + this.description = "Strips the UDP header from a UDP datagram, outputting the payload."; + this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const HEADER_LEN = 8; + + const s = new Stream(new Uint8Array(input)); + if (s.length < HEADER_LEN) { + throw new OperationError("Need 8 bytes for a UDP Header"); + } + + s.moveTo(HEADER_LEN); + + return s.getBytes().buffer; + } + +} + +export default StripUDPHeader; diff --git a/src/core/operations/Subsection.mjs b/src/core/operations/Subsection.mjs new file mode 100644 index 00000000..a7da44b0 --- /dev/null +++ b/src/core/operations/Subsection.mjs @@ -0,0 +1,161 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Recipe from "../Recipe.mjs"; +import Dish from "../Dish.mjs"; + +/** + * Subsection operation + */ +class Subsection extends Operation { + + /** + * Subsection constructor + */ + constructor() { + super(); + + this.name = "Subsection"; + this.flowControl = true; + this.module = "Default"; + 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.

Use the Merge operation to reset the effects of subsection."; + 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 !== "") { + // Set to 1 as if we are here, then there is one, the current one. + let numOp = 1; + // 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) { + numOp--; + if (numOp === 0 || opList[i].ingValues[0]) + break; + else + // Not this subsection's Merge. + subOpList.push(opList[i]); + } else { + if (opList[i].name === "Fork" || opList[i].name === "Subsection") + numOp++; + subOpList.push(opList[i]); + } + } + + let flags = "", + inOffset = 0, + output = "", + m, + progress = 0; + + if (!caseSensitive) flags += "i"; + if (global) flags += "g"; + + const regex = new RegExp(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 + recipe.opList.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..4a8cdc5d --- /dev/null +++ b/src/core/operations/Substitute.mjs @@ -0,0 +1,112 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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" + }, + { + "name": "Ignore case", + "type": "boolean", + "value": false + } + ]; + } + + /** + * Convert a single character using the dictionary, if ignoreCase is true then + * check in the dictionary for both upper and lower case versions of the character. + * In output the input character case is preserved. + * @param {string} char + * @param {Object} dict + * @param {boolean} ignoreCase + * @returns {string} + */ + cipherSingleChar(char, dict, ignoreCase) { + if (!ignoreCase) + return dict[char] || char; + + const isUpperCase = char === char.toUpperCase(); + + // convert using the dictionary keeping the case of the input character + + if (dict[char] !== undefined) { + // if the character is in the dictionary return the value with the input case + return isUpperCase ? dict[char].toUpperCase() : dict[char].toLowerCase(); + } + + // check for the other case, if it is in the dictionary return the value with the right case + if (isUpperCase) { + if (dict[char.toLowerCase()] !== undefined) + return dict[char.toLowerCase()].toUpperCase(); + } else { + if (dict[char.toUpperCase()] !== undefined) + return dict[char.toUpperCase()].toLowerCase(); + } + + return char; + } + + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const plaintext = Utils.expandAlphRange([...args[0]]), + ciphertext = Utils.expandAlphRange([...args[1]]), + ignoreCase = args[2]; + let output = ""; + + if (plaintext.length !== ciphertext.length) { + output = "Warning: Plaintext and Ciphertext lengths differ\n\n"; + } + + // create dictionary for conversion + const dict = {}; + for (let i = 0; i < Math.min(ciphertext.length, plaintext.length); i++) { + dict[plaintext[i]] = ciphertext[i]; + } + + // map every letter with the conversion function + for (const character of input) { + output += this.cipherSingleChar(character, dict, ignoreCase); + } + + 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..3d9cb176 --- /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.mjs"; +import { sub, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + + +/** + * 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..dd7ebd3b --- /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.mjs"; +import { sum, createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + + +/** + * 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/SwapCase.mjs b/src/core/operations/SwapCase.mjs new file mode 100644 index 00000000..679d7703 --- /dev/null +++ b/src/core/operations/SwapCase.mjs @@ -0,0 +1,76 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Swap case operation + */ +class SwapCase extends Operation { + + /** + * SwapCase constructor + */ + constructor() { + super(); + + this.name = "Swap case"; + this.module = "Default"; + this.description = "Converts uppercase letters to lowercase ones, and lowercase ones to uppercase ones."; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let result = ""; + for (let i = 0; i < input.length; i++) { + const c = input.charAt(i); + const upper = c.toUpperCase(); + if (c === upper) { + result += c.toLowerCase(); + } else { + result += upper; + } + } + return result; + } + + /** + * Highlight Swap case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Swap 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 SwapCase; diff --git a/src/core/operations/SwapEndianness.mjs b/src/core/operations/SwapEndianness.mjs new file mode 100644 index 00000000..872d3529 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {toHex, fromHex} from "../lib/Hex.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..f9044769 --- /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.mjs"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..b8c578d6 --- /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.mjs"; +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..0e5c6c60 --- /dev/null +++ b/src/core/operations/TCPIPChecksum.mjs @@ -0,0 +1,54 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + let csum = 0; + + for (let i = 0; i < input.length; i++) { + 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..8a29bb2d --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * 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..431bbee1 --- /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.mjs"; + +/** + * 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/TakeNthBytes.mjs b/src/core/operations/TakeNthBytes.mjs new file mode 100644 index 00000000..05de8869 --- /dev/null +++ b/src/core/operations/TakeNthBytes.mjs @@ -0,0 +1,79 @@ +/** + * @author Oshawk [oshawk@protonmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Take nth bytes operation + */ +class TakeNthBytes extends Operation { + + /** + * TakeNthBytes constructor + */ + constructor() { + super(); + + this.name = "Take nth bytes"; + this.module = "Default"; + this.description = "Takes every nth byte starting with a given byte."; + this.infoURL = ""; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Take every", + type: "number", + value: 4 + }, + { + name: "Starting at", + type: "number", + value: 0 + }, + { + name: "Apply to each line", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const n = args[0]; + const start = args[1]; + const eachLine = args[2]; + + if (parseInt(n, 10) !== n || n <= 0) { + throw new OperationError("'Take every' must be a positive integer."); + } + if (parseInt(start, 10) !== start || start < 0) { + throw new OperationError("'Starting at' must be a positive or zero integer."); + } + + let offset = 0; + const output = []; + for (let i = 0; i < input.length; i++) { + if (eachLine && input[i] === 0x0a) { + output.push(0x0a); + offset = i + 1; + } else if (i - offset >= start && (i - (start + offset)) % n === 0) { + output.push(input[i]); + } + } + + return output; + } + +} + +export default TakeNthBytes; diff --git a/src/core/operations/Tar.mjs b/src/core/operations/Tar.mjs new file mode 100644 index 00000000..3078d959 --- /dev/null +++ b/src/core/operations/Tar.mjs @@ -0,0 +1,142 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "File"; + this.args = [ + { + "name": "Filename", + "type": "string", + "value": "file.txt" + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + input = new Uint8Array(input); + + const Tarball = function() { + this.bytes = new Array(512); + this.position = 0; + }; + + 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..ae96fd0a --- /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.mjs"; +import Utils from "../Utils.mjs"; +import cptable from "codepage"; +import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs"; + +/** + * 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(CHR_ENC_CODE_PAGES).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(CHR_ENC_CODE_PAGES), + mode = args[0]; + + charsets.forEach(charset => { + try { + if (mode === "Decode") { + output[charset] = cptable.utils.decode(CHR_ENC_CODE_PAGES[charset], input); + } else { + output[charset] = Utils.arrayBufferToStr(cptable.utils.encode(CHR_ENC_CODE_PAGES[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.escapeHtml(Utils.escapeWhitespace(encodings[enc])); + 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 7e18d56c..00000000 --- a/src/core/operations/Tidy.js +++ /dev/null @@ -1,243 +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) { - var 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) { - var output = []; - for (var 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) { - var 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 - var lines = [], - line = []; - - for (var i = 0; i < input.length; i++) { - if (input[i] === 0x0a) { - lines.push(line); - line = []; - } else { - line.push(input[i]); - } - } - lines.push(line); - - var 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) { - var 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 - var lines = [], - line = []; - - for (var i = 0; i < input.length; i++) { - if (input[i] === 0x0a) { - lines.push(line); - line = []; - } else { - line.push(input[i]); - } - } - lines.push(line); - - var 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) { - var 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..3908742c --- /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.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD.mjs"; +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..09a91571 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..44eb8b48 --- /dev/null +++ b/src/core/operations/ToBase32.mjs @@ -0,0 +1,87 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base32.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Alphabet", + type: "editableOption", + value: ALPHABET_OPTIONS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + input = new Uint8Array(input); + + const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; + let output = "", + 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/ToBase45.mjs b/src/core/operations/ToBase45.mjs new file mode 100644 index 00000000..a1cd9d2f --- /dev/null +++ b/src/core/operations/ToBase45.mjs @@ -0,0 +1,82 @@ +/** + * @author Thomas Weißschuh [thomas@t-8ch.de] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import {ALPHABET, highlightToBase45, highlightFromBase45} from "../lib/Base45.mjs"; +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * To Base45 operation + */ +class ToBase45 extends Operation { + + /** + * ToBase45 constructor + */ + constructor() { + super(); + + this.name = "To Base45"; + this.module = "Default"; + this.description = "Base45 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. Base45 is optimized for usage with QR codes."; + this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Alphabet", + type: "string", + value: ALPHABET + } + ]; + + this.highlight = highlightToBase45; + this.highlightReverse = highlightFromBase45; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + input = new Uint8Array(input); + const alphabet = Utils.expandAlphRange(args[0]); + + const res = []; + + for (const pair of Utils.chunked(input, 2)) { + let b = 0; + for (const e of pair) { + b *= 256; + b += e; + } + + let chars = 0; + do { + res.push(alphabet[b % 45]); + chars++; + b = Math.floor(b / 45); + } while (b > 0); + + if (chars < 2) { + res.push("0"); + chars++; + } + if (pair.length > 1 && chars < 3) { + res.push("0"); + } + } + + + return res.join(""); + + } + +} + +export default ToBase45; diff --git a/src/core/operations/ToBase58.mjs b/src/core/operations/ToBase58.mjs new file mode 100644 index 00000000..2e71b20e --- /dev/null +++ b/src/core/operations/ToBase58.mjs @@ -0,0 +1,90 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base58.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Alphabet", + "type": "editableOption", + "value": ALPHABET_OPTIONS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + let alphabet = args[0] || ALPHABET_OPTIONS[0].value, + result = []; + + 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 = b; + + for (let i = 0; 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..a00d9275 --- /dev/null +++ b/src/core/operations/ToBase62.mjs @@ -0,0 +1,62 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import BigNumber from "bignumber.js"; +import Utils from "../Utils.mjs"; +import {toHexFast} from "../lib/Hex.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Alphabet", + type: "string", + value: "0-9A-Za-z" + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + if (input.length < 1) return ""; + + const alphabet = Utils.expandAlphRange(args[0]).join(""); + const BN62 = BigNumber.clone({ ALPHABET: alphabet }); + + input = toHexFast(input).toUpperCase(); + + // Read number in as hex using normal alphabet + const normalized = new BigNumber(input, 16); + // Copy to BigNumber clone that uses the specified Base62 alphabet + const number = new BN62(normalized); + + 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..53516a9f --- /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.mjs"; +import {toBase64, ALPHABET_OPTIONS} from "../lib/Base64.mjs"; + +/** + * 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(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..839ef1e4 --- /dev/null +++ b/src/core/operations/ToBase85.mjs @@ -0,0 +1,94 @@ +/** + * @author PenguinGeorge [george@penguingeorge.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Alphabet", + type: "editableOption", + value: ALPHABET_OPTIONS + }, + { + name: "Include delimeter", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + const alphabet = Utils.expandAlphRange(args[0]).join(""), + encoding = alphabetName(alphabet), + includeDelim = args[1]; + 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/ToBase92.mjs b/src/core/operations/ToBase92.mjs new file mode 100644 index 00000000..bca8e872 --- /dev/null +++ b/src/core/operations/ToBase92.mjs @@ -0,0 +1,67 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import { base92Chr } from "../lib/Base92.mjs"; +import Operation from "../Operation.mjs"; + +/** + * To Base92 operation + */ +class ToBase92 extends Operation { + /** + * ToBase92 constructor + */ + constructor() { + super(); + + this.name = "To Base92"; + this.module = "Default"; + this.description = "Base92 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.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "string"; + this.outputType = "byteArray"; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const res = []; + let bitString = ""; + + while (input.length > 0) { + while (bitString.length < 13 && input.length > 0) { + bitString += input[0].charCodeAt(0).toString(2).padStart(8, "0"); + input = input.slice(1); + } + if (bitString.length < 13) + break; + const i = parseInt(bitString.slice(0, 13), 2); + res.push(base92Chr(Math.floor(i / 91))); + res.push(base92Chr(i % 91)); + bitString = bitString.slice(13); + } + + if (bitString.length > 0) { + if (bitString.length < 7) { + bitString = bitString.padEnd(6, "0"); + res.push(base92Chr(parseInt(bitString, 2))); + } else { + bitString = bitString.padEnd(13, "0"); + const i = parseInt(bitString.slice(0, 13), 2); + res.push(base92Chr(Math.floor(i / 91))); + res.push(base92Chr(i % 91)); + } + } + + return res; + + } +} + +export default ToBase92; diff --git a/src/core/operations/ToBinary.mjs b/src/core/operations/ToBinary.mjs new file mode 100644 index 00000000..ba72a55b --- /dev/null +++ b/src/core/operations/ToBinary.mjs @@ -0,0 +1,88 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {BIN_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import {toBinary} from "../lib/Binary.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": BIN_DELIM_OPTIONS + }, + { + "name": "Byte Length", + "type": "number", + "value": 8 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + const padding = args[1] ? args[1] : 8; + return toBinary(input, args[0], padding); + } + + /** + * 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..fcf13de2 --- /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.mjs"; +import {BRAILLE_LOOKUP} from "../lib/Braille.mjs"; + +/** + * 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..8d7c5445 --- /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.js"; +import Operation from "../Operation.mjs"; +import { replaceVariableNames } from "../lib/Code.mjs"; + +/** + * 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..77b14172 --- /dev/null +++ b/src/core/operations/ToCaseInsensitiveRegex.mjs @@ -0,0 +1,95 @@ +/** + * @author masq [github.cyberchef@masq.cc] + * @author n1073645 + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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) { + + /** + * Simulates look behind behaviour since javascript doesn't support it. + * + * @param {string} input + * @returns {string} + */ + function preProcess(input) { + let result = ""; + for (let i = 0; i < input.length; i++) { + const temp = input.charAt(i); + if (temp.match(/[a-zA-Z]/g) && (input.charAt(i-1) !== "-") && (input.charAt(i+1) !== "-")) + result += "[" + temp.toLowerCase() + temp.toUpperCase() + "]"; + else + result += temp; + } + return result; + } + + try { + RegExp(input); + } catch (error) { + throw new OperationError("Invalid Regular Expression (Please note this version of node does not support look behinds)."); + } + + // Example: [test] -> [[tT][eE][sS][tT]] + return preProcess(input) + + // Example: [A-Z] -> [A-Za-z] + .replace(/([A-Z]-[A-Z]|[a-z]-[a-z])/g, m => `${m[0].toUpperCase()}-${m[2].toUpperCase()}${m[0].toLowerCase()}-${m[2].toLowerCase()}`) + + // Example: [H-d] -> [A-DH-dh-z] + .replace(/[A-Z]-[a-z]/g, m => `A-${m[2].toUpperCase()}${m}${m[0].toLowerCase()}-z`) + + // Example: [!-D] -> [!-Da-d] + .replace(/\\?[ -@]-[A-Z]/g, m => `${m}a-${m[2].toLowerCase()}`) + + // Example: [%-^] -> [%-^a-z] + .replace(/\\?[ -@]-\\?[[-`]/g, m => `${m}a-z`) + + // Example: [K-`] -> [K-`k-z] + .replace(/[A-Z]-\\?[[-`]/g, m => `${m}${m[0].toLowerCase()}-z`) + + // Example: [[-}] -> [[-}A-Z] + .replace(/\\?[[-`]-\\?[{-~]/g, m => `${m}A-Z`) + + // Example: [b-}] -> [b-}B-Z] + .replace(/[a-z]-\\?[{-~]/g, m => `${m}${m[0].toUpperCase()}-Z`) + + // Example: [<-j] -> [<-z] + .replace(/\\?[ -@]-[a-z]/g, m => `${m[0]}-z`) + + // Example: [^-j] -> [A-J^-j] + .replace(/\\?[[-`]-[a-z]/g, m => `A-${m[2].toUpperCase()}${m}`); + + } +} + +export default ToCaseInsensitiveRegex; diff --git a/src/core/operations/ToCharcode.mjs b/src/core/operations/ToCharcode.mjs new file mode 100644 index 00000000..a4806115 --- /dev/null +++ b/src/core/operations/ToCharcode.mjs @@ -0,0 +1,87 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { DELIM_OPTIONS } from "../lib/Delim.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * 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, + 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 && isWorkerEnvironment()) self.setOption("attemptHighlight", false); + + output += Utils.hex(ordinal, padding) + delim; + } else { + if (isWorkerEnvironment()) 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..65798a7c --- /dev/null +++ b/src/core/operations/ToDecimal.mjs @@ -0,0 +1,59 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + + +/** + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "Support signed values", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + const delim = Utils.charRep(args[0]), + signed = args[1]; + if (signed) { + input = input.map(v => v > 0x7F ? v - 0xFF - 1 : v); + } + return input.join(delim); + } + +} + +export default ToDecimal; diff --git a/src/core/operations/ToFloat.mjs b/src/core/operations/ToFloat.mjs new file mode 100644 index 00000000..e0c70bc6 --- /dev/null +++ b/src/core/operations/ToFloat.mjs @@ -0,0 +1,80 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import ieee754 from "ieee754"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * To Float operation + */ +class ToFloat extends Operation { + + /** + * ToFloat constructor + */ + constructor() { + super(); + + this.name = "To Float"; + this.module = "Default"; + this.description = "Convert to IEEE754 Floating Point Numbers"; + this.infoURL = "https://wikipedia.org/wiki/IEEE_754"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Endianness", + "type": "option", + "value": [ + "Big Endian", + "Little Endian" + ] + }, + { + "name": "Size", + "type": "option", + "value": [ + "Float (4 bytes)", + "Double (8 bytes)" + ] + }, + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [endianness, size, delimiterName] = args; + const delim = Utils.charRep(delimiterName || "Space"); + const byteSize = size === "Double (8 bytes)" ? 8 : 4; + const isLE = endianness === "Little Endian"; + const mLen = byteSize === 4 ? 23 : 52; + + if (input.length % byteSize !== 0) { + throw new OperationError(`Input is not a multiple of ${byteSize}`); + } + + const output = []; + for (let i = 0; i < input.length; i+=byteSize) { + output.push(ieee754.read(input, i, isLE, mLen, byteSize)); + } + return output.join(delim); + } + +} + +export default ToFloat; diff --git a/src/core/operations/ToHTMLEntity.mjs b/src/core/operations/ToHTMLEntity.mjs new file mode 100644 index 00000000..f2a57a43 --- /dev/null +++ b/src/core/operations/ToHTMLEntity.mjs @@ -0,0 +1,1512 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * To HTML Entity operation + */ +class ToHTMLEntity extends Operation { + + /** + * ToHTMLEntity constructor + */ + constructor() { + super(); + + this.name = "To HTML Entity"; + this.module = "Encodings"; + 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", "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 || charcodes[i] in byteToEntity) { + output += "&#" + charcodes[i] + ";"; + } else { + output += Utils.chr(charcodes[i]); + } + } else if (hexa) { + if (charcodes[i] > 255 || charcodes[i] in byteToEntity) { + 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 = { + 9: " ", + 10: " ", + 33: "!", + 34: """, + 35: "#", + 36: "$", + 37: "%", + 38: "&", + 39: "'", + 40: "(", + 41: ")", + 42: "*", + 43: "+", + 44: ",", + 46: ".", + 47: "/", + 58: ":", + 59: ";", + 60: "<", + 61: "=", + 62: ">", + 63: "?", + 64: "@", + 91: "[", + 92: "\", + 93: "]", + 94: "^", + 95: "_", + 96: "`", + 123: "{", + 124: "|", + 125: "}", + 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: "ÿ", + 256: "Ā", + 257: "ā", + 258: "Ă", + 259: "ă", + 260: "Ą", + 261: "ą", + 262: "Ć", + 263: "ć", + 264: "Ĉ", + 265: "ĉ", + 266: "Ċ", + 267: "ċ", + 268: "Č", + 269: "č", + 270: "Ď", + 271: "ď", + 272: "Đ", + 273: "đ", + 274: "Ē", + 275: "ē", + 278: "Ė", + 279: "ė", + 280: "Ę", + 281: "ę", + 282: "Ě", + 283: "ě", + 284: "Ĝ", + 285: "ĝ", + 286: "Ğ", + 287: "ğ", + 288: "Ġ", + 289: "ġ", + 290: "Ģ", + 292: "Ĥ", + 293: "ĥ", + 294: "Ħ", + 295: "ħ", + 296: "Ĩ", + 297: "ĩ", + 298: "Ī", + 299: "ī", + 302: "Į", + 303: "į", + 304: "İ", + 305: "ı", + 306: "IJ", + 307: "ij", + 308: "Ĵ", + 309: "ĵ", + 310: "Ķ", + 311: "ķ", + 312: "ĸ", + 313: "Ĺ", + 314: "ĺ", + 315: "Ļ", + 316: "ļ", + 317: "Ľ", + 318: "ľ", + 319: "Ŀ", + 320: "ŀ", + 321: "Ł", + 322: "ł", + 323: "Ń", + 324: "ń", + 325: "Ņ", + 326: "ņ", + 327: "Ň", + 328: "ň", + 329: "ʼn", + 330: "Ŋ", + 331: "ŋ", + 332: "Ō", + 333: "ō", + 336: "Ő", + 337: "ő", + 338: "Œ", + 339: "œ", + 340: "Ŕ", + 341: "ŕ", + 342: "Ŗ", + 343: "ŗ", + 344: "Ř", + 345: "ř", + 346: "Ś", + 347: "ś", + 348: "Ŝ", + 349: "ŝ", + 350: "Ş", + 351: "ş", + 352: "Š", + 353: "š", + 354: "Ţ", + 355: "ţ", + 356: "Ť", + 357: "ť", + 358: "Ŧ", + 359: "ŧ", + 360: "Ũ", + 361: "ũ", + 362: "Ū", + 363: "ū", + 364: "Ŭ", + 365: "ŭ", + 366: "Ů", + 367: "ů", + 368: "Ű", + 369: "ű", + 370: "Ų", + 371: "ų", + 372: "Ŵ", + 373: "ŵ", + 374: "Ŷ", + 375: "ŷ", + 376: "Ÿ", + 377: "Ź", + 378: "ź", + 379: "Ż", + 380: "ż", + 381: "Ž", + 382: "ž", + 402: "ƒ", + 437: "Ƶ", + 501: "ǵ", + 567: "ȷ", + 710: "ˆ", + 711: "ˇ", + 728: "˘", + 729: "˙", + 730: "˚", + 731: "˛", + 732: "˜", + 785: "̑", + 818: "_", + 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: "ϒ", + 981: "ϕ", + 982: "ϖ", + 988: "Ϝ", + 989: "ϝ", + 1008: "ϰ", + 1009: "ϱ", + 1013: "ε,", + 1014: "϶", + 1025: "Ё", + 1026: "Ђ", + 1027: "Ѓ", + 1028: "Є", + 1029: "Ѕ", + 1030: "І", + 1031: "Ї", + 1032: "Ј", + 1033: "Љ", + 1034: "Њ", + 1035: "Ћ", + 1036: "Ќ", + 1038: "Ў", + 1039: "Џ", + 1040: "А", + 1041: "Б", + 1042: "В", + 1043: "Г", + 1044: "Д", + 1045: "Е", + 1046: "Ж", + 1047: "З", + 1048: "И", + 1049: "Й", + 1050: "К", + 1051: "Л", + 1052: "М", + 1053: "Н", + 1054: "О", + 1055: "П", + 1056: "Р", + 1057: "С", + 1058: "Т", + 1059: "У", + 1060: "Ф", + 1061: "Х", + 1062: "Ц", + 1063: "Ч", + 1064: "Ш", + 1065: "Щ", + 1066: "Ъ", + 1067: "Ы", + 1068: "Ь", + 1069: "Э", + 1070: "Ю", + 1071: "Я", + 1072: "а", + 1073: "б", + 1074: "в", + 1075: "г", + 1076: "д", + 1077: "е", + 1078: "ж", + 1079: "з", + 1080: "и", + 1081: "й", + 1082: "к", + 1083: "л", + 1084: "м", + 1085: "н", + 1086: "о", + 1087: "п", + 1088: "р", + 1089: "с", + 1090: "т", + 1091: "у", + 1092: "ф", + 1093: "х", + 1094: "ц", + 1095: "ч", + 1096: "ш", + 1097: "щ", + 1098: "ъ", + 1099: "ы", + 1100: "ь", + 1101: "э", + 1102: "ю", + 1103: "я", + 1105: "ё", + 1106: "ђ", + 1107: "ѓ", + 1108: "є", + 1109: "ѕ", + 1110: "і", + 1111: "ї", + 1112: "ј", + 1113: "љ", + 1114: "њ", + 1115: "ћ", + 1116: "ќ", + 1118: "ў", + 1119: "џ", + 8194: " ", + 8195: " ", + 8196: " ", + 8197: " ", + 8199: " ", + 8200: " ", + 8201: " ", + 8202: " ", + 8203: "​", + 8204: "‌", + 8205: "‍", + 8206: "‎", + 8207: "‏", + 8208: "‐", + 8211: "–", + 8212: "—", + 8213: "―", + 8214: "‖", + 8216: "‘", + 8217: "’", + 8218: "‚", + 8220: "“", + 8221: "”", + 8222: "„", + 8224: "†", + 8225: "‡", + 8226: "•", + 8229: "‥", + 8230: "…", + 8240: "‰", + 8241: "‱", + 8242: "′", + 8243: "″", + 8244: "‴", + 8245: "‵", + 8249: "‹", + 8250: "›", + 8254: "‾", + 8257: "⁁", + 8259: "⁃", + 8260: "⁄", + 8271: "⁏", + 8279: "⁗", + 8287: " ", + 8288: "⁠", + 8289: "⁡", + 8290: "⁢", + 8291: "⁣", + 8364: "€", + 8411: "⃛", + 8412: "⃜", + 8450: "ℂ", + 8453: "℅", + 8458: "ℊ", + 8459: "ℋ", + 8460: "ℌ", + 8461: "ℍ", + 8462: "ℎ", + 8463: "ℏ", + 8464: "ℐ", + 8465: "ℑ", + 8466: "ℒ", + 8467: "ℓ", + 8469: "ℕ", + 8470: "№", + 8471: "℗", + 8472: "℘", + 8473: "ℙ", + 8474: "ℚ", + 8475: "ℛ", + 8476: "ℜ", + 8477: "ℝ", + 8478: "℞", + 8482: "™", + 8484: "ℤ", + 8486: "Ω", + 8487: "℧", + 8488: "ℨ", + 8489: "℩", + 8491: "Å", + 8492: "ℬ", + 8493: "ℭ", + 8495: "ℯ", + 8496: "ℰ", + 8497: "ℱ", + 8499: "ℳ", + 8500: "ℴ", + 8501: "ℵ", + 8502: "ℶ", + 8503: "ℷ", + 8504: "ℸ", + 8517: "ⅅ", + 8518: "ⅆ", + 8519: "ⅇ", + 8520: "ⅈ", + 8531: "⅓", + 8532: "⅔", + 8533: "⅕", + 8534: "⅖", + 8535: "⅗", + 8536: "⅘", + 8537: "⅙", + 8538: "⅚", + 8539: "⅛", + 8540: "⅜", + 8541: "⅝", + 8542: "⅞", + 8592: "←", + 8593: "↑", + 8594: "→", + 8595: "↓", + 8596: "↔", + 8597: "↕", + 8598: "↖", + 8599: "↗", + 8600: "↘", + 8601: "↙", + 8602: "↚", + 8603: "↛", + 8605: "↝", + 8606: "↞", + 8607: "↟", + 8608: "↠", + 8609: "↡", + 8610: "↢", + 8611: "↣", + 8612: "↤", + 8613: "↥", + 8614: "↦", + 8615: "↧", + 8617: "↩", + 8618: "↪", + 8619: "↫", + 8620: "↬", + 8621: "↭", + 8622: "↮", + 8624: "↰", + 8625: "↱", + 8626: "↲", + 8627: "↳", + 8629: "↵", + 8630: "↶", + 8631: "↷", + 8634: "↺", + 8635: "↻", + 8636: "↼", + 8637: "↽", + 8638: "↾", + 8639: "↿", + 8640: "⇀", + 8641: "⇁", + 8642: "⇂", + 8643: "⇃", + 8644: "⇄", + 8645: "⇅", + 8646: "⇆", + 8647: "⇇", + 8648: "⇈", + 8649: "⇉", + 8650: "⇊", + 8651: "⇋", + 8652: "⇌;", + 8653: "⇍", + 8654: "⇎", + 8655: "⇏", + 8656: "⇐", + 8657: "⇑", + 8658: "⇒", + 8659: "⇓", + 8660: "⇔", + 8661: "⇕", + 8662: "⇖", + 8663: "⇗", + 8664: "⇘", + 8665: "⇙", + 8666: "⇚", + 8667: "⇛", + 8669: "⇝", + 8676: "⇤", + 8677: "⇥", + 8693: "⇵", + 8701: "⇽", + 8702: "⇾", + 8703: "⇿", + 8704: "∀", + 8705: "∁", + 8706: "∂", + 8707: "∃", + 8708: "∄", + 8709: "∅", + 8711: "∇", + 8712: "∈", + 8713: "∉", + 8715: "∋", + 8716: "∌", + 8719: "∏", + 8720: "∐", + 8721: "∑", + 8722: "−", + 8723: "∓", + 8724: "∔", + 8726: "∖", + 8727: "∗", + 8728: "∘", + 8730: "√", + 8733: "∝", + 8734: "∞", + 8735: "∟", + 8736: "∠", + 8737: "∡", + 8738: "∢", + 8739: "∣", + 8740: "∤", + 8741: "∥", + 8742: "∦", + 8743: "∧", + 8744: "∨", + 8745: "∩", + 8746: "∪", + 8747: "∫", + 8748: "∬", + 8749: "∭", + 8750: "∮", + 8751: "∯", + 8752: "∰", + 8753: "∱", + 8754: "∲", + 8755: "∳", + 8756: "∴", + 8757: "∵", + 8758: "∶", + 8759: "∷", + 8760: "∸", + 8762: "∺", + 8763: "∻", + 8764: "∼", + 8765: "∽", + 8766: "∾", + 8767: "∿", + 8768: "≀", + 8769: "≁", + 8770: "≂", + 8771: "≃", + 8772: "≄", + 8773: "≅", + 8774: "≆", + 8775: "≇", + 8776: "≈", + 8777: "≉", + 8778: "≊", + 8779: "≋", + 8780: "≌", + 8781: "≍", + 8782: "≎", + 8783: "≏", + 8784: "≐", + 8785: "≑", + 8786: "≒", + 8787: "≓", + 8788: "≔", + 8789: "≕", + 8790: "≖", + 8791: "≗", + 8793: "≙", + 8794: "≚", + 8796: "≜", + 8799: "≟", + 8800: "≠", + 8801: "≡", + 8802: "≢", + 8804: "≤", + 8805: "≥", + 8806: "≦", + 8807: "≧", + 8808: "≨", + 8809: "≩", + 8810: "≪", + 8811: "≫", + 8812: "≬", + 8813: "≭", + 8814: "≮", + 8815: "≯", + 8816: "≰", + 8817: "≱;", + 8818: "≲", + 8819: "≳", + 8820: "≴", + 8821: "≵", + 8822: "≶", + 8823: "≷", + 8824: "≸", + 8825: "≹", + 8826: "≺", + 8827: "≻", + 8828: "≼", + 8829: "≽", + 8830: "≾", + 8831: "≿", + 8832: "⊀", + 8833: "⊁", + 8834: "⊂", + 8835: "⊃", + 8836: "⊄", + 8837: "⊅", + 8838: "⊆", + 8839: "⊇", + 8840: "⊈", + 8841: "⊉", + 8842: "⊊", + 8843: "⊋", + 8845: "⊍", + 8846: "⊎", + 8847: "⊏", + 8848: "⊐", + 8849: "⊑", + 8850: "⊒", + 8851: "⊓", + 8852: "⊔", + 8853: "⊕", + 8854: "⊖", + 8855: "⊗", + 8856: "⊘", + 8857: "⊙", + 8858: "⊚", + 8859: "⊛", + 8861: "⊝", + 8862: "⊞", + 8863: "⊟", + 8864: "⊠", + 8865: "⊡", + 8866: "⊢", + 8867: "⊣", + 8868: "⊤", + 8869: "⊥", + 8871: "⊧", + 8872: "⊨", + 8873: "⊩", + 8874: "⊪", + 8875: "⊫", + 8876: "⊬", + 8877: "⊭", + 8878: "⊮", + 8879: "⊯", + 8880: "⊰", + 8882: "⊲", + 8883: "⊳", + 8884: "⊴", + 8885: "⊵", + 8886: "⊶", + 8887: "⊷", + 8888: "⊸", + 8889: "⊹", + 8890: "⊺", + 8891: "⊻", + 8893: "⊽", + 8894: "⊾", + 8895: "⊿", + 8896: "⋀", + 8897: "⋁", + 8898: "⋂", + 8899: "⋃", + 8900: "⋄", + 8901: "⋅", + 8902: "⋆", + 8903: "⋇", + 8904: "⋈", + 8905: "⋉", + 8906: "⋊", + 8907: "⋋", + 8908: "⋌", + 8909: "⋍", + 8910: "⋎", + 8911: "⋏", + 8912: "⋐", + 8913: "⋑", + 8914: "⋒", + 8915: "⋓", + 8916: "⋔", + 8917: "⋕", + 8918: "⋖", + 8919: "⋗", + 8920: "⋘", + 8921: "⋙", + 8922: "⋚", + 8923: "⋛", + 8926: "⋞", + 8927: "⋟", + 8928: "⋠", + 8929: "⋡", + 8930: "⋢", + 8931: "⋣", + 8934: "⋦", + 8935: "⋧", + 8936: "⋨", + 8937: "⋩", + 8938: "⋪", + 8939: "⋫", + 8940: "⋬", + 8941: "⋭", + 8942: "⋮", + 8943: "⋯", + 8944: "⋰", + 8945: "⋱", + 8946: "⋲", + 8947: "⋳", + 8948: "⋴", + 8949: "⋵", + 8950: "⋶", + 8951: "⋷", + 8953: "⋹", + 8954: "⋺", + 8955: "⋻", + 8956: "⋼", + 8957: "⋽", + 8958: "⋾", + 8965: "⌅", + 8966: "⌆", + 8968: "⌈", + 8969: "⌉", + 8970: "⌊", + 8971: "⌋", + 8972: "⌌", + 8973: "⌍", + 8974: "⌎", + 8975: "⌏", + 8976: "⌐", + 8978: "⌒", + 8979: "⌓", + 8981: "⌕", + 8982: "⌖", + 8988: "⌜", + 8989: "⌝", + 8990: "⌞", + 8991: "⌟", + 8994: "⌢", + 8995: "⌣", + 9001: "⟨", + 9002: "⟩", + 9005: "⌭", + 9006: "⌮", + 9014: "⌶", + 9021: "⌽", + 9023: "⌿", + 9084: "⍼", + 9136: "⎰", + 9137: "⎱", + 9140: "⎴", + 9141: "⎵", + 9142: "⎶", + 9180: "⏜", + 9181: "⏝", + 9182: "⏞", + 9183: "⏟", + 9186: "⏢", + 9191: "⏧", + 9251: "␣", + 9416: "Ⓢ", + 9472: "─", + 9474: "│", + 9484: "┌", + 9488: "┐", + 9492: "└", + 9496: "┘", + 9500: "├", + 9508: "┤", + 9516: "┬", + 9524: "┴", + 9532: "┼", + 9552: "═", + 9553: "║", + 9554: "╒", + 9555: "╓", + 9556: "╔", + 9557: "╕", + 9558: "╖", + 9559: "╗", + 9560: "╘", + 9561: "╙", + 9562: "╚", + 9563: "╛", + 9564: "╜", + 9565: "╝", + 9566: "╞", + 9567: "╟", + 9568: "╠", + 9569: "╡", + 9570: "╢", + 9571: "╣", + 9572: "╤", + 9573: "╥", + 9674: "◊", + 9675: "○", + 9708: "◬", + 9711: "◯", + 9720: "◸", + 9721: "◹", + 9722: "◺", + 9723: "◻", + 9724: "◼", + 9733: "★", + 9734: "☆", + 9742: "☎", + 9792: "♀", + 9794: "♂", + 9824: "♠", + 9827: "♣", + 9829: "♥", + 9830: "♦", + 9834: "♪", + 9837: "♭", + 9838: "♮", + 9839: "♯", + 10003: "✓", + 10007: "✗", + 10016: "✠", + 10038: "✶", + 10072: "❘", + 10098: "❲", + 10099: "❳", + 10214: "⟦", + 10215: "⟧", + 10216: "⟨", + 10217: "⟩", + 10218: "⟪", + 10219: "⟫", + 10220: "⟬", + 10221: "⟭", + 10229: "⟵", + 10230: "⟶", + 10231: "⟷", + 10232: "⟸", + 10233: "⟹", + 10234: "⟺", + 10236: "⟼", + 10239: "⟿", + 10498: "⤂", + 10499: "⤃", + 10500: "⤄", + 10501: "⤅", + 10508: "⤌", + 10509: "⤍", + 10510: "⤎", + 10511: "⤏", + 10512: "⤐", + 10513: "⤑", + 10514: "⤒", + 10515: "⤓", + 10518: "⤖", + 10521: "⤙", + 10522: "⤚", + 10523: "⤛", + 10524: "⤜", + 10525: "⤝", + 10526: "⤞", + 10527: "⤟", + 10528: "⤠", + 10531: "⤣", + 10532: "⤤", + 10533: "⤥", + 10534: "⤦", + 10535: "⤧", + 10536: "⤨", + 10537: "⤩", + 10538: "⤪", + 10547: "⤳", + 10549: "⤵", + 10550: "⤶", + 10551: "⤷", + 10552: "⤸", + 10553: "⤹", + 10556: "⤼", + 10557: "⤽", + 10565: "⥅", + 10568: "⥈", + 10569: "⥉", + 10570: "⥊", + 10571: "⥋", + 10574: "⥎", + 10575: "⥏", + 10576: "⥐", + 10577: "⥑", + 10578: "⥒", + 10579: "⥓", + 10580: "⥔", + 10581: "⥕", + 10582: "⥖", + 10583: "⥗", + 10584: "⥘", + 10585: "⥙", + 10586: "⥚", + 10587: "⥛", + 10588: "⥜", + 10589: "⥝", + 10590: "⥞", + 10591: "⥟", + 10592: "⥠", + 10593: "⥡", + 10594: "⥢", + 10595: "⥣", + 10596: "⥤", + 10597: "⥥", + 10598: "⥦", + 10599: "⥧", + 10600: "⥨", + 10601: "⥩", + 10602: "⥪", + 10603: "⥫", + 10604: "⥬", + 10605: "⥭", + 10606: "⥮", + 10607: "⥯", + 10608: "⥰", + 10609: "⥱", + 10610: "⥲", + 10611: "⥳", + 10612: "⥴", + 10613: "⥵", + 10614: "⥶", + 10616: "⥸", + 10617: "⥹", + 10619: "⥻", + 10620: "⥼", + 10621: "⥽", + 10622: "⥾", + 10623: "⥿", + 10629: "⦅", + 10630: "⦆", + 10635: "⦋", + 10636: "⦌", + 10637: "⦍", + 10638: "⦎", + 10639: "⦏", + 10640: "⦐", + 10641: "⦑", + 10642: "⦒", + 10643: "⦓", + 10644: "⦔", + 10645: "⦕", + 10646: "⦖", + 10650: "⦚", + 10652: "⦜", + 10653: "⦝", + 10660: "⦤", + 10661: "⦥", + 10662: "⦦", + 10663: "⦧", + 10664: "⦨", + 10665: "⦩", + 10666: "⦪", + 10667: "⦫", + 10668: "⦬", + 10669: "⦭", + 10670: "⦮", + 10671: "⦯", + 10672: "⦰", + 10673: "⦱", + 10674: "⦲", + 10675: "⦳", + 10676: "⦴", + 10677: "⦵", + 10678: "⦶", + 10679: "⦷", + 10681: "⦹", + 10683: "⦻", + 10684: "⦼", + 10686: "⦾", + 10687: "⦿", + 10688: "⧀", + 10689: "⧁", + 10690: "⧂", + 10691: "⧃", + 10692: "⧄", + 10693: "⧅", + 10697: "⧉", + 10701: "⧍", + 10702: "⧎", + 10703: "⧏", + 10704: "⧐", + 10714: "∽̱", + 10716: "⧜", + 10717: "⧝", + 10718: "⧞", + 10723: "⧣", + 10724: "⧤", + 10725: "⧥", + 10731: "⧫", + 10740: "⧴", + 10742: "⧶", + 10752: "⨀", + 10753: "⨁", + 10754: "⨂", + 10756: "⨄", + 10758: "⨆", + 10764: "⨌", + 10765: "⨍", + 10768: "⨐", + 10769: "⨑", + 10770: "⨒", + 10771: "⨓", + 10772: "⨔", + 10773: "⨕", + 10774: "⨖", + 10775: "⨗", + 10786: "⨢", + 10787: "⨣", + 10788: "⨤", + 10789: "⨥", + 10790: "⨦", + 10791: "⨧", + 10793: "⨩", + 10794: "⨪", + 10797: "⨭", + 10798: "⨮", + 10799: "⨯", + 10800: "⨰", + 10801: "⨱", + 10803: "⨳", + 10804: "⨴", + 10805: "⨵", + 10806: "⨶", + 10807: "⨷", + 10808: "⨸", + 10809: "⨹", + 10810: "⨺", + 10811: "⨻", + 10812: "⨼", + 10815: "⨿", + 10816: "⩀", + 10818: "⩂", + 10819: "⩃", + 10820: "⩄", + 10821: "⩅", + 10822: "⩆", + 10823: "⩇", + 10824: "⩈", + 10825: "⩉", + 10826: "⩊", + 10827: "⩋", + 10828: "⩌", + 10829: "⩍", + 10832: "⩐", + 10835: "⩓", + 10836: "⩔", + 10837: "⩕", + 10838: "⩖", + 10839: "⩗", + 10840: "⩘", + 10842: "⩚", + 10843: "⩛", + 10844: "⩜", + 10845: "⩝", + 10847: "⩟", + 10854: "⩦", + 10858: "⩪", + 10861: "⩭", + 10862: "⩮", + 10863: "⩯", + 10864: "⩰", + 10865: "⩱", + 10866: "⩲", + 10867: "⩳", + 10868: "⩴", + 10869: "⩵", + 10871: "⩷", + 10872: "⩸", + 10873: "⩹", + 10874: "⩺", + 10875: "⩻", + 10876: "⩼", + 10877: "⩽", + 10878: "⩾", + 10879: "⩿", + 10880: "⪀", + 10881: "⪁", + 10882: "⪂", + 10883: "⪃", + 10884: "⪄", + 10885: "⪅", + 10886: "⪆", + 10887: "⪇", + 10888: "⪈", + 10889: "⪉", + 10890: "⪊", + 10891: "⪋", + 10892: "⪌", + 10893: "⪍", + 10894: "⪎", + 10895: "⪏", + 10896: "⪐", + 10897: "⪑", + 10898: "⪒", + 10899: "⪓", + 10900: "⪔", + 10901: "⪕", + 10902: "⪖", + 10903: "⪗", + 10904: "⪘", + 10905: "⪙", + 10906: "⪚", + 10909: "⪝", + 10910: "⪞", + 10911: "⪟", + 10912: "⪠", + 10913: "⪡", + 10914: "⪢", + 10916: "⪤", + 10917: "⪥", + 10918: "⪦", + 10919: "⪧", + 10920: "⪨", + 10921: "⪩", + 10922: "⪪", + 10923: "⪫", + 10924: "⪬", + 10925: "⪭", + 10926: "⪮", + 10927: "⪯", + 10928: "⪰", + 10931: "⪳", + 10932: "⪴", + 10933: "⪵", + 10934: "⪶", + 10935: "⪷", + 10936: "⪸", + 10937: "⪹", + 10938: "⪺", + 10939: "⪻", + 10940: "⪼", + 10941: "⪽", + 10942: "⪾", + 10943: "⪿", + 10944: "⫀", + 10945: "⫁", + 10946: "⫂", + 10947: "⫃", + 10948: "⫄", + 10949: "⫅", + 10950: "⫆", + 10951: "⫇", + 10952: "⫈", + 10955: "⫋", + 10956: "⫌", + 10959: "⫏", + 10960: "⫐", + 10961: "⫑", + 10962: "⫒", + 10963: "⫓", + 10964: "⫔", + 10965: "⫕", + 10966: "⫖", + 10967: "⫗", + 10968: "⫘", + 10969: "⫙", + 10970: "⫚", + 10971: "⫛", + 10980: "⫤", + 10982: "⫦", + 10983: "⫧", + 10984: "⫨", + 10985: "⫩", + 10987: "⫫", + 10988: "⫬", + 10989: "⫭", + 10990: "⫮", + 10991: "⫯", + 10992: "⫰", + 10993: "⫱", + 10994: "⫲", + 10995: "⫳", + 11005: "⫽", + 64256: "ff", + 64257: "fi", + 64258: "fl", + 64259: "ffi", + 64260: "ffl", + 119964: "𝒜", + 119966: "𝒞", + 119967: "𝒟", + 119970: "𝒢", + 119973: "𝒥", + 119974: "𝒦", + 119977: "𝒩", + 119978: "𝒪", + 119979: "𝒫", + 119980: "𝒬", + 119982: "𝒮", + 119983: "𝒯", + 119984: "𝒰", + 119985: "𝒱", + 119986: "𝒲", + 119987: "𝒳", + 119988: "𝒴", + 119989: "𝒵", + 119990: "𝒶", + 119991: "𝒷", + 119992: "𝒸", + 119993: "𝒹", + 119995: "𝒻", + 119997: "𝒽", + 119998: "𝒾", + 119999: "𝒿", + 120000: "𝓀", + 120001: "𝓁", + 120002: "𝓂", + 120003: "𝓃", + 120005: "𝓅", + 120006: "𝓆", + 120007: "𝓇", + 120008: "𝓈", + 120009: "𝓉", + 120010: "𝓊", + 120011: "𝓋", + 120012: "𝓌", + 120013: "𝓍", + 120014: "𝓎", + 120015: "𝓏", + 120068: "𝔄", + 120069: "𝔅", + 120071: "𝔇", + 120072: "𝔈", + 120073: "𝔉", + 120074: "𝔊", + 120077: "𝔍", + 120078: "𝔎", + 120079: "𝔏", + 120080: "𝔐", + 120081: "𝔑", + 120082: "𝔒", + 120083: "𝔓", + 120084: "𝔔", + 120086: "𝔖", + 120087: "𝔗", + 120088: "𝔘", + 120089: "𝔙", + 120090: "𝔚", + 120091: "𝔛", + 120092: "𝔜", + 120094: "𝔞", + 120095: "𝔟", + 120096: "𝔠", + 120097: "𝔡", + 120098: "𝔢", + 120099: "𝔣", + 120100: "𝔤", + 120101: "𝔥", + 120102: "𝔦", + 120103: "𝔧", + 120104: "𝔨", + 120105: "𝔩", + 120106: "𝔪", + 120107: "𝔫", + 120108: "𝔬", + 120109: "𝔭", + 120110: "𝔮", + 120111: "𝔯", + 120112: "𝔰", + 120113: "𝔱", + 120114: "𝔲", + 120115: "𝔳", + 120116: "𝔴", + 120117: "𝔵", + 120118: "𝔶", + 120119: "𝔷", + 120120: "𝔸", + 120121: "𝔹", + 120123: "𝔻", + 120124: "𝔼", + 120125: "𝔽", + 120126: "𝔾", + 120128: "𝕀", + 120129: "𝕁", + 120130: "𝕂", + 120131: "𝕃", + 120132: "𝕄", + 120134: "𝕆", + 120138: "𝕊", + 120139: "𝕋", + 120140: "𝕌", + 120141: "𝕍", + 120142: "𝕎", + 120143: "𝕏", + 120144: "𝕐", + 120146: "𝕒", + 120147: "𝕓", + 120148: "𝕔", + 120149: "𝕕", + 120150: "𝕖", + 120151: "𝕗", + 120152: "𝕘", + 120153: "𝕙", + 120154: "𝕚", + 120155: "𝕛", + 120156: "𝕜", + 120157: "𝕝", + 120158: "𝕞", + 120159: "𝕟", + 120160: "𝕠", + 120161: "𝕡", + 120162: "𝕢", + 120163: "𝕣", + 120164: "𝕤", + 120165: "𝕥", + 120166: "𝕦", + 120167: "𝕧", + 120168: "𝕨", + 120169: "𝕩", + 120170: "𝕪", + 120171: "𝕫" +}; + +export default ToHTMLEntity; diff --git a/src/core/operations/ToHex.mjs b/src/core/operations/ToHex.mjs new file mode 100644 index 00000000..092155a9 --- /dev/null +++ b/src/core/operations/ToHex.mjs @@ -0,0 +1,132 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {toHex, TO_HEX_DELIM_OPTIONS} from "../lib/Hex.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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 + }, + { + name: "Bytes per line", + type: "number", + value: 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let delim, comma; + if (args[0] === "0x with comma") { + delim = "0x"; + comma = ","; + } else { + delim = Utils.charRep(args[0] || "Space"); + } + const lineSize = args[1]; + + return toHex(new Uint8Array(input), delim, 2, comma, lineSize); + } + + /** + * Highlight to Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + let delim, commaLen = 0; + if (args[0] === "0x with comma") { + delim = "0x"; + commaLen = 1; + } else { + delim = Utils.charRep(args[0] || "Space"); + } + + const lineSize = args[1], + len = delim.length + commaLen; + + const countLF = function(p) { + // Count the number of LFs from 0 upto p + return (p / lineSize | 0) - (p >= lineSize && p % lineSize === 0); + }; + + pos[0].start = pos[0].start * (2 + len) + countLF(pos[0].start); + pos[0].end = pos[0].end * (2 + len) + countLF(pos[0].end); + + // if the delimiters are not prepended, trim the trailing delimiter + if (!(delim === "0x" || delim === "\\x")) { + pos[0].end -= delim.length; + } + // if there is comma, trim the trailing comma + pos[0].end -= commaLen; + 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) { + let delim, commaLen = 0; + if (args[0] === "0x with comma") { + delim = "0x"; + commaLen = 1; + } else { + delim = Utils.charRep(args[0] || "Space"); + } + + const lineSize = args[1], + len = delim.length + commaLen, + width = len + 2; + + const countLF = function(p) { + // Count the number of LFs from 0 up to p + const lineLength = width * lineSize; + return (p / lineLength | 0) - (p >= lineLength && p % lineLength === 0); + }; + + pos[0].start = pos[0].start === 0 ? 0 : Math.round((pos[0].start - countLF(pos[0].start)) / width); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil((pos[0].end - countLF(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..9af09f24 --- /dev/null +++ b/src/core/operations/ToHexContent.mjs @@ -0,0 +1,83 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {toHex} from "../lib/Hex.mjs"; + +/** + * 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 = "ArrayBuffer"; + 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 {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + const convert = args[0]; + const spaces = args[1]; + if (convert === "All chars") { + 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..a52b0451 --- /dev/null +++ b/src/core/operations/ToHexdump.mjs @@ -0,0 +1,195 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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.

The 'UNIX format' argument defines which subset of printable characters are displayed in the preview column."; + this.infoURL = "https://wikipedia.org/wiki/Hex_dump"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Width", + "type": "number", + "value": 16, + "min": 1 + }, + { + "name": "Upper case hex", + "type": "boolean", + "value": false + }, + { + "name": "Include final length", + "type": "boolean", + "value": false + }, + { + "name": "UNIX format", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const data = new Uint8Array(input); + const [length, upperCase, includeFinalLength, unixFormat] = args; + const padding = 2; + + if (length < 1 || Math.round(length) !== length) + throw new OperationError("Width must be a positive integer"); + + const lines = []; + for (let i = 0; i < data.length; i += length) { + let lineNo = Utils.hex(i, 8); + + const buff = data.slice(i, i+length); + const hex = []; + buff.forEach(b => hex.push(Utils.hex(b, padding))); + let hexStr = hex.join(" ").padEnd(length*(padding+1), " "); + + const ascii = Utils.printable(Utils.byteArrayToChars(buff), false, unixFormat); + const asciiStr = ascii.padEnd(buff.length, " "); + + if (upperCase) { + hexStr = hexStr.toUpperCase(); + lineNo = lineNo.toUpperCase(); + } + + lines.push(`${lineNo} ${hexStr} |${asciiStr}|`); + + + if (includeFinalLength && i+buff.length === data.length) { + lines.push(Utils.hex(i+buff.length, 8)); + } + } + + return lines.join("\n"); + } + + /** + * 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..27a8ecac --- /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.js"; +import Operation from "../Operation.mjs"; +import { replaceVariableNames } from "../lib/Code.mjs"; + +/** + * 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..82a99a48 --- /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.mjs"; + +/** + * 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..b56e408d --- /dev/null +++ b/src/core/operations/ToMessagePack.mjs @@ -0,0 +1,53 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import notepack from "notepack.io"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * 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 (isWorkerEnvironment()) { + 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/ToModhex.mjs b/src/core/operations/ToModhex.mjs new file mode 100644 index 00000000..6d91fb5d --- /dev/null +++ b/src/core/operations/ToModhex.mjs @@ -0,0 +1,55 @@ +/** + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { TO_MODHEX_DELIM_OPTIONS, toModhex } from "../lib/Modhex.mjs"; +import Utils from "../Utils.mjs"; + +/** + * To Modhex operation + */ +class ToModhex extends Operation { + + /** + * ToModhex constructor + */ + constructor() { + super(); + + this.name = "To Modhex"; + this.module = "Default"; + this.description = "Converts the input string to modhex bytes separated by the specified delimiter."; + this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: TO_MODHEX_DELIM_OPTIONS + }, + { + name: "Bytes per line", + type: "number", + value: 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]); + const lineSize = args[1]; + + return toModhex(new Uint8Array(input), delim, 2, "", lineSize); + } +} + +export default ToModhex; diff --git a/src/core/operations/ToMorseCode.mjs b/src/core/operations/ToMorseCode.mjs new file mode 100644 index 00000000..ce3c6b5c --- /dev/null +++ b/src/core/operations/ToMorseCode.mjs @@ -0,0 +1,155 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * 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..2ed8ef97 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + + +/** + * 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..0e579079 --- /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.mjs"; +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 56% rename from src/core/operations/QuotedPrintable.js rename to src/core/operations/ToQuotedPrintable.mjs index 4a41e3ad..9db5c5a5 --- a/src/core/operations/QuotedPrintable.js +++ b/src/core/operations/ToQuotedPrintable.mjs @@ -1,46 +1,43 @@ -/** @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.mjs"; + +/** + * To Quoted Printable operation + */ +class ToQuotedPrintable extends Operation { /** - * To Quoted Printable operation. - * - * @param {byteArray} input + * 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 = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ - runTo: function (input, args) { - var mimeEncodedStr = QuotedPrintable.mimeEncode(input); + run(input, args) { + input = new Uint8Array(input); + let mimeEncodedStr = this.mimeEncode(input); // fix line breaks mimeEncodedStr = mimeEncodedStr.replace(/\r?\n|\r/g, function() { @@ -49,71 +46,52 @@ 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) { - var 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) { - var encodedBytesCount = (str.match(/\=[\da-fA-F]{2}/g) || []).length, - bufferLength = str.length - encodedBytesCount * 2, - chr, hex, - buffer = new Array(bufferLength), - bufferPos = 0; - - for (var 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. * - * @param {byteArray} buffer + * @param {byteArray|Uint8Array} buffer * @returns {string} */ - mimeEncode: function(buffer) { - var 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 (var i = 0, len = buffer.length; i < len; i++) { + for (let i = 0, len = buffer.length; i < len; i++) { if (this._checkRanges(buffer[i], ranges)) { result += String.fromCharCode(buffer[i]); continue; @@ -122,8 +100,7 @@ const QuotedPrintable = { } return result; - }, - + } /** * Checks if a given number falls within a given set of ranges. @@ -131,10 +108,10 @@ const QuotedPrintable = { * @private * @param {number} nr * @param {byteArray[]} ranges - * @returns {bolean} + * @returns {boolean} */ - _checkRanges: function(nr, ranges) { - for (var i = ranges.length - 1; i >= 0; i--) { + _checkRanges(nr, ranges) { + for (let i = ranges.length - 1; i >= 0; i--) { if (!ranges[i].length) continue; if (ranges[i].length === 1 && nr === ranges[i][0]) @@ -143,8 +120,7 @@ const QuotedPrintable = { return true; } return false; - }, - + } /** * Adds soft line breaks to a string. @@ -156,8 +132,8 @@ const QuotedPrintable = { * @param {string} encoding * @returns {string} */ - _addSoftLinebreaks: function(str, encoding) { - var lineLengthMax = 76; + _addSoftLinebreaks(str, encoding) { + const lineLengthMax = 76; encoding = (encoding || "base64").toString().toLowerCase().trim(); @@ -166,8 +142,7 @@ const QuotedPrintable = { } else { return this._addBase64SoftLinebreaks(str, lineLengthMax); } - }, - + } /** * Adds soft line breaks to a base64 string. @@ -177,11 +152,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 +165,11 @@ const QuotedPrintable = { * @param {number} lineLengthMax * @returns {string} */ - _addQPSoftLinebreaks: function(mimeEncodedStr, lineLengthMax) { - var pos = 0, - len = mimeEncodedStr.length, + _addQPSoftLinebreaks(mimeEncodedStr, lineLengthMax) { + const len = mimeEncodedStr.length, + lineMargin = Math.floor(lineLengthMax / 3); + let pos = 0, match, code, line, - lineMargin = Math.floor(lineLengthMax / 3), result = ""; // insert soft linebreaks where needed @@ -219,21 +193,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 +224,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 +239,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..5cb566af --- /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.js"; +import Operation from "../Operation.mjs"; +import { replaceVariableNames } from "../lib/Code.mjs"; + +/** + * 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..082c39bc --- /dev/null +++ b/src/core/operations/ToTable.mjs @@ -0,0 +1,246 @@ +/** + * @author Mark Jones [github.com/justanothermark] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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, ASCII or Markdown 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", "Markdown"] + } + ]; + } + + /** + * @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": + return htmlOutput(tableData); + case "Markdown": + return markdownOutput(tableData); + 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; + } + } + + /** + * Outputs an array of data as a Markdown table. + * + * @param {string[][]} tableData + * @returns {string} + */ + function markdownOutput(tableData) { + const headerDivider = "-"; + const verticalBorder = "|"; + + 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; + } + }); + }); + + // Ignoring the checkbox, as current Mardown renderer in CF doesn't handle table without headers + const row = tableData.shift(); + output += outputRow(row, longestCells); + let rowOutput = verticalBorder; + row.forEach(function(cell, index) { + rowOutput += " " + headerDivider.repeat(longestCells[index]) + " " + verticalBorder; + }); + output += rowOutput += "\n"; + + // Add the rest of the table rows. + tableData.forEach(function(row, index) { + output += outputRow(row, 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; + } + + } + + } + +} + +export default ToTable; diff --git a/src/core/operations/ToUNIXTimestamp.mjs b/src/core/operations/ToUNIXTimestamp.mjs new file mode 100644 index 00000000..3eaf2ba2 --- /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.mjs"; +import moment from "moment-timezone"; +import {UNITS} from "../lib/DateTime.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..8eb00731 --- /dev/null +++ b/src/core/operations/ToUpperCase.mjs @@ -0,0 +1,95 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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) { + if (!args || args.length === 0) { + throw new OperationError("No capitalization scope was provided."); + } + + const scope = args[0]; + + if (scope === "All") { + return input.toUpperCase(); + } + + const scopeRegex = { + "Word": /(\b\w)/gi, + "Sentence": /(?:\.|^)\s*(\b\w)/gi, + "Paragraph": /(?:\n|^)\s*(\b\w)/gi + }[scope]; + + if (scopeRegex === undefined) { + throw new OperationError("Unrecognized capitalization scope"); + } + + // Use the regex to capitalize the input + return input.replace(scopeRegex, function(m) { + return m.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..70c76ac9 --- /dev/null +++ b/src/core/operations/TranslateDateTimeFormat.mjs @@ -0,0 +1,93 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import moment from "moment-timezone"; +import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime.mjs"; + +/** + * 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 = "string"; + this.presentType = "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()) + } + ]; + + this.invalidFormatMessage = "Invalid format."; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, inputTimezone, outputFormat, outputTimezone] = args.slice(1); + let date; + + try { + date = moment.tz(input, inputFormat, inputTimezone); + if (!date || date.format() === "Invalid date") throw Error; + } catch (err) { + return this.invalidFormatMessage; + } + + return date.tz(outputTimezone).format(outputFormat.replace(/[<>]/g, "")); + } + + /** + * @param {string} data + * @returns {html} + */ + present(data) { + if (data === this.invalidFormatMessage) { + return `${data}\n\n${FORMAT_EXAMPLES}`; + } + return Utils.escapeHtml(data); + } +} + +export default TranslateDateTimeFormat; diff --git a/src/core/operations/TripleDESDecrypt.mjs b/src/core/operations/TripleDESDecrypt.mjs new file mode 100644 index 00000000..927600de --- /dev/null +++ b/src/core/operations/TripleDESDecrypt.mjs @@ -0,0 +1,110 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; + +/** + * 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).

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 as a default."; + 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", "CBC/NoPadding", "ECB/NoPadding"] + }, + { + "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].substring(0, 3), + noPadding = args[2].endsWith("NoPadding"), + inputType = args[3], + outputType = args[4]; + + if (key.length !== 24 && key.length !== 16) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +Triple DES uses a key length of 24 bytes (192 bits).`); + } + if (iv.length !== 8 && mode !== "ECB") { + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +Triple DES uses an IV length of 8 bytes (64 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = forge.cipher.createDecipher("3DES-" + mode, + key.length === 16 ? key + key.substring(0, 8) : key); + + /* Allow for a "no padding" mode */ + if (noPadding) { + decipher.mode.unpad = function(output, options) { + return true; + }; + } + + 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..b4a218d0 --- /dev/null +++ b/src/core/operations/TripleDESEncrypt.mjs @@ -0,0 +1,97 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; + +/** + * 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).

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 && key.length !== 16) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +Triple DES uses a key length of 24 bytes (192 bits).`); + } + if (iv.length !== 8 && mode !== "ECB") { + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +Triple DES uses an IV length of 8 bytes (64 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + } + + input = Utils.convertToByteString(input, inputType); + + const cipher = forge.cipher.createCipher("3DES-" + mode, + key.length === 16 ? key + key.substring(0, 8) : 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/Typex.mjs b/src/core/operations/Typex.mjs new file mode 100644 index 00000000..5c9d952e --- /dev/null +++ b/src/core/operations/Typex.mjs @@ -0,0 +1,253 @@ +/** + * Emulation of the Typex machine. + * + * Tested against a genuine Typex machine using a variety of inputs + * and settings to confirm correctness. + * + * @author s2224834 + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {LETTERS, Reflector} from "../lib/Enigma.mjs"; +import {ROTORS, REFLECTORS, TypexMachine, Plugboard, Rotor} from "../lib/Typex.mjs"; + +/** + * Typex operation + */ +class Typex extends Operation { + /** + * Typex constructor + */ + constructor() { + super(); + + this.name = "Typex"; + this.module = "Bletchley"; + this.description = "Encipher/decipher with the WW2 Typex machine.

Typex was originally built by the British Royal Air Force prior to WW2, and is based on the Enigma machine with some improvements made, including using five rotors with more stepping points and interchangeable wiring cores. It was used across the British and Commonwealth militaries. A number of later variants were produced; here we simulate a WW2 era Mark 22 Typex with plugboards for the reflector and input. Typex rotors were changed regularly and none are public: a random example set are provided.

To configure the reflector plugboard, enter a string of connected pairs of letters in the reflector box, e.g. AB CD EF connects A to B, C to D, and E to F (you'll need to connect every letter). There is also an input plugboard: unlike Enigma's plugboard, it's not restricted to pairs, so it's entered like a rotor (without stepping). To create your own rotor, enter the letters that the rotor maps A to Z to, in order, optionally followed by < then a list of stepping points.

More detailed descriptions of the Enigma, Typex and Bombe operations can be found here."; + this.infoURL = "https://wikipedia.org/wiki/Typex"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "1st (left-hand) rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 0 + }, + { + name: "1st rotor reversed", + type: "boolean", + value: false + }, + { + name: "1st rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "1st rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "2nd rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 1 + }, + { + name: "2nd rotor reversed", + type: "boolean", + value: false + }, + { + name: "2nd rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "2nd rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "3rd (middle) rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 2 + }, + { + name: "3rd rotor reversed", + type: "boolean", + value: false + }, + { + name: "3rd rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "3rd rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "4th (static) rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 3 + }, + { + name: "4th rotor reversed", + type: "boolean", + value: false + }, + { + name: "4th rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "4th rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "5th (right-hand, static) rotor", + type: "editableOption", + value: ROTORS, + defaultIndex: 4 + }, + { + name: "5th rotor reversed", + type: "boolean", + value: false + }, + { + name: "5th rotor ring setting", + type: "option", + value: LETTERS + }, + { + name: "5th rotor initial value", + type: "option", + value: LETTERS + }, + { + name: "Reflector", + type: "editableOption", + value: REFLECTORS + }, + { + name: "Plugboard", + type: "string", + value: "" + }, + { + name: "Typex keyboard emulation", + type: "option", + value: ["None", "Encrypt", "Decrypt"] + }, + { + name: "Strict output", + hint: "Remove non-alphabet letters and group output", + type: "boolean", + value: true + }, + ]; + } + + /** + * Helper - for ease of use rotors are specified as a single string; this + * method breaks the spec string into wiring and steps parts. + * + * @param {string} rotor - Rotor specification string. + * @param {number} i - For error messages, the number of this rotor. + * @returns {string[]} + */ + parseRotorStr(rotor, i) { + if (rotor === "") { + throw new OperationError(`Rotor ${i} must be provided.`); + } + if (!rotor.includes("<")) { + return [rotor, ""]; + } + return rotor.split("<", 2); + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const reflectorstr = args[20]; + const plugboardstr = args[21]; + const typexKeyboard = args[22]; + const removeOther = args[23]; + const rotors = []; + for (let i=0; i<5; i++) { + const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*4]); + rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*4 + 1], args[i*4+2], args[i*4+3])); + } + // Rotors are handled in reverse + rotors.reverse(); + const reflector = new Reflector(reflectorstr); + let plugboardstrMod = plugboardstr; + if (plugboardstrMod === "") { + plugboardstrMod = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + } + const plugboard = new Plugboard(plugboardstrMod); + if (removeOther) { + if (typexKeyboard === "Encrypt") { + input = input.replace(/[^A-Za-z0-9 /%£()',.-]/g, ""); + } else { + input = input.replace(/[^A-Za-z]/g, ""); + } + } + const typex = new TypexMachine(rotors, reflector, plugboard, typexKeyboard); + let result = typex.crypt(input); + if (removeOther && typexKeyboard !== "Decrypt") { + // Five character cipher groups is traditional + result = result.replace(/([A-Z]{5})(?!$)/g, "$1 "); + } + return result; + } + + /** + * Highlight Typex + * This is only possible if we're passing through non-alphabet characters. + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + if (args[18] === false) { + return pos; + } + } + + /** + * Highlight Typex in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + if (args[18] === false) { + return pos; + } + } + +} + +export default Typex; diff --git a/src/core/operations/UNIXTimestampToWindowsFiletime.mjs b/src/core/operations/UNIXTimestampToWindowsFiletime.mjs new file mode 100644 index 00000000..ad88fb97 --- /dev/null +++ b/src/core/operations/UNIXTimestampToWindowsFiletime.mjs @@ -0,0 +1,93 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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 (big endian)", "Hex (little endian)"] + } + ]; + } + + /** + * @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.multipliedBy(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")); + + let result; + if (format.startsWith("Hex")) { + result = input.toString(16); + } else { + result = input.toFixed(); + } + + if (format === "Hex (little endian)") { + // Swap endianness + let flipped = ""; + for (let i = result.length - 2; i >= 0; i -= 2) { + flipped += result.charAt(i); + flipped += result.charAt(i + 1); + } + if (result.length % 2 !== 0) { + flipped += "0" + result.charAt(0); + } + result = flipped; + } + + return result; + } + +} + +export default UNIXTimestampToWindowsFiletime; diff --git a/src/core/operations/URL.js b/src/core/operations/URL.js deleted file mode 100755 index c7f68f4d..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) { - var 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) { - var 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."; - } - - var 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) { - var 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) { - var 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"; - var args_ = (a.search.slice(1, a.search.length)).split("&"); - var splitArgs = [], padding = 0; - for (var 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..7d6544ac --- /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.mjs"; + +/** + * 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.checks = [ + { + pattern: ".*(?:%[\\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..a5efd213 --- /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.mjs"; + +/** + * 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 programmatically + 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 9302a350..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") { - var buf = new Uint32Array(4), - i = 0; - window.crypto.getRandomValues(buf); - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - var 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) { - var 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..8fafb446 --- /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.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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)
  • \\nnn (Octal, where n is 0-7)
  • \\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..5bb0e5ac --- /dev/null +++ b/src/core/operations/UnescapeUnicodeCharacters.mjs @@ -0,0 +1,74 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * 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); + + // 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 4b67f4ef..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) { - var 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/UnicodeTextFormat.mjs b/src/core/operations/UnicodeTextFormat.mjs new file mode 100644 index 00000000..2c18848f --- /dev/null +++ b/src/core/operations/UnicodeTextFormat.mjs @@ -0,0 +1,67 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * Unicode Text Format operation + */ +class UnicodeTextFormat extends Operation { + + /** + * UnicodeTextFormat constructor + */ + constructor() { + super(); + + this.name = "Unicode Text Format"; + this.module = "Default"; + this.description = "Adds Unicode combining characters to change formatting of plaintext."; + this.infoURL = "https://wikipedia.org/wiki/Combining_character"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Underline", + type: "boolean", + value: "false" + }, + { + name: "Strikethrough", + type: "boolean", + value: "false" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const [underline, strikethrough] = args; + let output = input.map(char => [char]); + if (strikethrough) { + output = output.map(charFormat => { + charFormat.push(...Utils.strToUtf8ByteArray("\u0336")); + return charFormat; + }); + } + if (underline) { + output = output.map(charFormat => { + charFormat.push(...Utils.strToUtf8ByteArray("\u0332")); + return charFormat; + }); + } + // return output.flat(); - Not supported in Node 10, polyfilled + return [].concat(...output); + } + +} + +export default UnicodeTextFormat; diff --git a/src/core/operations/Unique.mjs b/src/core/operations/Unique.mjs new file mode 100644 index 00000000..7ca2db66 --- /dev/null +++ b/src/core/operations/Unique.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * 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 + }, + { + name: "Display count", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + count = args[1]; + + if (count) { + const valMap = input.split(delim).reduce((acc, curr) => { + if (Object.prototype.hasOwnProperty.call(acc, curr)) { + acc[curr]++; + } else { + acc[curr] = 1; + } + return acc; + }, {}); + + return Object.keys(valMap).map(val => `${valMap[val]} ${val}`).join(delim); + } else { + 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..4e6af250 --- /dev/null +++ b/src/core/operations/Untar.mjs @@ -0,0 +1,109 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; + +/** + * 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 = "ArrayBuffer"; + this.outputType = "List"; + this.presentType = "html"; + this.args = []; + this.checks = [ + { + "pattern": "^.{257}\\x75\\x73\\x74\\x61\\x72", + "flags": "", + "args": [] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {List} + */ + run(input, args) { + input = new Uint8Array(input); + 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 * parseInt(stream.readString(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..2e8be411 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import unzip from "zlibjs/bin/unzip.min.js"; + +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.checks = [ + { + pattern: "^\\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/VarIntDecode.mjs b/src/core/operations/VarIntDecode.mjs new file mode 100644 index 00000000..46c99cb7 --- /dev/null +++ b/src/core/operations/VarIntDecode.mjs @@ -0,0 +1,46 @@ +/** + * @author GCHQ Contributor [3] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Protobuf from "../lib/Protobuf.mjs"; + +/** + * VarInt Decode operation + */ +class VarIntDecode extends Operation { + + /** + * VarIntDecode constructor + */ + constructor() { + super(); + + this.name = "VarInt Decode"; + this.module = "Default"; + this.description = "Decodes a VarInt encoded integer. VarInt is an efficient way of encoding variable length integers and is commonly used with Protobuf."; + this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding#varints"; + this.inputType = "byteArray"; + this.outputType = "number"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + try { + return Protobuf.varIntDecode(input); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default VarIntDecode; diff --git a/src/core/operations/VarIntEncode.mjs b/src/core/operations/VarIntEncode.mjs new file mode 100644 index 00000000..d86d33f5 --- /dev/null +++ b/src/core/operations/VarIntEncode.mjs @@ -0,0 +1,46 @@ +/** + * @author GCHQ Contributor [3] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Protobuf from "../lib/Protobuf.mjs"; + +/** + * VarInt Encode operation + */ +class VarIntEncode extends Operation { + + /** + * VarIntEncode constructor + */ + constructor() { + super(); + + this.name = "VarInt Encode"; + this.module = "Default"; + this.description = "Encodes a Vn integer as a VarInt. VarInt is an efficient way of encoding variable length integers and is commonly used with Protobuf."; + this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding#varints"; + this.inputType = "number"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {number} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + try { + return Protobuf.varIntEncode(input); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default VarIntEncode; diff --git a/src/core/operations/ViewBitPlane.mjs b/src/core/operations/ViewBitPlane.mjs new file mode 100644 index 00000000..8c93f16c --- /dev/null +++ b/src/core/operations/ViewBitPlane.mjs @@ -0,0 +1,107 @@ +/** + * @author Ge0rg3 [georgeomnet+cyberchef@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { isImage } from "../lib/FileType.mjs"; +import { toBase64 } from "../lib/Base64.mjs"; +import Jimp from "jimp/es/index.js"; + +/** + * View Bit Plane operation + */ +class ViewBitPlane extends Operation { + + /** + * ViewBitPlane constructor + */ + constructor() { + super(); + + this.name = "View Bit Plane"; + this.module = "Image"; + this.description = "Extracts and displays a bit plane of any given image. These show only a single bit from each pixel, and can be used to hide messages in Steganography."; + this.infoURL = "https://wikipedia.org/wiki/Bit_plane"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.presentType = "html"; + this.args = [ + { + name: "Colour", + type: "option", + value: COLOUR_OPTIONS + }, + { + name: "Bit", + type: "number", + value: 0 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + if (!isImage(input)) throw new OperationError("Please enter a valid image file."); + + const [colour, bit] = args, + parsedImage = await Jimp.read(input), + width = parsedImage.bitmap.width, + height = parsedImage.bitmap.height, + colourIndex = COLOUR_OPTIONS.indexOf(colour), + bitIndex = 7-bit; + + if (bit < 0 || bit > 7) { + throw new OperationError("Error: Bit argument must be between 0 and 7"); + } + + let pixel, bin, newPixelValue; + + parsedImage.scan(0, 0, width, height, function(x, y, idx) { + pixel = this.bitmap.data[idx + colourIndex]; + bin = Utils.bin(pixel); + newPixelValue = 255; + + if (bin.charAt(bitIndex) === "1") newPixelValue = 0; + + for (let i=0; i < 3; i++) { + this.bitmap.data[idx + i] = newPixelValue; + } + this.bitmap.data[idx + 3] = 255; + + }); + + const imageBuffer = await parsedImage.getBufferAsync(Jimp.AUTO); + + return new Uint8Array(imageBuffer).buffer; + } + + /** + * Displays the extracted data as an image for web apps. + * @param {ArrayBuffer} data + * @returns {html} + */ + present(data) { + if (!data.byteLength) return ""; + const type = isImage(data); + + return ``; + } + +} + +const COLOUR_OPTIONS = [ + "Red", + "Green", + "Blue", + "Alpha" +]; + +export default ViewBitPlane; diff --git a/src/core/operations/VigenèreDecode.mjs b/src/core/operations/VigenèreDecode.mjs new file mode 100644 index 00000000..8abaeed8 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; +/** + * 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 necessary + 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..db212fe2 --- /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.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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..c2364912 --- /dev/null +++ b/src/core/operations/Whirlpool.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * 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 diffusion 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"] + }, + { + name: "Rounds", + type: "number", + value: 10, + min: 1, + max: 10 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const variant = args[0].toLowerCase(); + return runHash(variant, input, {rounds: args[1]}); + } + +} + +export default Whirlpool; diff --git a/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs b/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs new file mode 100644 index 00000000..786cbcce --- /dev/null +++ b/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs @@ -0,0 +1,90 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * 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 (big endian)", "Hex (little endian)"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [units, format] = args; + + if (!input) return ""; + + if (format === "Hex (little endian)") { + // Swap endianness + let result = ""; + if (input.length % 2 !== 0) { + result += input.charAt(input.length - 1); + } + for (let i = input.length - input.length % 2 - 2; i >= 0; i -= 2) { + result += input.charAt(i); + result += input.charAt(i + 1); + } + input = result; + } + + if (format.startsWith("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..ba9b5e27 --- /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.mjs"; + +/** + * 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..2cabb9b2 --- /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.mjs"; + +/** + * 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..5c710951 --- /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.mjs"; + +/** + * 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..aa228842 --- /dev/null +++ b/src/core/operations/XOR.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { bitOp, xor, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs"; + +/** + * 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 = "ArrayBuffer"; + 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 {ArrayBuffer} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + input = new Uint8Array(input); + const key = Utils.convertToByteArray(args[0].string || "", args[0].option), + [, scheme, nullPreserving] = args; + + 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..8c097731 --- /dev/null +++ b/src/core/operations/XORBruteForce.mjs @@ -0,0 +1,139 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { bitOp, xor } from "../lib/BitwiseOp.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * 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 = "ArrayBuffer"; + 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 {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + input = new Uint8Array(input); + 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 (isWorkerEnvironment()) + 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 && isWorkerEnvironment()) { + 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)) + ": "; + record += outputHex ? toHex(result) : Utils.escapeWhitespace(resultUtf8); + + 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..768247cb --- /dev/null +++ b/src/core/operations/XPathExpression.mjs @@ -0,0 +1,80 @@ +/** + * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import xmldom from "@xmldom/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({ + errorHandler: { + fatalError(e) { + throw e; + } + } + }).parseFromString(input, "application/xml"); + } catch (err) { + throw new OperationError("Invalid input XML."); + } + + let nodes; + try { + nodes = xpath.parse(query).select({ node: doc, allowAnyNamespaceForNoPrefix: true }); + } 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/XSalsa20.mjs b/src/core/operations/XSalsa20.mjs new file mode 100644 index 00000000..d289585b --- /dev/null +++ b/src/core/operations/XSalsa20.mjs @@ -0,0 +1,156 @@ +/** + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { salsa20Block, hsalsa20 } from "../lib/Salsa20.mjs"; + +/** + * XSalsa20 operation + */ +class XSalsa20 extends Operation { + + /** + * XSalsa20 constructor + */ + constructor() { + super(); + + this.name = "XSalsa20"; + this.module = "Ciphers"; + this.description = "XSalsa20 is a variant of the Salsa20 stream cipher designed by Daniel J. Bernstein; XSalsa uses longer nonces.

Key: XSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).

Nonce: XSalsa20 uses a nonce of 24 bytes (192 bits).

Counter: XSalsa uses a counter of 8 bytes (64 bits). The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes."; + this.infoURL = "https://en.wikipedia.org/wiki/Salsa20#XSalsa20_with_192-bit_nonce"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Nonce", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"] + }, + { + "name": "Counter", + "type": "number", + "value": 0, + "min": 0 + }, + { + "name": "Rounds", + "type": "option", + "value": ["20", "12", "8"] + }, + { + "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.convertToByteArray(args[0].string, args[0].option), + nonceType = args[1].option, + rounds = parseInt(args[3], 10), + inputType = args[4], + outputType = args[5]; + + if (key.length !== 16 && key.length !== 32) { + throw new OperationError(`Invalid key length: ${key.length} bytes. + +XSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`); + } + + let counter, nonce; + if (nonceType === "Integer") { + nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little"); + } else { + nonce = Utils.convertToByteArray(args[1].string, args[1].option); + if (!(nonce.length === 24)) { + throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. + +XSalsa20 uses a nonce of 24 bytes (192 bits).`); + } + } + counter = Utils.intToByteArray(args[2], 8, "little"); + + const xsalsaKey = hsalsa20(key, nonce.slice(0, 16), rounds); + + const output = []; + input = Utils.convertToByteArray(input, inputType); + + let counterAsInt = Utils.byteArrayToInt(counter, "little"); + for (let i = 0; i < input.length; i += 64) { + counter = Utils.intToByteArray(counterAsInt, 8, "little"); + const stream = salsa20Block(xsalsaKey, nonce.slice(16, 24), counter, rounds); + for (let j = 0; j < 64 && i + j < input.length; j++) { + output.push(input[i + j] ^ stream[j]); + } + counterAsInt++; + } + + if (outputType === "Hex") { + return toHex(output); + } else { + return Utils.arrayBufferToStr(Uint8Array.from(output).buffer); + } + } + + /** + * Highlight XSalsa20 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + + /** + * Highlight XSalsa20 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + +} + +export default XSalsa20; diff --git a/src/core/operations/XXTEADecrypt.mjs b/src/core/operations/XXTEADecrypt.mjs new file mode 100644 index 00000000..496e5409 --- /dev/null +++ b/src/core/operations/XXTEADecrypt.mjs @@ -0,0 +1,57 @@ +/** + * @author devcydo [devcydo@gmail.com] + * @author Ma Bingyao [mabingyao@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {decrypt} from "../lib/XXTEA.mjs"; + +/** + * XXTEA Decrypt operation + */ +class XXTEADecrypt extends Operation { + + /** + * XXTEADecrypt constructor + */ + constructor() { + super(); + + this.name = "XXTEA Decrypt"; + this.module = "Ciphers"; + this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block."; + this.infoURL = "https://wikipedia.org/wiki/XXTEA"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option)); + try { + return decrypt(new Uint8Array(input), key).buffer; + } catch (err) { + throw new OperationError("Unable to decrypt using this key"); + } + } + +} + +export default XXTEADecrypt; diff --git a/src/core/operations/XXTEAEncrypt.mjs b/src/core/operations/XXTEAEncrypt.mjs new file mode 100644 index 00000000..85379de0 --- /dev/null +++ b/src/core/operations/XXTEAEncrypt.mjs @@ -0,0 +1,52 @@ +/** + * @author devcydo [devcydo@gmail.com] + * @author Ma Bingyao [mabingyao@gmail.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {encrypt} from "../lib/XXTEA.mjs"; + +/** + * XXTEA Encrypt operation + */ +class XXTEAEncrypt extends Operation { + + /** + * XXTEAEncrypt constructor + */ + constructor() { + super(); + + this.name = "XXTEA Encrypt"; + this.module = "Ciphers"; + this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block."; + this.infoURL = "https://wikipedia.org/wiki/XXTEA"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option)); + return encrypt(new Uint8Array(input), key).buffer; + } + +} + +export default XXTEAEncrypt; diff --git a/src/core/operations/YAMLToJSON.mjs b/src/core/operations/YAMLToJSON.mjs new file mode 100644 index 00000000..5b986575 --- /dev/null +++ b/src/core/operations/YAMLToJSON.mjs @@ -0,0 +1,45 @@ +/** + * @author ccarpo [ccarpo@gmx.net] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import jsYaml from "js-yaml"; +/** + * YAML to JSON operation + */ +class YAMLToJSON extends Operation { + + /** + * YAMLToJSON constructor + */ + constructor() { + super(); + + this.name = "YAML to JSON"; + this.module = "Default"; + this.description = "Convert YAML to JSON"; + this.infoURL = "https://en.wikipedia.org/wiki/YAML"; + this.inputType = "string"; + this.outputType = "JSON"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + try { + return jsYaml.load(input); + } catch (err) { + throw new OperationError("Unable to parse YAML: " + err); + } + } + +} + +export default YAMLToJSON; diff --git a/src/core/operations/YARARules.mjs b/src/core/operations/YARARules.mjs new file mode 100644 index 00000000..d91f50ae --- /dev/null +++ b/src/core/operations/YARARules.mjs @@ -0,0 +1,141 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Yara from "libyara-wasm"; +import { isWorkerEnvironment } from "../Utils.mjs"; + +/** + * 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 + }, + { + name: "Show rule warnings", + type: "boolean", + value: true + }, + { + name: "Show console module messages", + type: "boolean", + value: true + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + if (isWorkerEnvironment()) + self.sendStatusMessage("Instantiating YARA..."); + const [rules, showStrings, showLengths, showMeta, showCounts, showRuleWarns, showConsole] = args; + return new Promise((resolve, reject) => { + Yara().then(yara => { + if (isWorkerEnvironment()) self.sendStatusMessage("Converting data for YARA."); + let matchString = ""; + + const inpArr = new Uint8Array(input); // Turns out embind knows that JS uint8array <==> C++ std::string + + if (isWorkerEnvironment()) self.sendStatusMessage("Running YARA matching."); + + const resp = yara.run(inpArr, rules); + + if (isWorkerEnvironment()) 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 if (showRuleWarns) { + matchString += `Warning on line ${compileError.lineNumber}: ${compileError.message}\n`; + } + } + } + + if (showConsole) { + const consoleLogs = resp.consoleLogs; + for (let i = 0; i < consoleLogs.size(); i++) { + matchString += consoleLogs.get(i) + "\n"; + } + } + + 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 = matches.size() === 0 ? "" : (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..dbe51462 --- /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.mjs"; +import Utils from "../Utils.mjs"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib.mjs"; +import zip from "zlibjs/bin/zip.min.js"; + +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..4436d84a --- /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.mjs"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib.mjs"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min.js"; + +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..a24d7476 --- /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.mjs"; +import {INFLATE_BUFFER_TYPE} from "../lib/Zlib.mjs"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min.js"; + +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.checks = [ + { + pattern: "^\\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/DisassembleX86-64.mjs b/src/core/vendor/DisassembleX86-64.mjs new file mode 100644 index 00000000..aeae426b --- /dev/null +++ b/src/core/vendor/DisassembleX86-64.mjs @@ -0,0 +1,5760 @@ +/*------------------------------------------------------------------------------------------------------------------------- +Created by Damian Recoskie (https://github.com/Recoskie/X86-64-Disassembler-JS) + & exported for CyberChef by Matt [me@mitt.dev] +--------------------------------------------------------------------------------------------------------------------------- +MIT License + +Copyright (c) 2019 Damian Recoskie + +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. +-------------------------------------------------------------------------------------------------------------------------*/ + + +/*------------------------------------------------------------------------------------------------------------------------- +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 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, Sign = 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. + + Sign = int32; + + //Remove the Sign 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 Sign Bit positions back though each rotation. + + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( ( Sign >> 24 ) & 0x80 ) | int32 ) & 0xFF; + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( ( Sign >> 16 ) & 0x80 ) | int32 ) & 0xFF; + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( ( Sign >> 8 ) & 0x80 ) | int32 ) & 0xFF; + int32 = ( int32 >> 24 ) | ( ( int32 << 8 ) & 0x7FFFFFFF ); + BinCode[el++] = ( ( Sign & 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. + var t; + if ( CodePos < BinCode.length ) //If not out of bounds. + { + //Convert current byte to String, and pad. + + ( ( 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 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 Sign value that is only set for Negative, or Positive Relative displacements. + + var Sign = 0; + + //*Initialize the Sign Extend variable size as 0 Some Immediate numbers Sign 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; + + //Carry bit to 64 bit section. + + var C64 = 0; + + //Relative size. + + var n = Math.min( 0x100000000, Math.pow( 2, 4 << ( S + 1 ) ) ); + + //Sign bit adjust. + + if( V32 >= ( n / 2 ) ) { V32 -= n; } + + //Add position. + + V32 += Pos32; + + //Remove carry bit and add it to C64. + + ( C64 = ( ( V32 ) >= 0x100000000 ) ) && ( V32 -= 0x100000000 ); + + //Do not carry to 64 if address is 32, and below. + + if ( S <= 2 ) { C64 = false; } + + //Add the 64 bit position plus carry. + + ( ( 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 Sign is Positive. + + Sign = 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 Sign is negative. + + Sign = 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 ( ( Sign > 0 ? ( Sign > 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/vendor/gost/gostCipher.mjs b/src/core/vendor/gost/gostCipher.mjs new file mode 100644 index 00000000..8505fd34 --- /dev/null +++ b/src/core/vendor/gost/gostCipher.mjs @@ -0,0 +1,2259 @@ +/** + * GOST 28147-89/GOST R 34.12-2015/GOST R 32.13-2015 Encryption Algorithm + * 1.76 + * 2014-2016, Rudolf Nickolaev. All rights reserved. + * + * Exported for CyberChef by mshwed [m@ttshwed.com] + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 HOLDER 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. + * + */ + +import GostRandom from './gostRandom.mjs'; + +import crypto from 'crypto' + +/* +* Initial parameters and common algortithms of GOST 28147-89 +* +* http://tools.ietf.org/html/rfc5830 +* +*/ // + +var root = {}; +var rootCrypto = crypto; +var CryptoOperationData = ArrayBuffer; +var SyntaxError = Error, + DataError = Error, + NotSupportedError = Error; +/* +* Check supported +* This implementation support only Little Endian arhitecture +*/ + +var littleEndian = (function () { + var buffer = new CryptoOperationData(2); + new DataView(buffer).setInt16(0, 256, true); + return new Int16Array(buffer)[0] === 256; +})(); + +// Default initial vector +var defaultIV = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]); + +// Predefined sBox collection +var sBoxes = { + 'E-TEST': [ + 0x4, 0x2, 0xF, 0x5, 0x9, 0x1, 0x0, 0x8, 0xE, 0x3, 0xB, 0xC, 0xD, 0x7, 0xA, 0x6, + 0xC, 0x9, 0xF, 0xE, 0x8, 0x1, 0x3, 0xA, 0x2, 0x7, 0x4, 0xD, 0x6, 0x0, 0xB, 0x5, + 0xD, 0x8, 0xE, 0xC, 0x7, 0x3, 0x9, 0xA, 0x1, 0x5, 0x2, 0x4, 0x6, 0xF, 0x0, 0xB, + 0xE, 0x9, 0xB, 0x2, 0x5, 0xF, 0x7, 0x1, 0x0, 0xD, 0xC, 0x6, 0xA, 0x4, 0x3, 0x8, + 0x3, 0xE, 0x5, 0x9, 0x6, 0x8, 0x0, 0xD, 0xA, 0xB, 0x7, 0xC, 0x2, 0x1, 0xF, 0x4, + 0x8, 0xF, 0x6, 0xB, 0x1, 0x9, 0xC, 0x5, 0xD, 0x3, 0x7, 0xA, 0x0, 0xE, 0x2, 0x4, + 0x9, 0xB, 0xC, 0x0, 0x3, 0x6, 0x7, 0x5, 0x4, 0x8, 0xE, 0xF, 0x1, 0xA, 0x2, 0xD, + 0xC, 0x6, 0x5, 0x2, 0xB, 0x0, 0x9, 0xD, 0x3, 0xE, 0x7, 0xA, 0xF, 0x4, 0x1, 0x8 + ], + 'E-A': [ + 0x9, 0x6, 0x3, 0x2, 0x8, 0xB, 0x1, 0x7, 0xA, 0x4, 0xE, 0xF, 0xC, 0x0, 0xD, 0x5, + 0x3, 0x7, 0xE, 0x9, 0x8, 0xA, 0xF, 0x0, 0x5, 0x2, 0x6, 0xC, 0xB, 0x4, 0xD, 0x1, + 0xE, 0x4, 0x6, 0x2, 0xB, 0x3, 0xD, 0x8, 0xC, 0xF, 0x5, 0xA, 0x0, 0x7, 0x1, 0x9, + 0xE, 0x7, 0xA, 0xC, 0xD, 0x1, 0x3, 0x9, 0x0, 0x2, 0xB, 0x4, 0xF, 0x8, 0x5, 0x6, + 0xB, 0x5, 0x1, 0x9, 0x8, 0xD, 0xF, 0x0, 0xE, 0x4, 0x2, 0x3, 0xC, 0x7, 0xA, 0x6, + 0x3, 0xA, 0xD, 0xC, 0x1, 0x2, 0x0, 0xB, 0x7, 0x5, 0x9, 0x4, 0x8, 0xF, 0xE, 0x6, + 0x1, 0xD, 0x2, 0x9, 0x7, 0xA, 0x6, 0x0, 0x8, 0xC, 0x4, 0x5, 0xF, 0x3, 0xB, 0xE, + 0xB, 0xA, 0xF, 0x5, 0x0, 0xC, 0xE, 0x8, 0x6, 0x2, 0x3, 0x9, 0x1, 0x7, 0xD, 0x4 + ], + 'E-B': [ + 0x8, 0x4, 0xB, 0x1, 0x3, 0x5, 0x0, 0x9, 0x2, 0xE, 0xA, 0xC, 0xD, 0x6, 0x7, 0xF, + 0x0, 0x1, 0x2, 0xA, 0x4, 0xD, 0x5, 0xC, 0x9, 0x7, 0x3, 0xF, 0xB, 0x8, 0x6, 0xE, + 0xE, 0xC, 0x0, 0xA, 0x9, 0x2, 0xD, 0xB, 0x7, 0x5, 0x8, 0xF, 0x3, 0x6, 0x1, 0x4, + 0x7, 0x5, 0x0, 0xD, 0xB, 0x6, 0x1, 0x2, 0x3, 0xA, 0xC, 0xF, 0x4, 0xE, 0x9, 0x8, + 0x2, 0x7, 0xC, 0xF, 0x9, 0x5, 0xA, 0xB, 0x1, 0x4, 0x0, 0xD, 0x6, 0x8, 0xE, 0x3, + 0x8, 0x3, 0x2, 0x6, 0x4, 0xD, 0xE, 0xB, 0xC, 0x1, 0x7, 0xF, 0xA, 0x0, 0x9, 0x5, + 0x5, 0x2, 0xA, 0xB, 0x9, 0x1, 0xC, 0x3, 0x7, 0x4, 0xD, 0x0, 0x6, 0xF, 0x8, 0xE, + 0x0, 0x4, 0xB, 0xE, 0x8, 0x3, 0x7, 0x1, 0xA, 0x2, 0x9, 0x6, 0xF, 0xD, 0x5, 0xC + ], + 'E-C': [ + 0x1, 0xB, 0xC, 0x2, 0x9, 0xD, 0x0, 0xF, 0x4, 0x5, 0x8, 0xE, 0xA, 0x7, 0x6, 0x3, + 0x0, 0x1, 0x7, 0xD, 0xB, 0x4, 0x5, 0x2, 0x8, 0xE, 0xF, 0xC, 0x9, 0xA, 0x6, 0x3, + 0x8, 0x2, 0x5, 0x0, 0x4, 0x9, 0xF, 0xA, 0x3, 0x7, 0xC, 0xD, 0x6, 0xE, 0x1, 0xB, + 0x3, 0x6, 0x0, 0x1, 0x5, 0xD, 0xA, 0x8, 0xB, 0x2, 0x9, 0x7, 0xE, 0xF, 0xC, 0x4, + 0x8, 0xD, 0xB, 0x0, 0x4, 0x5, 0x1, 0x2, 0x9, 0x3, 0xC, 0xE, 0x6, 0xF, 0xA, 0x7, + 0xC, 0x9, 0xB, 0x1, 0x8, 0xE, 0x2, 0x4, 0x7, 0x3, 0x6, 0x5, 0xA, 0x0, 0xF, 0xD, + 0xA, 0x9, 0x6, 0x8, 0xD, 0xE, 0x2, 0x0, 0xF, 0x3, 0x5, 0xB, 0x4, 0x1, 0xC, 0x7, + 0x7, 0x4, 0x0, 0x5, 0xA, 0x2, 0xF, 0xE, 0xC, 0x6, 0x1, 0xB, 0xD, 0x9, 0x3, 0x8 + ], + 'E-D': [ + 0xF, 0xC, 0x2, 0xA, 0x6, 0x4, 0x5, 0x0, 0x7, 0x9, 0xE, 0xD, 0x1, 0xB, 0x8, 0x3, + 0xB, 0x6, 0x3, 0x4, 0xC, 0xF, 0xE, 0x2, 0x7, 0xD, 0x8, 0x0, 0x5, 0xA, 0x9, 0x1, + 0x1, 0xC, 0xB, 0x0, 0xF, 0xE, 0x6, 0x5, 0xA, 0xD, 0x4, 0x8, 0x9, 0x3, 0x7, 0x2, + 0x1, 0x5, 0xE, 0xC, 0xA, 0x7, 0x0, 0xD, 0x6, 0x2, 0xB, 0x4, 0x9, 0x3, 0xF, 0x8, + 0x0, 0xC, 0x8, 0x9, 0xD, 0x2, 0xA, 0xB, 0x7, 0x3, 0x6, 0x5, 0x4, 0xE, 0xF, 0x1, + 0x8, 0x0, 0xF, 0x3, 0x2, 0x5, 0xE, 0xB, 0x1, 0xA, 0x4, 0x7, 0xC, 0x9, 0xD, 0x6, + 0x3, 0x0, 0x6, 0xF, 0x1, 0xE, 0x9, 0x2, 0xD, 0x8, 0xC, 0x4, 0xB, 0xA, 0x5, 0x7, + 0x1, 0xA, 0x6, 0x8, 0xF, 0xB, 0x0, 0x4, 0xC, 0x3, 0x5, 0x9, 0x7, 0xD, 0x2, 0xE + ], + 'E-SC': [ + 0x3, 0x6, 0x1, 0x0, 0x5, 0x7, 0xd, 0x9, 0x4, 0xb, 0x8, 0xc, 0xe, 0xf, 0x2, 0xa, + 0x7, 0x1, 0x5, 0x2, 0x8, 0xb, 0x9, 0xc, 0xd, 0x0, 0x3, 0xa, 0xf, 0xe, 0x4, 0x6, + 0xf, 0x1, 0x4, 0x6, 0xc, 0x8, 0x9, 0x2, 0xe, 0x3, 0x7, 0xa, 0xb, 0xd, 0x5, 0x0, + 0x3, 0x4, 0xf, 0xc, 0x5, 0x9, 0xe, 0x0, 0x6, 0x8, 0x7, 0xa, 0x1, 0xb, 0xd, 0x2, + 0x6, 0x9, 0x0, 0x7, 0xb, 0x8, 0x4, 0xc, 0x2, 0xe, 0xa, 0xf, 0x1, 0xd, 0x5, 0x3, + 0x6, 0x1, 0x2, 0xf, 0x0, 0xb, 0x9, 0xc, 0x7, 0xd, 0xa, 0x5, 0x8, 0x4, 0xe, 0x3, + 0x0, 0x2, 0xe, 0xc, 0x9, 0x1, 0x4, 0x7, 0x3, 0xf, 0x6, 0x8, 0xa, 0xd, 0xb, 0x5, + 0x5, 0x2, 0xb, 0x8, 0x4, 0xc, 0x7, 0x1, 0xa, 0x6, 0xe, 0x0, 0x9, 0x3, 0xd, 0xf + ], + 'E-Z': [// This is default S-box in according to draft of new standard + 0xc, 0x4, 0x6, 0x2, 0xa, 0x5, 0xb, 0x9, 0xe, 0x8, 0xd, 0x7, 0x0, 0x3, 0xf, 0x1, + 0x6, 0x8, 0x2, 0x3, 0x9, 0xa, 0x5, 0xc, 0x1, 0xe, 0x4, 0x7, 0xb, 0xd, 0x0, 0xf, + 0xb, 0x3, 0x5, 0x8, 0x2, 0xf, 0xa, 0xd, 0xe, 0x1, 0x7, 0x4, 0xc, 0x9, 0x6, 0x0, + 0xc, 0x8, 0x2, 0x1, 0xd, 0x4, 0xf, 0x6, 0x7, 0x0, 0xa, 0x5, 0x3, 0xe, 0x9, 0xb, + 0x7, 0xf, 0x5, 0xa, 0x8, 0x1, 0x6, 0xd, 0x0, 0x9, 0x3, 0xe, 0xb, 0x4, 0x2, 0xc, + 0x5, 0xd, 0xf, 0x6, 0x9, 0x2, 0xc, 0xa, 0xb, 0x7, 0x8, 0x1, 0x4, 0x3, 0xe, 0x0, + 0x8, 0xe, 0x2, 0x5, 0x6, 0x9, 0x1, 0xc, 0xf, 0x4, 0xb, 0x0, 0xd, 0xa, 0x3, 0x7, + 0x1, 0x7, 0xe, 0xd, 0x0, 0x5, 0x8, 0x3, 0x4, 0xf, 0xa, 0x6, 0x9, 0xc, 0xb, 0x2 + ], + //S-box for digest + 'D-TEST': [ + 0x4, 0xA, 0x9, 0x2, 0xD, 0x8, 0x0, 0xE, 0x6, 0xB, 0x1, 0xC, 0x7, 0xF, 0x5, 0x3, + 0xE, 0xB, 0x4, 0xC, 0x6, 0xD, 0xF, 0xA, 0x2, 0x3, 0x8, 0x1, 0x0, 0x7, 0x5, 0x9, + 0x5, 0x8, 0x1, 0xD, 0xA, 0x3, 0x4, 0x2, 0xE, 0xF, 0xC, 0x7, 0x6, 0x0, 0x9, 0xB, + 0x7, 0xD, 0xA, 0x1, 0x0, 0x8, 0x9, 0xF, 0xE, 0x4, 0x6, 0xC, 0xB, 0x2, 0x5, 0x3, + 0x6, 0xC, 0x7, 0x1, 0x5, 0xF, 0xD, 0x8, 0x4, 0xA, 0x9, 0xE, 0x0, 0x3, 0xB, 0x2, + 0x4, 0xB, 0xA, 0x0, 0x7, 0x2, 0x1, 0xD, 0x3, 0x6, 0x8, 0x5, 0x9, 0xC, 0xF, 0xE, + 0xD, 0xB, 0x4, 0x1, 0x3, 0xF, 0x5, 0x9, 0x0, 0xA, 0xE, 0x7, 0x6, 0x8, 0x2, 0xC, + 0x1, 0xF, 0xD, 0x0, 0x5, 0x7, 0xA, 0x4, 0x9, 0x2, 0x3, 0xE, 0x6, 0xB, 0x8, 0xC + ], + 'D-A': [ + 0xA, 0x4, 0x5, 0x6, 0x8, 0x1, 0x3, 0x7, 0xD, 0xC, 0xE, 0x0, 0x9, 0x2, 0xB, 0xF, + 0x5, 0xF, 0x4, 0x0, 0x2, 0xD, 0xB, 0x9, 0x1, 0x7, 0x6, 0x3, 0xC, 0xE, 0xA, 0x8, + 0x7, 0xF, 0xC, 0xE, 0x9, 0x4, 0x1, 0x0, 0x3, 0xB, 0x5, 0x2, 0x6, 0xA, 0x8, 0xD, + 0x4, 0xA, 0x7, 0xC, 0x0, 0xF, 0x2, 0x8, 0xE, 0x1, 0x6, 0x5, 0xD, 0xB, 0x9, 0x3, + 0x7, 0x6, 0x4, 0xB, 0x9, 0xC, 0x2, 0xA, 0x1, 0x8, 0x0, 0xE, 0xF, 0xD, 0x3, 0x5, + 0x7, 0x6, 0x2, 0x4, 0xD, 0x9, 0xF, 0x0, 0xA, 0x1, 0x5, 0xB, 0x8, 0xE, 0xC, 0x3, + 0xD, 0xE, 0x4, 0x1, 0x7, 0x0, 0x5, 0xA, 0x3, 0xC, 0x8, 0xF, 0x6, 0x2, 0x9, 0xB, + 0x1, 0x3, 0xA, 0x9, 0x5, 0xB, 0x4, 0xF, 0x8, 0x6, 0x7, 0xE, 0xD, 0x0, 0x2, 0xC + ], + 'D-SC': [ + 0xb, 0xd, 0x7, 0x0, 0x5, 0x4, 0x1, 0xf, 0x9, 0xe, 0x6, 0xa, 0x3, 0xc, 0x8, 0x2, + 0x1, 0x2, 0x7, 0x9, 0xd, 0xb, 0xf, 0x8, 0xe, 0xc, 0x4, 0x0, 0x5, 0x6, 0xa, 0x3, + 0x5, 0x1, 0xd, 0x3, 0xf, 0x6, 0xc, 0x7, 0x9, 0x8, 0xb, 0x2, 0x4, 0xe, 0x0, 0xa, + 0xd, 0x1, 0xb, 0x4, 0x9, 0xc, 0xe, 0x0, 0x7, 0x5, 0x8, 0xf, 0x6, 0x2, 0xa, 0x3, + 0x2, 0xd, 0xa, 0xf, 0x9, 0xb, 0x3, 0x7, 0x8, 0xc, 0x5, 0xe, 0x6, 0x0, 0x1, 0x4, + 0x0, 0x4, 0x6, 0xc, 0x5, 0x3, 0x8, 0xd, 0xa, 0xb, 0xf, 0x2, 0x1, 0x9, 0x7, 0xe, + 0x1, 0x3, 0xc, 0x8, 0xa, 0x6, 0xb, 0x0, 0x2, 0xe, 0x7, 0x9, 0xf, 0x4, 0x5, 0xd, + 0xa, 0xb, 0x6, 0x0, 0x1, 0x3, 0x4, 0x7, 0xe, 0xd, 0x5, 0xf, 0x8, 0x2, 0x9, 0xc + ] +}; + +var C = new Uint8Array([ + 0x69, 0x00, 0x72, 0x22, 0x64, 0xC9, 0x04, 0x23, + 0x8D, 0x3A, 0xDB, 0x96, 0x46, 0xE9, 0x2A, 0xC4, + 0x18, 0xFE, 0xAC, 0x94, 0x00, 0xED, 0x07, 0x12, + 0xC0, 0x86, 0xDC, 0xC2, 0xEF, 0x4C, 0xA9, 0x2B +]); + +function signed(x) { + return x >= 0x80000000 ? x - 0x100000000 : x; +} + +function unsigned(x) { + return x < 0 ? x + 0x100000000 : x; +} + +// Set random values into Uint8Arry +// Random generator +function randomSeed(e) { + GostRandom = GostRandom || root.GostRandom; + var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto; + if (randomSource.getRandomValues) + randomSource.getRandomValues(e); + else + throw new NotSupportedError('Random generator not found'); +} + +// Get buffer +function buffer(d) { + if (d instanceof CryptoOperationData) + return d; + else if (d && d?.buffer instanceof CryptoOperationData) + return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? + d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; + else + throw new DataError('CryptoOperationData required'); +} + +// Get byte array +function byteArray(d) { + return new Uint8Array(buffer(d)); +} + +// Clone byte array +function cloneArray(d) { + return new Uint8Array(byteArray(d)); +} + + +// Get int32 array +function intArray(d) { + return new Int32Array(buffer(d)); +} + +// Swap bytes for version 2015 +function swap32(b) { + return ((b & 0xff) << 24) + | ((b & 0xff00) << 8) + | ((b >> 8) & 0xff00) + | ((b >> 24) & 0xff); +} + +// + +/* + * Initial parameters and common algortithms of GOST R 34.12-15 + * Algorithm "Kuznechik" 128bit + * + */ // + +// Default initial vector +var defaultIV128 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + +// Mult table for R function +var multTable = (function () { + + // Multiply two numbers in the GF(2^8) finite field defined + // by the polynomial x^8 + x^7 + x^6 + x + 1 = 0 */ + function gmul(a, b) { + var p = 0, counter, carry; + for (counter = 0; counter < 8; counter++) { + if (b & 1) + p ^= a; + carry = a & 0x80; // detect if x^8 term is about to be generated + a = (a << 1) & 0xff; + if (carry) + a ^= 0xc3; // replace x^8 with x^7 + x^6 + x + 1 + b >>= 1; + } + return p & 0xff; + } + + // It is required only this values for R function + // 0 1 2 3 4 5 6 7 + var x = [1, 16, 32, 133, 148, 192, 194, 251]; + var m = []; + for (var i = 0; i < 8; i++) { + m[i] = []; + for (var j = 0; j < 256; j++) + m[i][j] = gmul(x[i], j); + } + return m; +})(); + +// 148, 32, 133, 16, 194, 192, 1, 251, 1, 192, 194, 16, 133, 32, 148, 1 +var kB = [4, 2, 3, 1, 6, 5, 0, 7, 0, 5, 6, 1, 3, 2, 4, 0]; + +// R - function +function funcR(d) { + var sum = 0; + for (var i = 0; i < 16; i++) + sum ^= multTable[kB[i]][d[i]]; + + for (var i = 16; i > 0; --i) + d[i] = d[i - 1]; + d[0] = sum; +} + +function funcReverseR(d) { + var tmp = d[0]; + for (var i = 0; i < 15; i++) + d[i] = d[i + 1]; + d[15] = tmp; + + var sum = 0; + for (i = 0; i < 16; i++) + sum ^= multTable[kB[i]][d[i]]; + d[15] = sum; +} + +// Nonlinear transformation +var kPi = [ + 252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, 218, 35, 197, 4, 77, + 233, 119, 240, 219, 147, 46, 153, 186, 23, 54, 241, 187, 20, 205, 95, 193, + 249, 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, 139, 1, 142, 79, + 5, 132, 2, 174, 227, 106, 143, 160, 6, 11, 237, 152, 127, 212, 211, 31, + 235, 52, 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, 58, 206, 204, + 181, 112, 14, 86, 8, 12, 118, 18, 191, 114, 19, 71, 156, 183, 93, 135, + 21, 161, 150, 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, 178, 177, + 50, 117, 25, 61, 255, 53, 138, 126, 109, 84, 198, 128, 195, 189, 13, 87, + 223, 245, 36, 169, 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, 3, + 224, 15, 236, 222, 122, 148, 176, 188, 220, 232, 40, 80, 78, 51, 10, 74, + 167, 151, 96, 115, 30, 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65, + 173, 69, 70, 146, 39, 94, 85, 47, 140, 163, 165, 125, 105, 213, 149, 59, + 7, 88, 179, 64, 134, 172, 29, 247, 48, 55, 107, 228, 136, 217, 231, 137, + 225, 27, 131, 73, 76, 63, 248, 254, 141, 83, 170, 144, 202, 216, 133, 97, + 32, 113, 103, 164, 45, 43, 9, 91, 203, 155, 37, 208, 190, 229, 108, 82, + 89, 166, 116, 210, 230, 244, 180, 192, 209, 102, 175, 194, 57, 75, 99, 182 +]; + +var kReversePi = (function () { + var m = []; + for (var i = 0, n = kPi.length; i < n; i++) + m[kPi[i]] = i; + return m; +})(); + +function funcS(d) { + for (var i = 0; i < 16; ++i) + d[i] = kPi[d[i]]; +} + +function funcReverseS(d) { + for (var i = 0; i < 16; ++i) + d[i] = kReversePi[d[i]]; +} + +function funcX(a, b) { + for (var i = 0; i < 16; ++i) + a[i] ^= b[i]; +} + +function funcL(d) { + for (var i = 0; i < 16; ++i) + funcR(d); +} + +function funcReverseL(d) { + for (var i = 0; i < 16; ++i) + funcReverseR(d); +} + +function funcLSX(a, b) { + funcX(a, b); + funcS(a); + funcL(a); +} + +function funcReverseLSX(a, b) { + funcX(a, b); + funcReverseL(a); + funcReverseS(a); +} + +function funcF(inputKey, inputKeySecond, iterationConst) { + var tmp = new Uint8Array(inputKey); + funcLSX(inputKey, iterationConst); + funcX(inputKey, inputKeySecond); + inputKeySecond.set(tmp); +} + +function funcC(number, d) { + for (var i = 0; i < 15; i++) + d[i] = 0; + d[15] = number; + funcL(d); +} + +// + +/** + * Key schedule for GOST R 34.12-15 128bits + * + * @memberOf GostCipher + * @private + * @instance + * @method keySchedule + * @param {type} k + * @returns {Uint8Array} + */ +function keySchedule128(k) // +{ + var keys = new Uint8Array(160), c = new Uint8Array(16); + keys.set(byteArray(k)); + for (var j = 0; j < 4; j++) { + var j0 = 32 * j, j1 = 32 * (j + 1); + keys.set(new Uint8Array(keys.buffer, j0, 32), j1); + for (var i = 1; i < 9; i++) { + funcC(j * 8 + i, c); + funcF(new Uint8Array(keys.buffer, j1, 16), + new Uint8Array(keys.buffer, j1 + 16, 16), c); + } + } + return keys; +} // + +/** + * GOST R 34.12-15 128 bits encrypt/decrypt process + * + * @memberOf GostCipher + * @private + * @instance + * @method round + * @param {Uint8Array} k Scheduled key + * @param {Uint8Array} d Data + * @param {number} ofs Offsec + * @param {number} e true - decrypt + */ +function process128(k, d, ofs, e) // +{ + ofs = ofs || d.byteOffset; + var r = new Uint8Array(d.buffer, ofs, 16); + if (e) { + for (var i = 0; i < 9; i++) + funcReverseLSX(r, new Uint8Array(k.buffer, (9 - i) * 16, 16)); + + funcX(r, new Uint8Array(k.buffer, 0, 16)); + } else { + for (var i = 0; i < 9; i++) + funcLSX(r, new Uint8Array(k.buffer, 16 * i, 16)); + + funcX(r, new Uint8Array(k.buffer, 16 * 9, 16)); + } +} // + +/** + * One GOST encryption round + * + * @memberOf GostCipher + * @private + * @instance + * @method round + * @param {Int8Array} S sBox + * @param {Int32Array} m 2x32 bits cipher block + * @param {Int32Array} k 32 bits key[i] + */ +function round(S, m, k) // +{ + var cm = (m[0] + k) & 0xffffffff; + + var om = S[ 0 + ((cm >> (0 * 4)) & 0xF)] << (0 * 4); + om |= S[ 16 + ((cm >> (1 * 4)) & 0xF)] << (1 * 4); + om |= S[ 32 + ((cm >> (2 * 4)) & 0xF)] << (2 * 4); + om |= S[ 48 + ((cm >> (3 * 4)) & 0xF)] << (3 * 4); + om |= S[ 64 + ((cm >> (4 * 4)) & 0xF)] << (4 * 4); + om |= S[ 80 + ((cm >> (5 * 4)) & 0xF)] << (5 * 4); + om |= S[ 96 + ((cm >> (6 * 4)) & 0xF)] << (6 * 4); + om |= S[112 + ((cm >> (7 * 4)) & 0xF)] << (7 * 4); + cm = om << 11 | om >>> (32 - 11); + + cm ^= m[1]; + m[1] = m[0]; + m[0] = cm; + +} // + +/** + * Process encrypt/decrypt block with key K using GOST 28147-89 + * + * @memberOf GostCipher + * @private + * @instance + * @method process + * @param k {Int32Array} 8x32 bits key + * @param d {Int32Array} 8x8 bits cipher block + * @param ofs {number} offset + */ +function process89(k, d, ofs) // +{ + ofs = ofs || d.byteOffset; + var s = this.sBox, + m = new Int32Array(d.buffer, ofs, 2); + + for (var i = 0; i < 32; i++) + round(s, m, k[i]); + + var r = m[0]; + m[0] = m[1]; + m[1] = r; +} // + +/** + * Process encrypt/decrypt block with key K using GOST R 34.12-15 64bit block + * + * @memberOf GostCipher + * @private + * @instance + * @method process + * @param k {Int32Array} 8x32 bits key + * @param d {Int32Array} 8x8 bits cipher block + * @param ofs {number} offset + */ +function process15(k, d, ofs) // +{ + ofs = ofs || d.byteOffset; + var s = this.sBox, + m = new Int32Array(d.buffer, ofs, 2), + r = swap32(m[0]); + m[0] = swap32(m[1]); + m[1] = r; + + for (var i = 0; i < 32; i++) + round(s, m, k[i]); + + m[0] = swap32(m[0]); + m[1] = swap32(m[1]); +} // + +/** + * Key keySchedule algorithm for GOST 28147-89 64bit cipher + * + * @memberOf GostCipher + * @private + * @instance + * @method process + * @param k {Uint8Array} 8 bit key array + * @param e {boolean} true - decrypt + * @returns {Int32Array} keyScheduled 32-bit key + */ +function keySchedule89(k, e) // +{ + var sch = new Int32Array(32), + key = new Int32Array(buffer(k)); + + for (var i = 0; i < 8; i++) + sch[i] = key[i]; + + if (e) { + for (var i = 0; i < 8; i++) + sch[i + 8] = sch[7 - i]; + + for (var i = 0; i < 8; i++) + sch[i + 16] = sch[7 - i]; + } else { + for (var i = 0; i < 8; i++) + sch[i + 8] = sch[i]; + + for (var i = 0; i < 8; i++) + sch[i + 16] = sch[i]; + } + + for (var i = 0; i < 8; i++) + sch[i + 24] = sch[7 - i]; + + return sch; +} // + +/** + * Key keySchedule algorithm for GOST R 34.12-15 64bit cipher + * + * @memberOf GostCipher + * @private + * @instance + * @method process + * @param k {Uint8Array} 8 bit key array + * @param e {boolean} true - decrypt + * @returns {Int32Array} keyScheduled 32-bit key + */ +function keySchedule15(k, e) // +{ + var sch = new Int32Array(32), + key = new Int32Array(buffer(k)); + + for (var i = 0; i < 8; i++) + sch[i] = swap32(key[i]); + + if (e) { + for (var i = 0; i < 8; i++) + sch[i + 8] = sch[7 - i]; + + for (var i = 0; i < 8; i++) + sch[i + 16] = sch[7 - i]; + } else { + for (var i = 0; i < 8; i++) + sch[i + 8] = sch[i]; + + for (var i = 0; i < 8; i++) + sch[i + 16] = sch[i]; + } + + for (var i = 0; i < 8; i++) + sch[i + 24] = sch[7 - i]; + + return sch; +} // + +/** + * Key schedule for RC2 + * + * https://tools.ietf.org/html/rfc2268 + * + * @memberOf GostCipher + * @private + * @instance + * @method keySchedule + * @param {Uint8Array} k + * @returns {Uint16Array} + */ +var keyScheduleRC2 = (function () // +{ + // an array of "random" bytes based on the digits of PI = 3.14159... + var PITABLE = new Uint8Array([ + 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d, + 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2, + 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32, + 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82, + 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc, + 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26, + 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03, + 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7, + 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a, + 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec, + 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39, + 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31, + 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9, + 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9, + 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e, + 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad + ]); + + return function (k) + { + var key = new Uint8Array(buffer(k)), + T = Math.min(key.length, 128), + T1 = this.effectiveLength, + T8 = Math.floor((T1 + 7) / 8), + TM = 0xff % Math.pow(2, 8 + T1 - 8 * T8); + + var L = new Uint8Array(128), K = new Uint16Array(L.buffer); + for (var i = 0; i < T; i++) + L[i] = key[i]; + for (var i = T; i < 128; i++) + L[i] = PITABLE[(L[i - 1] + L[i - T]) % 256]; + L[128 - T8] = PITABLE[L[128 - T8] & TM]; + for (var i = 127 - T8; i >= 0; --i) + L[i] = PITABLE[L[i + 1] ^ L[i + T8]]; + return K; + }; +} // +)(); + +/** + * RC2 encrypt/decrypt process + * + * https://tools.ietf.org/html/rfc2268 + * + * @memberOf GostCipher + * @private + * @instance + * @method round + * @param {CryptoOperationData} k Scheduled key + * @param {CryptoOperationData} d Data + * @param {number} ofs Offsec + * @param {number} e true - decrypt + */ +var processRC2 = (function () // +{ + var K, j, R = new Uint16Array(4), + s = new Uint16Array([1, 2, 3, 5]), reverse; + + function rol(R, s) { + return (R << s | R >>> (16 - s)) & 0xffff; + } + + function ror(R, s) { + return (R >>> s | R << (16 - s)) & 0xffff; + } + + function mix(i) { + if (reverse) { + R[i] = ror(R[i], s[i]); + R[i] = R[i] - K[j] - (R[(i + 3) % 4] & R[(i + 2) % 4]) - ((~R[(i + 3) % 4]) & R[(i + 1) % 4]); + j = j - 1; + } else { + R[i] = R[i] + K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) + ((~R[(i + 3) % 4]) & R[(i + 1) % 4]); + j = j + 1; + R[i] = rol(R[i], s[i]); + } + } + + function mash(i) { + if (reverse) { + R[i] = R[i] - K[R[(i + 3) % 4] & 63]; + } else { + R[i] = R[i] + K[R[(i + 3) % 4] & 63]; + } + } + + function perform(method, count) { + count = count || 1; + for (var j = 0; j < count; j++) { + if (reverse) { + for (var i = 3; i >= 0; --i) + method(i); + } else { + for (var i = 0; i < 4; i++) + method(i); + } + } + } + + return function (k, d, ofs, e) { + reverse = e; + // 1. Initialize words R[0], ..., R[3] to contain the 64-bit + // ciphertext value. + R = new Uint16Array(d.buffer, ofs || d.byteOffset, 4); + // 2. Expand the key, so that words K[0], ..., K[63] become + // defined. + K = k; + // 3. Initialize j to zero (enc) j to 63 (dec). + j = e ? 63 : 0; + // 4. Perform five mixing rounds. + perform(mix, 5); + // 5. Perform one mashing round. + perform(mash); + // 6. Perform six mixing rounds. + perform(mix, 6); + // 7. Perform one mashing round. + perform(mash); + // 8. Perform five mixing rounds. + perform(mix, 5); + }; +} // +)(); + +/** + * Algorithm name GOST 28147-ECB

+ * + * encryptECB (K, D) is D, encrypted with key k using GOST 28147/GOST R 34.13 in + * "prostaya zamena" (Electronic Codebook, ECB) mode. + * @memberOf GostCipher + * @method encrypt + * @instance + * @param k {CryptoOperationData} 8x32 bit key + * @param d {CryptoOperationData} 8 bits message + * @return {CryptoOperationData} result + */ +function encryptECB(k, d) // +{ + var p = this.pad(byteArray(d)), + n = this.blockSize, + b = p.byteLength / n, + key = this.keySchedule(k); + + for (var i = 0; i < b; i++) + this.process(key, p, n * i); + + return p.buffer; +} // + +/** + * Algorithm name GOST 28147-ECB

+ * + * decryptECB (K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 in + * "prostaya zamena" (Electronic Codebook, ECB) mode. + * + * @memberOf GostCipher + * @method decrypt + * @instance + * @param k {CryptoOperationData} 8x32 bits key + * @param d {CryptoOperationData} 8 bits message + * @return {CryptoOperationData} result + */ +function decryptECB(k, d) // +{ + var p = cloneArray(d), + n = this.blockSize, + b = p.byteLength / n, + key = this.keySchedule(k, 1); + + for (var i = 0; i < b; i++) + this.process(key, p, n * i, 1); + + return this.unpad(p).buffer; +} // + +/** + * Algorithm name GOST 28147-CFB

+ * + * encryptCFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie s obratnoj svyaziyu" (Cipher Feedback, CFB) mode, and IV is + * used as the initialization vector. + * + * @memberOf GostCipher + * @method encrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function encryptCFB(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + c = cloneArray(d), + m = s.length, + t = new Uint8Array(m), + b = this.shiftBits >> 3, + cb = c.length, r = cb % b, q = (cb - r) / b, + key = this.keySchedule(k); + + for (var i = 0; i < q; i++) { + + for (var j = 0; j < m; j++) + t[j] = s[j]; + + this.process(key, s); + + for (var j = 0; j < b; j++) + c[i * b + j] ^= s[j]; + + for (var j = 0; j < m - b; j++) + s[j] = t[b + j]; + + for (var j = 0; j < b; j++) + s[m - b + j] = c[i * b + j]; + + k = this.keyMeshing(k, s, i, key); + } + + if (r > 0) { + this.process(key, s); + + for (var i = 0; i < r; i++) + c[q * b + i] ^= s[i]; + } + return c.buffer; +} // + +/** + * Algorithm name GOST 28147-CFB

+ * + * decryptCFB (IV, K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie s obratnoj svyaziyu po shifrotekstu" (Cipher Feedback, CFB) mode, and IV is + * used as the initialization vector. + * + * @memberOf GostCipher + * @method decrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function decryptCFB(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + c = cloneArray(d), + m = s.length, + t = new Uint8Array(m), + b = this.shiftBits >> 3, + cb = c.length, r = cb % b, q = (cb - r) / b, + key = this.keySchedule(k); + + for (var i = 0; i < q; i++) { + + for (var j = 0; j < m; j++) + t[j] = s[j]; + + this.process(key, s); + + for (var j = 0; j < b; j++) { + t[j] = c[i * b + j]; + c[i * b + j] ^= s[j]; + } + + for (var j = 0; j < m - b; j++) + s[j] = t[b + j]; + + for (var j = 0; j < b; j++) + s[m - b + j] = t[j]; + + k = this.keyMeshing(k, s, i, key); + } + + if (r > 0) { + this.process(key, s); + + for (var i = 0; i < r; i++) + c[q * b + i] ^= s[i]; + } + return c.buffer; +} // + +/** + * Algorithm name GOST 28147-OFB

+ * + * encryptOFB/decryptOFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie s obratnoj svyaziyu po vyhodu" (Output Feedback, OFB) mode, and IV is + * used as the initialization vector. + * + * @memberOf GostCipher + * @method encrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv 8x8 optional bits initial vector + * @return {CryptoOperationData} result + */ +/** + * Algorithm name GOST 28147-OFB

+ * + * encryptOFB/decryptOFB (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie s obratnoj svyaziyu po vyhodu" (Output Feedback, OFB) mode, and IV is + * used as the initialization vector. + * + * @memberOf GostCipher + * @method decrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function processOFB(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + c = cloneArray(d), + m = s.length, + t = new Uint8Array(m), + b = this.shiftBits >> 3, + p = new Uint8Array(b), + cb = c.length, r = cb % b, q = (cb - r) / b, + key = this.keySchedule(k); + + for (var i = 0; i < q; i++) { + + for (var j = 0; j < m; j++) + t[j] = s[j]; + + this.process(key, s); + + for (var j = 0; j < b; j++) + p[j] = s[j]; + + for (var j = 0; j < b; j++) + c[i * b + j] ^= s[j]; + + for (var j = 0; j < m - b; j++) + s[j] = t[b + j]; + + for (var j = 0; j < b; j++) + s[m - b + j] = p[j]; + + k = this.keyMeshing(k, s, i, key); + } + + if (r > 0) { + this.process(key, s); + + for (var i = 0; i < r; i++) + c[q * b + i] ^= s[i]; + } + return c.buffer; +} // + +/** + * Algorithm name GOST 28147-CTR

+ * + * encryptCTR/decryptCTR (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie" (Counter Mode-CTR) mode, and IV is used as the + * initialization vector. + * @memberOf GostCipher + * @method encrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv 8x8 optional bits initial vector + * @return {CryptoOperationData} result + */ +/** + * Algorithm name GOST 28147-CTR

+ * + * encryptCTR/decryptCTR (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "gammirovanie" (Counter Mode-CTR) mode, and IV is used as the + * initialization vector. + * @memberOf GostCipher + * @method decrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function processCTR89(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + c = cloneArray(d), + b = this.blockSize, + t = new Int8Array(b), + cb = c.length, r = cb % b, q = (cb - r) / b, + key = this.keySchedule(k), + syn = new Int32Array(s.buffer); + + this.process(key, s); + + for (var i = 0; i < q; i++) { + syn[0] = (syn[0] + 0x1010101) & 0xffffffff; + // syn[1] = signed(unsigned((syn[1] + 0x1010104) & 0xffffffff) % 0xffffffff); + var tmp = unsigned(syn[1]) + 0x1010104; // Special thanks to Ilya Matveychikov + syn[1] = signed(tmp < 0x100000000 ? tmp : tmp - 0xffffffff); + + for (var j = 0; j < b; j++) + t[j] = s[j]; + + this.process(key, syn); + + for (var j = 0; j < b; j++) + c[i * b + j] ^= s[j]; + + for (var j = 0; j < b; j++) + s[j] = t[j]; + + k = this.keyMeshing(k, s, i, key); + } + if (r > 0) { + syn[0] = (syn[0] + 0x1010101) & 0xffffffff; + // syn[1] = signed(unsigned((syn[1] + 0x1010104) & 0xffffffff) % 0xffffffff); + var tmp = unsigned(syn[1]) + 0x1010104; // Special thanks to Ilya Matveychikov + syn[1] = signed(tmp < 0x100000000 ? tmp : tmp - 0xffffffff); + + this.process(key, syn); + + for (var i = 0; i < r; i++) + c[q * b + i] ^= s[i]; + } + return c.buffer; +} // + +function processCTR15(k, d, iv) // +{ + var c = cloneArray(d), + n = this.blockSize, + b = this.shiftBits >> 3, + cb = c.length, r = cb % b, q = (cb - r) / b, + s = new Uint8Array(n), + t = new Int32Array(n), + key = this.keySchedule(k); + + s.set(iv || this.iv); + for (var i = 0; i < q; i++) { + + for (var j = 0; j < n; j++) + t[j] = s[j]; + + this.process(key, s); + + for (var j = 0; j < b; j++) + c[b * i + j] ^= s[j]; + + for (var j = 0; j < n; j++) + s[j] = t[j]; + + for (var j = n - 1; i >= 0; --i) { + if (s[j] > 0xfe) { + s[j] -= 0xfe; + } else { + s[j]++; + break; + } + } + } + + if (r > 0) { + this.process(key, s); + for (var j = 0; j < r; j++) + c[b * q + j] ^= s[j]; + } + + return c.buffer; +} // + +/** + * Algorithm name GOST 28147-CBC

+ * + * encryptCBC (IV, K, D) is D, encrypted with key K using GOST 28147/GOST R 34.13 + * in "Prostaya zamena s zatsepleniem" (Cipher-Block-Chaining, CBC) mode and IV is used as the initialization + * vector. + * + * @memberOf GostCipher + * @method encrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function encryptCBC(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + n = this.blockSize, + m = s.length, + c = this.pad(byteArray(d)), + key = this.keySchedule(k); + + for (var i = 0, b = c.length / n; i < b; i++) { + + for (var j = 0; j < n; j++) + s[j] ^= c[i * n + j]; + + this.process(key, s); + + for (var j = 0; j < n; j++) + c[i * n + j] = s[j]; + + if (m !== n) { + for (var j = 0; j < m - n; j++) + s[j] = s[n + j]; + + for (var j = 0; j < n; j++) + s[j + m - n] = c[i * n + j]; + } + + k = this.keyMeshing(k, s, i, key); + } + + return c.buffer; +} // + +/** + * Algorithm name GOST 28147-CBC

+ * + * decryptCBC (IV, K, D) is D, decrypted with key K using GOST 28147/GOST R 34.13 + * in "Prostaya zamena s zatsepleniem" (Cipher-Block-Chaining, CBC) mode and IV is used as the initialization + * vector. + * + * @memberOf GostCipher + * @method decrypt + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function decryptCBC(k, d, iv) // +{ + var s = new Uint8Array(iv || this.iv), + n = this.blockSize, + m = s.length, + c = cloneArray(d), + next = new Uint8Array(n), + key = this.keySchedule(k, 1); + + for (var i = 0, b = c.length / n; i < b; i++) { + + for (var j = 0; j < n; j++) + next[j] = c[i * n + j]; + + this.process(key, c, i * n, 1); + + for (var j = 0; j < n; j++) + c[i * n + j] ^= s[j]; + + if (m !== n) { + for (var j = 0; j < m - n; j++) + s[j] = s[n + j]; + } + + for (var j = 0; j < n; j++) + s[j + m - n] = next[j]; + + k = this.keyMeshing(k, s, i, key, 1); + } + + return this.unpad(c).buffer; +} // + +/** + * The generateKey method returns a new generated key. + * + * @memberOf GostCipher + * @method generateKey + * @instance + * @return {CryptoOperationData} result + */ + +function generateKey() // +{ + // Simple generate 256 bit random seed + var k = new Uint8Array(this.keySize); + randomSeed(k); + return k.buffer; +} // + + +/** + * makeIMIT (K, D) is the 32-bit result of the GOST 28147/GOST R 34.13 in + * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV + * as initialization vector. Note that the standard specifies its use + * in this mode only with an initialization vector of zero. + * + * @memberOf GostCipher + * @method processMAC + * @private + * @instance + * @param {Int32Array} key 8x32 bits key + * @param {Int32Array} s 8x8 sum array + * @param {Uint8Array} d 8 bits array with data + * @return {Uint8Array} result + */ +function processMAC89(key, s, d) // +{ + var c = zeroPad.call(this, byteArray(d)), + n = this.blockSize, + q = c.length / n, + sBox = this.sBox, + sum = new Int32Array(s.buffer); + + for (var i = 0; i < q; i++) { + + for (var j = 0; j < n; j++) + s[j] ^= c[i * n + j]; + + for (var j = 0; j < 16; j++) // 1-16 steps + round(sBox, sum, key[j]); + } +} // + +function processKeyMAC15(s) // +{ + var t = 0, n = s.length; + for (var i = n - 1; i >= 0; --i) { + var t1 = s[i] >>> 7; + s[i] = (s[i] << 1) & 0xff | t; + t = t1; + } + if (t !== 0) { + if (n === 16) + s[15] ^= 0x87; + else + s[7] ^= 0x1b; + } +} // + +function processMAC15(key, s, d) // +{ + var n = this.blockSize, + sBox = this.sBox, c = byteArray(d), + r = new Uint8Array(n); + // R + this.process(key, r); + // K1 + processKeyMAC15(r); + if (d.byteLength % n !== 0) { + c = bitPad.call(this, byteArray(d)); + // K2 + processKeyMAC15(r); + } + + for (var i = 0, q = c.length / n; i < q; i++) { + + for (var j = 0; j < n; j++) + s[j] ^= c[i * n + j]; + + if (i === q - 1) {// Last block + for (var j = 0; j < n; j++) + s[j] ^= r[j]; + } + + this.process(key, s); + } +} // + +/** + * signMAC (K, D, IV) is the 32-bit result of the GOST 28147/GOST R 34.13 in + * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV + * as initialization vector. Note that the standard specifies its use + * in this mode only with an initialization vector of zero. + * + * @memberOf GostCipher + * @method sign + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv initial vector + * @return {CryptoOperationData} result + */ +function signMAC(k, d, iv) // +{ + var key = this.keySchedule(k), + s = new Uint8Array(iv || this.iv), + m = Math.ceil(this.macLength >> 3) || this.blockSize >> 1; + + this.processMAC(key, s, d); + + var mac = new Uint8Array(m); // mac size + mac.set(new Uint8Array(s.buffer, 0, m)); + return mac.buffer; +} // + +/** + * verifyMAC (K, M, D, IV) the 32-bit result verification of the GOST 28147/GOST R 34.13 in + * "imitovstavka" (MAC) mode, used with D as plaintext, K as key and IV + * as initialization vector. Note that the standard specifies its use + * in this mode only with an initialization vector of zero. + * + * @memberOf GostCipher + * @method verify + * @instance + * @param {CryptoOperationData} k 8x32 bits key + * @param {CryptoOperationData} m 8 bits array with signature + * @param {CryptoOperationData} d 8 bits array with data + * @param {CryptoOperationData} iv 8x8 optional bits initial vector + * @return {boolen} MAC verified = true + */ +function verifyMAC(k, m, d, iv) // +{ + var mac = new Uint8Array(signMAC.call(this, k, d, iv)), + test = byteArray(m); + if (mac.length !== test.length) + return false; + for (var i = 0, n = mac.length; i < n; i++) + if (mac[i] !== test[i]) + return false; + return true; +} // + +/** + * Algorithm name GOST 28147-KW

+ * + * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147/GOST R 34.13 KEK. + * Ref. rfc4357 6.1 GOST 28147-89 Key Wrap + * Note: This algorithm MUST NOT be used with a KEK produced by VKO GOST + * R 34.10-94, because such a KEK is constant for every sender-recipient + * pair. Encrypting many different content encryption keys on the same + * constant KEK may reveal that KEK. + * + * @memberOf GostCipher + * @method wrapKey + * @instance + * @param {CryptoOperationData} kek Key encryption key + * @param {CryptoOperationData} cek Content encryption key + * @returns {CryptoOperationData} Encrypted cek + */ +function wrapKeyGOST(kek, cek) // +{ + var n = this.blockSize, k = this.keySize, len = k + (n >> 1); + // 1) For a unique symmetric KEK, generate 8 octets at random and call + // the result UKM. For a KEK, produced by VKO GOST R 34.10-2001, use + // the UKM that was used for key derivation. + if (!this.ukm) + throw new DataError('UKM must be defined'); + var ukm = new Uint8Array(this.ukm); + // 2) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK, CEK). + // Call the result CEK_MAC. + var mac = signMAC.call(this, kek, cek, ukm); + // 3) Encrypt the CEK in ECB mode using the KEK. Call the ciphertext CEK_ENC. + var enc = encryptECB.call(this, kek, cek); + // 4) The wrapped content-encryption key is (UKM | CEK_ENC | CEK_MAC). + var r = new Uint8Array(len); + r.set(new Uint8Array(enc), 0); + r.set(new Uint8Array(mac), k); + return r.buffer; +} // + +/** + * Algorithm name GOST 28147-KW

+ * + * This algorithm decrypts GOST 28147-89 CEK with a GOST 28147 KEK. + * Ref. rfc4357 6.2 GOST 28147-89 Key Unwrap + * + * @memberOf GostCipher + * @method unwrapKey + * @instance + * @param {type} kek Key encryption key + * @param {type} data Content encryption key + * @return {CryptoOperationData} result + */ +function unwrapKeyGOST(kek, data) // +{ + var n = this.blockSize, k = this.keySize, len = k + (n >> 1); + // 1) If the wrapped content-encryption key is not 44 octets, then error. + var d = buffer(data); + if (d.byteLength !== len) + throw new DataError('Wrapping key size must be ' + len + ' bytes'); + // 2) Decompose the wrapped content-encryption key into UKM, CEK_ENC, and CEK_MAC. + // UKM is the most significant (first) 8 octets. CEK_ENC is next 32 octets, + // and CEK_MAC is the least significant (last) 4 octets. + if (!this.ukm) + throw new DataError('UKM must be defined'); + var ukm = new Uint8Array(this.ukm), + enc = new Uint8Array(d, 0, k), + mac = new Uint8Array(d, k, n >> 1); + // 3) Decrypt CEK_ENC in ECB mode using the KEK. Call the output CEK. + var cek = decryptECB.call(this, kek, enc); + // 4) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK, CEK), + // compare the result with CEK_MAC. If they are not equal, then error. + var check = verifyMAC.call(this, kek, mac, cek, ukm); + if (!check) + throw new DataError('Error verify MAC of wrapping key'); + return cek; +} // + +/** + * Algorithm name GOST 28147-CPKW

+ * + * Given a random 64-bit UKM and a GOST 28147 key K, this algorithm + * creates a new GOST 28147-89 key K(UKM). + * Ref. rfc4357 6.3 CryptoPro KEK Diversification Algorithm + * + * @memberOf GostCipher + * @method diversify + * @instance + * @private + * @param {CryptoOperationData} kek Key encryption key + * @param {CryptoOperationData} ukm Random generated value + * @returns {CryptoOperationData} Diversified kek + */ +function diversifyKEK(kek, ukm) // +{ + var n = this.blockSize; + + // 1) Let K[0] = K; + var k = intArray(kek); + // 2) UKM is split into components a[i,j]: + // UKM = a[0]|..|a[7] (a[i] - byte, a[i,0]..a[i,7] - it’s bits) + var a = []; + for (var i = 0; i < n; i++) { + a[i] = []; + for (var j = 0; j < 8; j++) { + a[i][j] = (ukm[i] >>> j) & 0x1; + } + } + // 3) Let i be 0. + // 4) K[1]..K[8] are calculated by repeating the following algorithm + // eight times: + for (var i = 0; i < n; i++) { + // A) K[i] is split into components k[i,j]: + // K[i] = k[i,0]|k[i,1]|..|k[i,7] (k[i,j] - 32-bit integer) + // B) Vector S[i] is calculated: + // S[i] = ((a[i,0]*k[i,0] + ... + a[i,7]*k[i,7]) mod 2^32) | + // (((~a[i,0])*k[i,0] + ... + (~a[i,7])*k[i,7]) mod 2^32); + var s = new Int32Array(2); + for (var j = 0; j < 8; j++) { + if (a[i][j]) + s[0] = (s[0] + k[j]) & 0xffffffff; + else + s[1] = (s[1] + k[j]) & 0xffffffff; + } + // C) K[i+1] = encryptCFB (S[i], K[i], K[i]) + var iv = new Uint8Array(s.buffer); + k = new Int32Array(encryptCFB.call(this, k, k, iv)); + // D) i = i + 1 + } + // 5) Let K(UKM) be K[8]. + return k; +} // + +/** + * Algorithm name GOST 28147-CPKW

+ * + * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147 KEK. + * It can be used with any KEK (e.g., produced by VKO GOST R 34.10-94 or + * VKO GOST R 34.10-2001) because a unique UKM is used to diversify the KEK. + * Ref. rfc4357 6.3 CryptoPro Key Wrap + * + * @memberOf GostCipher + * @method wrapKey + * @instance + * @param {CryptoOperationData} kek Key encryption key + * @param {CryptoOperationData} cek Content encryption key + * @returns {CryptoOperationData} Encrypted cek + */ +function wrapKeyCP(kek, cek) // +{ + var n = this.blockSize, k = this.keySize, len = k + (n >> 1); + // 1) For a unique symmetric KEK or a KEK produced by VKO GOST R + // 34.10-94, generate 8 octets at random. Call the result UKM. For + // a KEK, produced by VKO GOST R 34.10-2001, use the UKM that was + // used for key derivation. + if (!this.ukm) + throw new DataError('UKM must be defined'); + var ukm = new Uint8Array(this.ukm); + // 2) Diversify KEK, using the CryptoPro KEK Diversification Algorithm, + // described in Section 6.5. Call the result KEK(UKM). + var dek = diversifyKEK.call(this, kek, ukm); + // 3) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK(UKM), + // CEK). Call the result CEK_MAC. + var mac = signMAC.call(this, dek, cek, ukm); + // 4) Encrypt CEK in ECB mode using KEK(UKM). Call the ciphertext + // CEK_ENC. + var enc = encryptECB.call(this, dek, cek); + // 5) The wrapped content-encryption key is (UKM | CEK_ENC | CEK_MAC). + var r = new Uint8Array(len); + r.set(new Uint8Array(enc), 0); + r.set(new Uint8Array(mac), k); + return r.buffer; +} // + +/** + * Algorithm name GOST 28147-CPKW

+ * + * This algorithm encrypts GOST 28147-89 CEK with a GOST 28147 KEK. + * Ref. rfc4357 6.4 CryptoPro Key Unwrap + * + * @memberOf GostCipher + * @method unwrapKey + * @instance + * @param {CryptoOperationData} kek Key encryption key + * @param {CryptoOperationData} data Encrypted content encryption keu + * @return {CryptoOperationData} result Decrypted content encryption keu + */ +function unwrapKeyCP(kek, data) // +{ + var n = this.blockSize, k = this.keySize, len = k + (n >> 1); + // 1) If the wrapped content-encryption key is not 44 octets, then error. + var d = buffer(data); + if (d.byteLength !== len) + throw new DataError('Wrapping key size must be ' + len + ' bytes'); + // 2) Decompose the wrapped content-encryption key into UKM, CEK_ENC, + // and CEK_MAC. UKM is the most significant (first) 8 octets. + // CEK_ENC is next 32 octets, and CEK_MAC is the least significant + // (last) 4 octets. + if (!this.ukm) + throw new DataError('UKM must be defined'); + var ukm = new Uint8Array(this.ukm), + enc = new Uint8Array(d, 0, k), + mac = new Uint8Array(d, k, n >> 1); + // 3) Diversify KEK using the CryptoPro KEK Diversification Algorithm, + // described in section 6.5. Call the result KEK(UKM). + var dek = diversifyKEK.call(this, kek, ukm); + // 4) Decrypt CEK_ENC in ECB mode using KEK(UKM). Call the output CEK. + var cek = decryptECB.call(this, dek, enc); + // 5) Compute a 4-byte checksum value, GOST 28147IMIT (UKM, KEK(UKM), + // CEK), compare the result with CEK_MAC. If they are not equal, + // then it is an error. + var check = verifyMAC.call(this, dek, mac, cek, ukm); + if (!check) + throw new DataError('Error verify MAC of wrapping key'); + return cek; +} // + +/** + * SignalCom master key packing algorithm + * + * kek stored in 3 files - kek.opq, mk.db3, masks.db3 + * kek.opq - always 36 bytes length = 32 bytes encrypted kek + 4 bytes mac of decrypted kek + * mk.db3 - 6 bytes header (1 byte magic code 0x22 + 1 byte count of masks + 4 bytes mac of + * xor summarizing masks value) + attached masks + * masks.db3 - detached masks. + * Total length of attached + detached masks = 32 bits * count of masks + * Default value of count 8 = (7 attached + 1 detached). But really no reason for such + * separation - all masks xor summarizing - order is not matter. + * Content of file rand.opq can used as ukm. Don't forget change file content after using. + * + * For usb-token files has names: + * a001 - mk.db3, b001 - masks.db3, c001 - kek.opq, d001 - rand.opq + * For windows registry + * 00000001 - mk.db3, 00000002 - masks.db3, 00000003 - key.opq, 00000004 - rand.opq, + * 00000006 - keys\00000001.key, 0000000A - certificate + * + * @memberOf GostCipher + * @method packKey + * @instance + * @private + * @param {CryptoOperationData} unpacked - clear main key 32 bytes + * @param {CryptoOperationData} ukm - random vector for packing - 32 bytes * (count of masks - 1) + * @returns {CryptoOperationData} packed master key - concatination of mk.db3 + masks.db3 + */ +function packKeySC(unpacked, ukm) // +{ + var m = this.blockSize >> 1, k = this.keySize; + var mcount = 8; + var key = new Uint8Array(buffer(unpacked)); + if (key.byteLength !== k) + throw new DataError('Wrong cleartext size ' + key.byteLength + ' bytes'); + // Check or generate UKM + ukm = ukm || this.ukm; + if (ukm) { + ukm = new Uint8Array(buffer(ukm)); + if (ukm.byteLength > 0 && ukm.byteLength % k === 0) + mcount = ukm.byteLength / k + 1; + else + throw new DataError('Wrong rand size ' + ukm.byteLength + ' bytes'); + } else + randomSeed(ukm = new Uint8Array((mcount - 1) * k)); + // Output array + var d = new Uint8Array(mcount * k + m + 2), b = d.buffer; + // Calculate MAC + var zero32 = new Uint8Array(k); + var mac = signMAC.call(this, key, zero32); + d[0] = 0x22; // Magic code + d[1] = mcount; // Count of masks + d.set(new Uint8Array(mac), 2); + d.set(ukm, k + m + 2); + for (var i = 1; i < mcount; i++) { + var mask = new Uint8Array(b, 2 + m + k * i); + for (var j = 0; j < k; j++) + key[j] ^= mask[j]; + } + d.set(key, m + 2); + return d.buffer; +} // + +/** + * Algorithm name GOST 28147-SCKW

+ * + * SignalCom master key unpacking algorithm + * + * @memberOf GostCipher + * @method unpackKey + * @instance + * @private + * @param {CryptoOperationData} packed - concatination of mk.db3 + masks.db3 + * @returns {CryptoOperationData} unpacked master key + */ +function unpackKeySC(packed) // +{ + var m = this.blockSize >> 1, k = this.keySize; + var b = buffer(packed); + // Unpack master key + var magic = new Uint8Array(b, 0, 1)[0]; + if (magic !== 0x22) + throw new DataError('Invalid magic number'); + var mcount = new Uint8Array(b, 1, 1)[0]; + var mac = new Uint8Array(b, 2, m); // MAC for summarized mask + // Compute packKey xor summing for all masks + var key = new Uint8Array(k); + for (var i = 0; i < mcount; i++) { + var mask = new Uint8Array(b, 2 + m + k * i, k); + for (var j = 0; j < k; j++) + key[j] ^= mask[j]; + } + // Test MAC for packKey with default sBox on zero 32 bytes array + var zero32 = new Uint8Array(k); + var test = verifyMAC.call(this, key, mac, zero32); + if (!test) { + // Try to use different sBoxes + var names = ['E-A', 'E-B', 'E-C', 'E-D', 'E-SC']; + for (var i = 0, n = names.length; i < n; i++) { + this.sBox = sBoxes[names[i]]; + test = verifyMAC.call(this, key, mac, zero32); + if (test) + break; + } + } + if (!test) + throw new DataError('Invalid main key MAC'); + return key.buffer; +} // + +/** + * Algorithm name GOST 28147-SCKW

+ * + * SignalCom Key Wrapping algorithm + * + * @memberOf GostCipher + * @method wrapKey + * @instance + * @param {CryptoOperationData} kek - clear kek or concatination of mk.db3 + masks.db3 + * @param {CryptoOperationData} cek - key for wrapping + * @returns {CryptoOperationData} wrapped key - file kek.opq + */ +function wrapKeySC(kek, cek) // +{ + var m = this.blockSize >> 1, n = this.keySize; + var k = buffer(kek); + var c = buffer(cek); + if (k.byteLength !== n) + k = unpackKeySC.call(this, k); + var enc = encryptECB.call(this, k, c); + var mac = signMAC.call(this, k, c); + var d = new Uint8Array(m + n); + d.set(new Uint8Array(enc), 0); + d.set(new Uint8Array(mac), n); + return d.buffer; +} // + +/** + * Algorithm name GOST 28147-SCKW

+ * + * SignalCom Key UnWrapping algorithm + * + * @memberOf GostCipher + * @method unwrapKey + * @instance + * @param {CryptoOperationData} kek - concatination of files mk.db3 + masks.db3 or clear kek + * @param {CryptoOperationData} cek - wrapping key - file kek.opq + * @return {CryptoOperationData} result + */ +function unwrapKeySC(kek, cek) // +{ + var m = this.blockSize >> 1, n = this.keySize; + var k = buffer(kek); + var c = buffer(cek); + if (k.byteLength !== n) + k = unpackKeySC.call(this, k); + var enc = new Uint8Array(c, 0, n); // Encrypted kek + var mac = new Uint8Array(c, n, m); // MAC for clear kek + var d = decryptECB.call(this, k, enc); + if (!verifyMAC.call(this, k, mac, d)) + throw new DataError('Invalid key MAC'); + return d; +} // + +/** + * Algorithm name GOST 28147-SCKW

+ * + * SignalCom master key generation for wrapping + * + * @memberOf GostCipher + * @method generateKey + * @instance + * @return {CryptoOperationData} result + */ +function generateWrappingKeySC() // +{ + return packKeySC.call(this, generateKey.call(this)); +} // + +function maskKey(mask, key, inverse, keySize) // +{ + var k = keySize / 4, + m32 = new Int32Array(buffer(mask)), + k32 = new Int32Array(buffer(key)), + r32 = new Int32Array(k); + if (inverse) + for (var i = 0; i < k; i++) + r32[i] = (k32[i] + m32[i]) & 0xffffffff; + else + for (var i = 0; i < k; i++) + r32[i] = (k32[i] - m32[i]) & 0xffffffff; + return r32.buffer; +} // + +/** + * Algorithm name GOST 28147-MASK

+ * + * This algorithm wrap key mask + * + * @memberOf GostCipher + * @method wrapKey + * @instance + * @param {CryptoOperationData} mask The mask + * @param {CryptoOperationData} key The key + * @returns {CryptoOperationData} The masked key + */ +function wrapKeyMask(mask, key) // +{ + return maskKey(mask, key, this.procreator === 'VN', this.keySize); +} // + +/** + * Algorithm name GOST 28147-CPKW

+ * + * This algorithm unwrap key mask + * + * @memberOf GostCipher + * @method unwrapKey + * @instance + * @param {CryptoOperationData} mask The mask + * @param {CryptoOperationData} key The masked key + * @return {CryptoOperationData} result The key + */ +function unwrapKeyMask(mask, key) // +{ + return maskKey(mask, key, this.procreator !== 'VN', this.keySize); +} // + +/** + * Algorithm name GOST 28147-CPKM

+ * + * Key meshing in according to rfc4357 2.3.2. CryptoPro Key Meshing + * + * @memberOf GostCipher + * @method keyMeshing + * @instance + * @private + * @param {(Uint8Array|CryptoOperationData)} k 8x8 bit key + * @param {Uint8Array} s 8x8 bit sync (iv) + * @param {Integer} i block index + * @param {Int32Array} key 8x32 bit key schedule + * @param {boolean} e true - decrypt + * @returns CryptoOperationData next 8x8 bit key + */ +function keyMeshingCP(k, s, i, key, e) // +{ + if ((i + 1) * this.blockSize % 1024 === 0) { // every 1024 octets + // K[i+1] = decryptECB (K[i], C); + k = decryptECB.call(this, k, C); + // IV0[i+1] = encryptECB (K[i+1],IVn[i]) + s.set(new Uint8Array(encryptECB.call(this, k, s))); + // restore key schedule + key.set(this.keySchedule(k, e)); + } + return k; +} // + +/** + * Null Key Meshing in according to rfc4357 2.3.1 + * + * @memberOf GostCipher + * @method keyMeshing + * @instance + * @private + * @param {(Uint8Array|CryptoOperationData)} k 8x8 bit key + */ +function noKeyMeshing(k) // +{ + return k; +} // + +/** + * Algorithm name GOST 28147-NoPadding

+ * + * No padding. + * + * @memberOf GostCipher + * @method padding + * @instance + * @private + * @param {Uint8Array} d array with source data + * @returns {Uint8Array} result + */ +function noPad(d) // +{ + return new Uint8Array(d); +} // + +/** + * Algorithm name GOST 28147-PKCS5Padding

+ * + * PKCS#5 padding: 8-x remaining bytes are filled with the value of + * 8-x. If there’s no incomplete block, one extra block filled with + * value 8 is added + * + * @memberOf GostCipher + * @method padding + * @instance + * @private + * @param {Uint8Array} d array with source data + * @returns {Uint8Array} result + */ +function pkcs5Pad(d) // +{ + var n = d.byteLength, + nb = this.blockSize, + q = nb - n % nb, + m = Math.ceil((n + 1) / nb) * nb, + r = new Uint8Array(m); + r.set(d); + for (var i = n; i < m; i++) + r[i] = q; + return r; +} // + +function pkcs5Unpad(d) // +{ + var m = d.byteLength, + nb = this.blockSize, + q = d[m - 1], + n = m - q; + if (q > nb) + throw DataError('Invalid padding'); + var r = new Uint8Array(n); + if (n > 0) + r.set(new Uint8Array(d.buffer, 0, n)); + return r; +} // + + +/** + * Algorithm name GOST 28147-ZeroPadding

+ * + * Zero padding: 8-x remaining bytes are filled with zero + * + * @memberOf GostCipher + * @method padding + * @instance + * @private + * @param {Uint8Array} d array with source data + * @returns {Uint8Array} result + */ +function zeroPad(d) // +{ + var n = d.byteLength, + nb = this.blockSize, + m = Math.ceil(n / nb) * nb, + r = new Uint8Array(m); + r.set(d); + for (var i = n; i < m; i++) + r[i] = 0; + return r; +} // + + +/** + * Algorithm name GOST 28147-BitPadding

+ * + * Bit padding: P* = P || 1 || 000...0 If there’s no incomplete block, + * one extra block filled with 1 || 000...0 + * + * @memberOf GostCipher + * @method padding + * @instance + * @private + * @param {Uint8Array} d array with source data + * @returns {Uint8Array} result + */ +function bitPad(d) // +{ + var n = d.byteLength, + nb = this.blockSize, + m = Math.ceil((n + 1) / nb) * nb, + r = new Uint8Array(m); + r.set(d); + r[n] = 1; + for (var i = n + 1; i < m; i++) + r[i] = 0; + return r; +} // + +function bitUnpad(d) // +{ + var m = d.byteLength, + n = m; + while (n > 1 && d[n - 1] === 0) + n--; + if (d[n - 1] !== 1) + throw DataError('Invalid padding'); + n--; + var r = new Uint8Array(n); + if (n > 0) + r.set(new Uint8Array(d.buffer, 0, n)); + return r; +} // + +/** + * Algorithm name GOST 28147-RandomPadding

+ * + * Random padding: 8-x remaining bytes of the last block are set to + * random. + * + * @memberOf GostCipher + * @method padding + * @instance + * @private + * @param {Uint8Array} d array with source data + * @returns {Uint8Array} result + */ +function randomPad(d) // +{ + var n = d.byteLength, + nb = this.blockSize, + q = nb - n % nb, + m = Math.ceil(n / nb) * nb, + r = new Uint8Array(m), e = new Uint8Array(r.buffer, n, q); + r.set(d); + randomSeed(e); + return r; +} // + +/** + * GOST 28147-89 Encryption Algorithm

+ * + * References {@link http://tools.ietf.org/html/rfc5830}

+ * + * When keys and initialization vectors are converted to/from byte arrays, + * little-endian byte order is assumed.

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST 28147' or 'GOST R 34.12'
  • + *
  • version Algorithm version, number + *
      + *
    • 1989 Current version of standard
    • + *
    • 2015 New draft version of standard
    • + *
    + *
  • + *
  • length Block length + *
      + *
    • 64 64 bits length (default)
    • + *
    • 128 128 bits length (only for version 2015)
    • + *
    + *
  • + *
  • mode Algorithm mode, string + *
      + *
    • ES Encryption mode (default)
    • + *
    • MAC "imitovstavka" (MAC) mode
    • + *
    • KW Key wrapping mode
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 28147-89, string. Used only if version = 1989
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Encript/Decrypt mode (ES) + *
      + *
    • block Block mode, string. Default ECB
    • + *
    • keyMeshing Key meshing mode, string. Default NO
    • + *
    • padding Padding mode, string. Default NO for CFB and CTR modes, or ZERO for others
    • + *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • + *
    + *
  • + *
  • Sign/Verify mode (MAC) + *
      + *
    • macLength Length of mac in bits (default - 32 bits)
    • + *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • + *
    + *
  • + *
  • Wrap/Unwrap key mode (KW) + *
      + *
    • keyWrapping Mode of keywrapping, string. Default NO - standard GOST key wrapping
    • + *
    • ukm {@link CryptoOperationData} User key material. Default - random generated value
    • + *
    + *
  • + *
+ * + * Supported paramters values: + * + *
    + *
  • Block modes (parameter 'block') + *
      + *
    • ECB "prostaya zamena" (ECB) mode (default)
    • + *
    • CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
    • + *
    • OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
    • + *
    • CTR "gammirovanie" (counter) mode
    • + *
    • CBC Cipher-Block-Chaining (CBC) mode
    • + *
    + *
  • + *
  • Key meshing modes (parameter 'keyMeshing') + *
      + *
    • NO No key wrapping (default)
    • + *
    • CP CryptoPor Key key meshing
    • + *
    + *
  • + *
  • Padding modes (parameter 'padding') + *
      + *
    • NO No padding only for CFB, OFB and CTR modes
    • + *
    • PKCS5 PKCS#5 padding mode
    • + *
    • ZERO Zero bits padding mode
    • + *
    • RANDOM Random bits padding mode
    • + *
    • BIT One bit padding mode
    • + *
    + *
  • + *
  • Wrapping key modes (parameter 'keyWrapping') + *
      + *
    • NO Ref. rfc4357 6.1 GOST 28147-89 Key wrapping
    • + *
    • CP CryptoPro Key wrapping mode
    • + *
    • SC SignalCom Key wrapping mode
    • + *
    + *
  • + *
+ * + * @class GostCipher + * @param {AlgorithmIndentifier} algorithm WebCryptoAPI algorithm identifier + */ +function GostCipher(algorithm) // +{ + // Check little endian support + if (!littleEndian) + throw new NotSupportedError('Big endian platform not supported'); + algorithm = algorithm || {}; + this.keySize = 32; + this.blockLength = algorithm.length || 64; + this.blockSize = this.blockLength >> 3; + + this.name = (algorithm.name || (algorithm.version === 1 ? 'RC2' : + algorithm.version === 1989 ? 'GOST 28147' : 'GOST R 34.12')) + + (algorithm.version > 4 ? '-' + ((algorithm.version || 1989) % 100) : '') + '-' + + (this.blockLength === 64 ? '' : this.blockLength + '-') + + ((algorithm.mode === 'MAC') ? 'MAC-' + (algorithm.macLength || this.blockLength >> 1) : + (algorithm.mode === 'KW' || algorithm.keyWrapping) ? + ((algorithm.keyWrapping || 'NO') !== 'NO' ? algorithm.keyWrapping : '') + 'KW' : + (algorithm.block || 'ECB') + ((algorithm.block === 'CFB' || algorithm.block === 'OFB' || + (algorithm.block === 'CTR' && algorithm.version === 2015)) && + algorithm?.shiftBits !== this.blockLength ? '-' + algorithm.shiftBits : '') + + (algorithm.padding ? '-' + (algorithm.padding || (algorithm.block === 'CTR' || + algorithm.block === 'CFB' || algorithm.block === 'OFB' ? 'NO' : 'ZERO')) + 'PADDING' : '') + + ((algorithm.keyMeshing || 'NO') !== 'NO' ? '-CPKEYMESHING' : '')) + + (algorithm.procreator ? '/' + algorithm.procreator : '') + + (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : ''); + + // Algorithm procreator + this.procreator = algorithm.procreator; + + switch (algorithm.version || 1989) { + case 1: + this.process = processRC2; + this.keySchedule = keyScheduleRC2; + this.blockLength = 64; + this.effectiveLength = algorithm.length || 32; + this.keySize = 8 * Math.ceil(this.effectiveLength / 8); // Max 128 + this.blockSize = this.blockLength >> 3; + break; + case 2015: + this.version = 2015; + if (this.blockLength === 64) { + this.process = process15; + this.keySchedule = keySchedule15; + } else if (this.blockLength === 128) { + this.process = process128; + this.keySchedule = keySchedule128; + } else + throw new DataError('Invalid block length'); + this.processMAC = processMAC15; + break; + case 1989: + this.version = 1989; + this.process = process89; + this.processMAC = processMAC89; + this.keySchedule = keySchedule89; + if (this.blockLength !== 64) + throw new DataError('Invalid block length'); + break; + default: + throw new NotSupportedError('Algorithm version ' + algorithm.version + ' not supported'); + } + + switch (algorithm.mode || (algorithm.keyWrapping && 'KW') || 'ES') { + + case 'ES': + switch (algorithm.block || 'ECB') { + case 'ECB': + this.encrypt = encryptECB; + this.decrypt = decryptECB; + break; + case 'CTR': + if (this.version === 1989) { + this.encrypt = processCTR89; + this.decrypt = processCTR89; + } else { + this.encrypt = processCTR15; + this.decrypt = processCTR15; + this.shiftBits = algorithm.shiftBits || this.blockLength; + } + break + case 'CBC': + this.encrypt = encryptCBC; + this.decrypt = decryptCBC; + break + case 'CFB': + this.encrypt = encryptCFB; + this.decrypt = decryptCFB; + this.shiftBits = algorithm.shiftBits || this.blockLength; + break; + case 'OFB': + this.encrypt = processOFB; + this.decrypt = processOFB; + this.shiftBits = algorithm.shiftBits || this.blockLength; + break; + default: + throw new NotSupportedError('Block mode ' + algorithm.block + ' not supported'); + } + switch (algorithm.keyMeshing) { + case 'CP': + this.keyMeshing = keyMeshingCP; + break; + default: + this.keyMeshing = noKeyMeshing; + } + if (this.encrypt === encryptECB || this.encrypt === encryptCBC) { + switch (algorithm.padding) { + case 'PKCS5P': + this.pad = pkcs5Pad; + this.unpad = pkcs5Unpad; + break; + case 'RANDOM': + this.pad = randomPad; + this.unpad = noPad; + break; + case 'BIT': + this.pad = bitPad; + this.unpad = bitUnpad; + break; + default: + this.pad = zeroPad; + this.unpad = noPad; + } + } else { + this.pad = noPad; + this.unpad = noPad; + } + this.generateKey = generateKey; + break; + case 'MAC': + this.sign = signMAC; + this.verify = verifyMAC; + this.generateKey = generateKey; + this.macLength = algorithm.macLength || (this.blockLength >> 1); + this.pad = noPad; + this.unpad = noPad; + this.keyMeshing = noKeyMeshing; + break; + case 'KW': + this.pad = noPad; + this.unpad = noPad; + this.keyMeshing = noKeyMeshing; + switch (algorithm.keyWrapping) { + case 'CP': + this.wrapKey = wrapKeyCP; + this.unwrapKey = unwrapKeyCP; + this.generateKey = generateKey; + this.shiftBits = algorithm.shiftBits || this.blockLength; + break; + case 'SC': + this.wrapKey = wrapKeySC; + this.unwrapKey = unwrapKeySC; + this.generateKey = generateWrappingKeySC; + break; + default: + this.wrapKey = wrapKeyGOST; + this.unwrapKey = unwrapKeyGOST; + this.generateKey = generateKey; + } + break; + case 'MASK': + this.wrapKey = wrapKeyMask; + this.unwrapKey = unwrapKeyMask; + this.generateKey = generateKey; + break; + default: + throw new NotSupportedError('Mode ' + algorithm.mode + ' not supported'); + } + + // Define sBox parameter + var sBox = algorithm.sBox, sBoxName; + if (!sBox) + sBox = this.version === 2015 ? sBoxes['E-Z'] : this.procreator === 'SC' ? sBoxes['E-SC'] : sBoxes['E-A']; + else if (typeof sBox === 'string') { + sBoxName = sBox.toUpperCase(); + sBox = sBoxes[sBoxName]; + if (!sBox) + throw new SyntaxError('Unknown sBox name: ' + algorithm.sBox); + } else if (!sBox.length || sBox.length !== sBoxes['E-Z'].length) + throw new SyntaxError('Length of sBox must be ' + sBoxes['E-Z'].length); + this.sBox = sBox; + // Initial vector + if (algorithm.iv) { + this.iv = new Uint8Array(algorithm.iv); + if (this.iv.byteLength !== this.blockSize && this.version === 1989) + throw new SyntaxError('Length of iv must be ' + this.blockLength + ' bits'); + else if (this.iv.byteLength !== this.blockSize >> 1 && this.encrypt === processCTR15) + throw new SyntaxError('Length of iv must be ' + this.blockLength >> 1 + ' bits'); + else if (this.iv.byteLength % this.blockSize !== 0 && this.encrypt !== processCTR15) + throw new SyntaxError('Length of iv must be a multiple of ' + this.blockLength + ' bits'); + } else + this.iv = this.blockLength === 128 ? defaultIV128 : defaultIV; + // User key material + if (algorithm.ukm) { + this.ukm = new Uint8Array(algorithm.ukm); + if (this.ukm.byteLength * 8 !== this.blockLength) + throw new SyntaxError('Length of ukm must be ' + this.blockLength + ' bits'); + } +} // + +export default GostCipher; diff --git a/src/core/vendor/gost/gostCoding.mjs b/src/core/vendor/gost/gostCoding.mjs new file mode 100644 index 00000000..ed42b3cc --- /dev/null +++ b/src/core/vendor/gost/gostCoding.mjs @@ -0,0 +1,1160 @@ +/** + * Coding algorithms: Base64, Hex, Int16, Chars, BER and PEM + * version 1.76 + * 2014-2016, Rudolf Nickolaev. All rights reserved. + * + * Exported for CyberChef by mshwed [m@ttshwed.com] + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 HOLDER 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. + * + */ + +import gostCrypto from './gostCrypto.mjs'; + +/** + * The Coding interface provides string converting methods: Base64, Hex, + * Int16, Chars, BER and PEM + * @class GostCoding + * + */ // +var root = {}; +var DataError = Error; +var CryptoOperationData = ArrayBuffer; +var Date = Date; + +function buffer(d) { + if (d instanceof CryptoOperationData) + return d; + else if (d && d?.buffer instanceof CryptoOperationData) + return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? + d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; + else + throw new DataError('CryptoOperationData required'); +} // + +function GostCoding() { +} + +/** + * BASE64 conversion + * + * @class GostCoding.Base64 + */ +var Base64 = {// + /** + * Base64.decode convert BASE64 string s to CryptoOperationData + * + * @memberOf GostCoding.Base64 + * @param {String} s BASE64 encoded string value + * @returns {CryptoOperationData} Binary decoded data + */ + decode: function (s) { + s = s.replace(/[^A-Za-z0-9\+\/]/g, ''); + var n = s.length, + k = n * 3 + 1 >> 2, r = new Uint8Array(k); + + for (var m3, m4, u24 = 0, j = 0, i = 0; i < n; i++) { + m4 = i & 3; + var c = s.charCodeAt(i); + + c = c > 64 && c < 91 ? + c - 65 : c > 96 && c < 123 ? + c - 71 : c > 47 && c < 58 ? + c + 4 : c === 43 ? + 62 : c === 47 ? + 63 : 0; + + u24 |= c << 18 - 6 * m4; + if (m4 === 3 || n - i === 1) { + for (m3 = 0; m3 < 3 && j < k; m3++, j++) { + r[j] = u24 >>> (16 >>> m3 & 24) & 255; + } + u24 = 0; + + } + } + return r.buffer; + }, + /** + * Base64.encode(data) convert CryptoOperationData data to BASE64 string + * + * @memberOf GostCoding.Base64 + * @param {CryptoOperationData} data Bynary data for encoding + * @returns {String} BASE64 encoded data + */ + encode: function (data) { + var slen = 8, d = new Uint8Array(buffer(data)); + var m3 = 2, s = ''; + for (var n = d.length, u24 = 0, i = 0; i < n; i++) { + m3 = i % 3; + if (i > 0 && (i * 4 / 3) % (12 * slen) === 0) + s += '\r\n'; + u24 |= d[i] << (16 >>> m3 & 24); + if (m3 === 2 || n - i === 1) { + for (var j = 18; j >= 0; j -= 6) { + var c = u24 >>> j & 63; + c = c < 26 ? c + 65 : c < 52 ? c + 71 : c < 62 ? c - 4 : + c === 62 ? 43 : c === 63 ? 47 : 65; + s += String.fromCharCode(c); + } + u24 = 0; + } + } + return s.substr(0, s.length - 2 + m3) + (m3 === 2 ? '' : m3 === 1 ? '=' : '=='); + } // +}; + +/** + * BASE64 conversion + * + * @memberOf GostCoding + * @insnance + * @type GostCoding.Base64 + */ +GostCoding.prototype.Base64 = Base64; + +/** + * Text string conversion
+ * Methods support charsets: ascii, win1251, utf8, utf16 (ucs2, unicode), utf32 (ucs4) + * + * @class GostCoding.Chars + */ +var Chars = (function () { // + + var _win1251_ = { + 0x402: 0x80, 0x403: 0x81, 0x201A: 0x82, 0x453: 0x83, 0x201E: 0x84, 0x2026: 0x85, 0x2020: 0x86, 0x2021: 0x87, + 0x20AC: 0x88, 0x2030: 0x89, 0x409: 0x8A, 0x2039: 0x8B, 0x40A: 0x8C, 0x40C: 0x8D, 0x40B: 0x8E, 0x40f: 0x8f, + 0x452: 0x90, 0x2018: 0x91, 0x2019: 0x92, 0x201C: 0x93, 0x201D: 0x94, 0x2022: 0x95, 0x2013: 0x96, 0x2014: 0x97, + 0x2122: 0x99, 0x459: 0x9A, 0x203A: 0x9B, 0x45A: 0x9C, 0x45C: 0x9D, 0x45B: 0x9E, 0x45f: 0x9f, + 0xA0: 0xA0, 0x40E: 0xA1, 0x45E: 0xA2, 0x408: 0xA3, 0xA4: 0xA4, 0x490: 0xA5, 0xA6: 0xA6, 0xA7: 0xA7, + 0x401: 0xA8, 0xA9: 0xA9, 0x404: 0xAA, 0xAB: 0xAB, 0xAC: 0xAC, 0xAD: 0xAD, 0xAE: 0xAE, 0x407: 0xAf, + 0xB0: 0xB0, 0xB1: 0xB1, 0x406: 0xB2, 0x456: 0xB3, 0x491: 0xB4, 0xB5: 0xB5, 0xB6: 0xB6, 0xB7: 0xB7, + 0x451: 0xB8, 0x2116: 0xB9, 0x454: 0xBA, 0xBB: 0xBB, 0x458: 0xBC, 0x405: 0xBD, 0x455: 0xBE, 0x457: 0xBf + }; + var _win1251back_ = {}; + for (var from in _win1251_) { + var to = _win1251_[from]; + _win1251back_[to] = from; + } + + return { + /** + * Chars.decode(s, charset) convert string s with defined charset to CryptoOperationData + * + * @memberOf GostCoding.Chars + * @param {string} s Javascript string + * @param {string} charset Charset, default 'win1251' + * @returns {CryptoOperationData} Decoded binary data + */ + decode: function (s, charset) { + charset = (charset || 'win1251').toLowerCase().replace('-', ''); + var r = []; + for (var i = 0, j = s.length; i < j; i++) { + var c = s.charCodeAt(i); + if (charset === 'utf8') { + if (c < 0x80) { + r.push(c); + } else if (c < 0x800) { + r.push(0xc0 + (c >>> 6)); + r.push(0x80 + (c & 63)); + } else if (c < 0x10000) { + r.push(0xe0 + (c >>> 12)); + r.push(0x80 + (c >>> 6 & 63)); + r.push(0x80 + (c & 63)); + } else if (c < 0x200000) { + r.push(0xf0 + (c >>> 18)); + r.push(0x80 + (c >>> 12 & 63)); + r.push(0x80 + (c >>> 6 & 63)); + r.push(0x80 + (c & 63)); + } else if (c < 0x4000000) { + r.push(0xf8 + (c >>> 24)); + r.push(0x80 + (c >>> 18 & 63)); + r.push(0x80 + (c >>> 12 & 63)); + r.push(0x80 + (c >>> 6 & 63)); + r.push(0x80 + (c & 63)); + } else { + r.push(0xfc + (c >>> 30)); + r.push(0x80 + (c >>> 24 & 63)); + r.push(0x80 + (c >>> 18 & 63)); + r.push(0x80 + (c >>> 12 & 63)); + r.push(0x80 + (c >>> 6 & 63)); + r.push(0x80 + (c & 63)); + } + } else if (charset === 'unicode' || charset === 'ucs2' || charset === 'utf16') { + if (c < 0xD800 || (c >= 0xE000 && c <= 0x10000)) { + r.push(c >>> 8); + r.push(c & 0xff); + } else if (c >= 0x10000 && c < 0x110000) { + c -= 0x10000; + var first = ((0xffc00 & c) >> 10) + 0xD800; + var second = (0x3ff & c) + 0xDC00; + r.push(first >>> 8); + r.push(first & 0xff); + r.push(second >>> 8); + r.push(second & 0xff); + } + } else if (charset === 'utf32' || charset === 'ucs4') { + r.push(c >>> 24 & 0xff); + r.push(c >>> 16 & 0xff); + r.push(c >>> 8 & 0xff); + r.push(c & 0xff); + } else if (charset === 'win1251') { + if (c >= 0x80) { + if (c >= 0x410 && c < 0x450) // А..Яа..я + c -= 0x350; + else + c = _win1251_[c] || 0; + } + r.push(c); + } else + r.push(c & 0xff); + } + return new Uint8Array(r).buffer; + }, + /** + * Chars.encode(data, charset) convert CryptoOperationData data to string with defined charset + * + * @memberOf GostCoding.Chars + * @param {CryptoOperationData} data Binary data + * @param {string} charset Charset, default win1251 + * @returns {string} Encoded javascript string + */ + encode: function (data, charset) { + charset = (charset || 'win1251').toLowerCase().replace('-', ''); + var r = [], d = new Uint8Array(buffer(data)); + for (var i = 0, n = d.length; i < n; i++) { + var c = d[i]; + if (charset === 'utf8') { + c = c >= 0xfc && c < 0xfe && i + 5 < n ? // six bytes + (c - 0xfc) * 1073741824 + (d[++i] - 0x80 << 24) + (d[++i] - 0x80 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 + : c >> 0xf8 && c < 0xfc && i + 4 < n ? // five bytes + (c - 0xf8 << 24) + (d[++i] - 0x80 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 + : c >> 0xf0 && c < 0xf8 && i + 3 < n ? // four bytes + (c - 0xf0 << 18) + (d[++i] - 0x80 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 + : c >= 0xe0 && c < 0xf0 && i + 2 < n ? // three bytes + (c - 0xe0 << 12) + (d[++i] - 0x80 << 6) + d[++i] - 0x80 + : c >= 0xc0 && c < 0xe0 && i + 1 < n ? // two bytes + (c - 0xc0 << 6) + d[++i] - 0x80 + : c; // one byte + } else if (charset === 'unicode' || charset === 'ucs2' || charset === 'utf16') { + c = (c << 8) + d[++i]; + if (c >= 0xD800 && c < 0xE000) { + var first = (c - 0xD800) << 10; + c = d[++i]; + c = (c << 8) + d[++i]; + var second = c - 0xDC00; + c = first + second + 0x10000; + } + } else if (charset === 'utf32' || charset === 'ucs4') { + c = (c << 8) + d[++i]; + c = (c << 8) + d[++i]; + c = (c << 8) + d[++i]; + } else if (charset === 'win1251') { + if (c >= 0x80) { + if (c >= 0xC0 && c < 0x100) + c += 0x350; // А..Яа..я + else + c = _win1251back_[c] || 0; + } + } + r.push(String.fromCharCode(c)); + } + return r.join(''); + } + }; // +})(); + +/** + * Text string conversion + * + * @memberOf GostCoding + * @insnance + * @type GostCoding.Chars + */ +GostCoding.prototype.Chars = Chars; + +/** + * HEX conversion + * + * @class GostCoding.Hex + */ +var Hex = {// + /** + * Hex.decode(s, endean) convert HEX string s to CryptoOperationData in endean mode + * + * @memberOf GostCoding.Hex + * @param {string} s Hex encoded string + * @param {boolean} endean Little or Big Endean, default Little + * @returns {CryptoOperationData} Decoded binary data + */ + decode: function (s, endean) { + s = s.replace(/[^A-Fa-f0-9]/g, ''); + var n = Math.ceil(s.length / 2), r = new Uint8Array(n); + s = (s.length % 2 > 0 ? '0' : '') + s; + if (endean && ((typeof endean !== 'string') || + (endean.toLowerCase().indexOf('little') < 0))) + for (var i = 0; i < n; i++) + r[i] = parseInt(s.substr((n - i - 1) * 2, 2), 16); + else + for (var i = 0; i < n; i++) + r[i] = parseInt(s.substr(i * 2, 2), 16); + return r.buffer; + }, + /** + * Hex.encode(data, endean) convert CryptoOperationData data to HEX string in endean mode + * + * @memberOf GostCoding.Hex + * @param {CryptoOperationData} data Binary data + * @param {boolean} endean Little/Big Endean, default Little + * @returns {string} Hex decoded string + */ + encode: function (data, endean) { + var s = [], d = new Uint8Array(buffer(data)), n = d.length; + if (endean && ((typeof endean !== 'string') || + (endean.toLowerCase().indexOf('little') < 0))) + for (var i = 0; i < n; i++) { + var j = n - i - 1; + s[j] = (j > 0 && j % 32 === 0 ? '\r\n' : '') + + ('00' + d[i].toString(16)).slice(-2); + } + else + for (var i = 0; i < n; i++) + s[i] = (i > 0 && i % 32 === 0 ? '\r\n' : '') + + ('00' + d[i].toString(16)).slice(-2); + return s.join(''); + } // +}; + +/** + * HEX conversion + * @memberOf GostCoding + * @insnance + * @type GostCoding.Hex + */ +GostCoding.prototype.Hex = Hex; + +/** + * String hex-encoded integer conversion + * + * @class GostCoding.Int16 + */ +var Int16 = {// + /** + * Int16.decode(s) convert hex big insteger s to CryptoOperationData + * + * @memberOf GostCoding.Int16 + * @param {string} s Int16 string + * @returns {CryptoOperationData} Decoded binary data + */ + decode: function (s) { + s = (s || '').replace(/[^\-A-Fa-f0-9]/g, ''); + if (s.length === 0) + s = '0'; + // Signature + var neg = false; + if (s.charAt(0) === '-') { + neg = true; + s = s.substring(1); + } + // Align 2 chars + while (s.charAt(0) === '0' && s.length > 1) + s = s.substring(1); + s = (s.length % 2 > 0 ? '0' : '') + s; + // Padding for singanuture + // '800000' - 'ffffff' - for positive + // '800001' - 'ffffff' - for negative + if ((!neg && !/^[0-7]/.test(s)) || + (neg && !/^[0-7]|8[0]+$/.test(s))) + s = '00' + s; + // Convert hex + var n = s.length / 2, r = new Uint8Array(n), t = 0; + for (var i = n - 1; i >= 0; --i) { + var c = parseInt(s.substr(i * 2, 2), 16); + if (neg && (c + t > 0)) { + c = 256 - c - t; + t = 1; + } + r[i] = c; + } + return r.buffer; + }, + /** + * Int16.encode(data) convert CryptoOperationData data to big integer hex string + * + * @memberOf GostCoding.Int16 + * @param {CryptoOperationData} data Binary data + * @returns {string} Int16 encoded string + */ + encode: function (data) { + var d = new Uint8Array(buffer(data)), n = d.length; + if (d.length === 0) + return '0x00'; + var s = [], neg = d[0] > 0x7f, t = 0; + for (var i = n - 1; i >= 0; --i) { + var v = d[i]; + if (neg && (v + t > 0)) { + v = 256 - v - t; + t = 1; + } + s[i] = ('00' + v.toString(16)).slice(-2); + } + s = s.join(''); + while (s.charAt(0) === '0') + s = s.substring(1); + return (neg ? '-' : '') + '0x' + s; + } // +}; + +/** + * String hex-encoded integer conversion + * @memberOf GostCoding + * @insnance + * @type GostCoding.Int16 + */ +GostCoding.prototype.Int16 = Int16; + +/** + * BER, DER, CER conversion + * + * @class GostCoding.BER + */ +var BER = (function () { // + + // Predefenition block + function encodeBER(source, format, onlyContent) { + // Correct primitive type + var object = source.object; + if (object === undefined) + object = source; + + // Determinate tagClass + var tagClass = source.tagClass = source.tagClass || 0; // Universial default + + // Determinate tagNumber. Use only for Universal class + if (tagClass === 0) { + var tagNumber = source.tagNumber; + if (typeof tagNumber === 'undefined') { + if (typeof object === 'string') { + if (object === '') // NULL + tagNumber = 0x05; + else if (/^\-?0x[0-9a-fA-F]+$/.test(object)) // INTEGER + tagNumber = 0x02; + else if (/^(\d+\.)+\d+$/.test(object)) // OID + tagNumber = 0x06; + else if (/^[01]+$/.test(object)) // BIT STRING + tagNumber = 0x03; + else if (/^(true|false)$/.test(object)) // BOOLEAN + tagNumber = 0x01; + else if (/^[0-9a-fA-F]+$/.test(object)) // OCTET STRING + tagNumber = 0x04; + else + tagNumber = 0x13; // Printable string (later can be changed to UTF8String) + } else if (typeof object === 'number') { // INTEGER + tagNumber = 0x02; + } else if (typeof object === 'boolean') { // BOOLEAN + tagNumber = 0x01; + } else if (object instanceof Array) { // SEQUENCE + tagNumber = 0x10; + } else if (object instanceof Date) { // GeneralizedTime + tagNumber = 0x18; + } else if (object instanceof CryptoOperationData || (object && object.buffer instanceof CryptoOperationData)) { + tagNumber = 0x04; + } else + throw new DataError('Unrecognized type for ' + object); + } + } + + // Determinate constructed + var tagConstructed = source.tagConstructed; + if (typeof tagConstructed === 'undefined') + tagConstructed = source.tagConstructed = object instanceof Array; + + // Create content + var content; + if (object instanceof CryptoOperationData || (object && object.buffer instanceof CryptoOperationData)) { // Direct + content = new Uint8Array(buffer(object)); + if (tagNumber === 0x03) { // BITSTRING + // Set unused bits + var a = new Uint8Array(buffer(content)); + content = new Uint8Array(a.length + 1); + content[0] = 0; // No unused bits + content.set(a, 1); + } + } else if (tagConstructed) { // Sub items coding + if (object instanceof Array) { + var bytelen = 0, ba = [], offset = 0; + for (var i = 0, n = object.length; i < n; i++) { + ba[i] = encodeBER(object[i], format); + bytelen += ba[i].length; + } + if (tagNumber === 0x11) + ba.sort(function (a, b) { // Sort order for SET components + for (var i = 0, n = Math.min(a.length, b.length); i < n; i++) { + var r = a[i] - b[i]; + if (r !== 0) + return r; + } + return a.length - b.length; + }); + if (format === 'CER') { // final for CER 00 00 + ba[n] = new Uint8Array(2); + bytelen += 2; + } + content = new Uint8Array(bytelen); + for (var i = 0, n = ba.length; i < n; i++) { + content.set(ba[i], offset); + offset = offset + ba[i].length; + } + } else + throw new DataError('Constracted block can\'t be primitive'); + } else { + switch (tagNumber) { + // 0x00: // EOC + case 0x01: // BOOLEAN + content = new Uint8Array(1); + content[0] = object ? 0xff : 0; + break; + case 0x02: // INTEGER + case 0x0a: // ENUMIRATED + content = Int16.decode( + typeof object === 'number' ? object.toString(16) : object); + break; + case 0x03: // BIT STRING + if (typeof object === 'string') { + var unusedBits = 7 - (object.length + 7) % 8; + var n = Math.ceil(object.length / 8); + content = new Uint8Array(n + 1); + content[0] = unusedBits; + for (var i = 0; i < n; i++) { + var c = 0; + for (var j = 0; j < 8; j++) { + var k = i * 8 + j; + c = (c << 1) + (k < object.length ? (object.charAt(k) === '1' ? 1 : 0) : 0); + } + content[i + 1] = c; + } + } + break; + case 0x04: + content = Hex.decode( + typeof object === 'number' ? object.toString(16) : object); + break; + // case 0x05: // NULL + case 0x06: // OBJECT IDENTIFIER + var a = object.match(/\d+/g), r = []; + for (var i = 1; i < a.length; i++) { + var n = +a[i], r1 = []; + if (i === 1) + n = n + a[0] * 40; + do { + r1.push(n & 0x7F); + n = n >>> 7; + } while (n); + // reverse order + for (j = r1.length - 1; j >= 0; --j) + r.push(r1[j] + (j === 0 ? 0x00 : 0x80)); + } + content = new Uint8Array(r); + break; + // case 0x07: // ObjectDescriptor + // case 0x08: // EXTERNAL + // case 0x09: // REAL + // case 0x0A: // ENUMERATED + // case 0x0B: // EMBEDDED PDV + case 0x0C: // UTF8String + content = Chars.decode(object, 'utf8'); + break; + // case 0x10: // SEQUENCE + // case 0x11: // SET + case 0x12: // NumericString + case 0x16: // IA5String // ASCII + case 0x13: // PrintableString // ASCII subset + case 0x14: // TeletexString // aka T61String + case 0x15: // VideotexString + case 0x19: // GraphicString + case 0x1A: // VisibleString // ASCII subset + case 0x1B: // GeneralString + // Reflect on character encoding + for (var i = 0, n = object.length; i < n; i++) + if (object.charCodeAt(i) > 255) + tagNumber = 0x0C; + if (tagNumber === 0x0C) + content = Chars.decode(object, 'utf8'); + else + content = Chars.decode(object, 'ascii'); + break; + case 0x17: // UTCTime + case 0x18: // GeneralizedTime + var result = object.original; + if (!result) { + var date = new Date(object); + date.setMinutes(date.getMinutes() + date.getTimezoneOffset()); // to UTC + var ms = tagNumber === 0x18 ? date.getMilliseconds().toString() : ''; // Milliseconds, remove trailing zeros + while (ms.length > 0 && ms.charAt(ms.length - 1) === '0') + ms = ms.substring(0, ms.length - 1); + if (ms.length > 0) + ms = '.' + ms; + result = (tagNumber === 0x17 ? date.getYear().toString().slice(-2) : date.getFullYear().toString()) + + ('00' + (date.getMonth() + 1)).slice(-2) + + ('00' + date.getDate()).slice(-2) + + ('00' + date.getHours()).slice(-2) + + ('00' + date.getMinutes()).slice(-2) + + ('00' + date.getSeconds()).slice(-2) + ms + 'Z'; + } + content = Chars.decode(result, 'ascii'); + break; + case 0x1C: // UniversalString + content = Chars.decode(object, 'utf32'); + break; + case 0x1E: // BMPString + content = Chars.decode(object, 'utf16'); + break; + } + } + + if (!content) + content = new Uint8Array(0); + if (content instanceof CryptoOperationData) + content = new Uint8Array(content); + + if (!tagConstructed && format === 'CER') { + // Encoding CER-form for string types + var k; + switch (tagNumber) { + case 0x03: // BIT_STRING + k = 1; // ingnore unused bit for bit string + case 0x04: // OCTET_STRING + case 0x0C: // UTF8String + case 0x12: // NumericString + case 0x13: // PrintableString + case 0x14: // TeletexString + case 0x15: // VideotexString + case 0x16: // IA5String + case 0x19: // GraphicString + case 0x1A: // VisibleString + case 0x1B: // GeneralString + case 0x1C: // UniversalString + case 0x1E: // BMPString + k = k || 0; + // Split content on 1000 octet len parts + var size = 1000; + var bytelen = 0, ba = [], offset = 0; + for (var i = k, n = content.length; i < n; i += size - k) { + ba[i] = encodeBER({ + object: new Unit8Array(content.buffer, i, Math.min(size - k, n - i)), + tagNumber: tagNumber, + tagClass: 0, + tagConstructed: false + }, format); + bytelen += ba[i].length; + } + ba[n] = new Uint8Array(2); // final for CER 00 00 + bytelen += 2; + content = new Uint8Array(bytelen); + for (var i = 0, n = ba.length; i < n; i++) { + content.set(ba[i], offset); + offset = offset + ba[i].length; + } + } + } + + // Restore tagNumber for all classes + if (tagClass === 0) + source.tagNumber = tagNumber; + else + source.tagNumber = tagNumber = source.tagNumber || 0; + source.content = content; + + if (onlyContent) + return content; + + // Create header + // tagNumber + var ha = [], first = tagClass === 3 ? 0xC0 : tagClass === 2 ? 0x80 : + tagClass === 1 ? 0x40 : 0x00; + if (tagConstructed) + first |= 0x20; + if (tagNumber < 0x1F) { + first |= tagNumber & 0x1F; + ha.push(first); + } else { + first |= 0x1F; + ha.push(first); + var n = tagNumber, ha1 = []; + do { + ha1.push(n & 0x7F); + n = n >>> 7; + } while (n) + // reverse order + for (var j = ha1.length - 1; j >= 0; --j) + ha.push(ha1[j] + (j === 0 ? 0x00 : 0x80)); + } + // Length + if (tagConstructed && format === 'CER') { + ha.push(0x80); + } else { + var len = content.length; + if (len > 0x7F) { + var l2 = len, ha2 = []; + do { + ha2.push(l2 & 0xff); + l2 = l2 >>> 8; + } while (l2); + ha.push(ha2.length + 0x80); // reverse order + for (var j = ha2.length - 1; j >= 0; --j) + ha.push(ha2[j]); + } else { + // simple len + ha.push(len); + } + } + var header = source.header = new Uint8Array(ha); + + // Result - complete buffer + var block = new Uint8Array(header.length + content.length); + block.set(header, 0); + block.set(content, header.length); + return block; + } + + function decodeBER(source, offset) { + + // start pos + var pos = offset || 0, start = pos; + var tagNumber, tagClass, tagConstructed, + content, header, buffer, sub, len; + + if (source.object) { + // Ready from source + tagNumber = source.tagNumber; + tagClass = source.tagClass; + tagConstructed = source.tagConstructed; + content = source.content; + header = source.header; + buffer = source.object instanceof CryptoOperationData ? + new Uint8Array(source.object) : null; + sub = source.object instanceof Array ? source.object : null; + len = buffer && buffer.length || null; + } else { + // Decode header + var d = source; + + // Read tag + var buf = d[pos++]; + tagNumber = buf & 0x1f; + tagClass = buf >> 6; + tagConstructed = (buf & 0x20) !== 0; + if (tagNumber === 0x1f) { // long tag + tagNumber = 0; + do { + if (tagNumber > 0x1fffffffffff80) + throw new DataError('Convertor not supported tag number more then (2^53 - 1) at position ' + offset); + buf = d[pos++]; + tagNumber = (tagNumber << 7) + (buf & 0x7f); + } while (buf & 0x80); + } + + // Read len + buf = d[pos++]; + len = buf & 0x7f; + if (len !== buf) { + if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways + throw new DataError('Length over 48 bits not supported at position ' + offset); + if (len === 0) + len = null; // undefined + else { + buf = 0; + for (var i = 0; i < len; ++i) + buf = (buf << 8) + d[pos++]; + len = buf; + } + } + + start = pos; + sub = null; + + if (tagConstructed) { + // must have valid content + sub = []; + if (len !== null) { + // definite length + var end = start + len; + while (pos < end) { + var s = decodeBER(d, pos); + sub.push(s); + pos += s.header.length + s.content.length; + } + if (pos !== end) + throw new DataError('Content size is not correct for container starting at offset ' + start); + } else { + // undefined length + try { + for (; ; ) { + var s = decodeBER(d, pos); + pos += s.header.length + s.content.length; + if (s.tagClass === 0x00 && s.tagNumber === 0x00) + break; + sub.push(s); + } + len = pos - start; + } catch (e) { + throw new DataError('Exception ' + e + ' while decoding undefined length content at offset ' + start); + } + } + } + + // Header and content + header = new Uint8Array(d.buffer, offset, start - offset); + content = new Uint8Array(d.buffer, start, len); + buffer = content; + } + + // Constructed types - check for string concationation + if (sub !== null && tagClass === 0) { + var k; + switch (tagNumber) { + case 0x03: // BIT_STRING + k = 1; // ingnore unused bit for bit string + case 0x04: // OCTET_STRING + case 0x0C: // UTF8String + case 0x12: // NumericString + case 0x13: // PrintableString + case 0x14: // TeletexString + case 0x15: // VideotexString + case 0x16: // IA5String + case 0x19: // GraphicString + case 0x1A: // VisibleString + case 0x1B: // GeneralString + case 0x1C: // UniversalString + case 0x1E: // BMPString + k = k || 0; + // Concatination + if (sub.length === 0) + throw new DataError('No constructed encoding content of string type at offset ' + start); + len = k; + for (var i = 0, n = sub.length; i < n; i++) { + var s = sub[i]; + if (s.tagClass !== tagClass || s.tagNumber !== tagNumber || s.tagConstructed) + throw new DataError('Invalid constructed encoding of string type at offset ' + start); + len += s.content.length - k; + } + buffer = new Uint8Array(len); + for (var i = 0, n = sub.length, j = k; i < n; i++) { + var s = sub[i]; + if (k > 0) + buffer.set(s.content.subarray(1), j); + else + buffer.set(s.content, j); + j += s.content.length - k; + } + tagConstructed = false; // follow not required + sub = null; + break; + } + } + // Primitive types + var object = ''; + if (sub === null) { + if (len === null) + throw new DataError('Invalid tag with undefined length at offset ' + start); + + if (tagClass === 0) { + switch (tagNumber) { + case 0x01: // BOOLEAN + object = buffer[0] !== 0; + break; + case 0x02: // INTEGER + case 0x0a: // ENUMIRATED + if (len > 6) { + object = Int16.encode(buffer); + } else { + var v = buffer[0]; + if (buffer[0] > 0x7f) + v = v - 256; + for (var i = 1; i < len; i++) + v = v * 256 + buffer[i]; + object = v; + } + break; + case 0x03: // BIT_STRING + if (len > 5) { // Content buffer + object = new Uint8Array(buffer.subarray(1)).buffer; + } else { // Max bit mask only for 32 bit + var unusedBit = buffer[0], + skip = unusedBit, s = []; + for (var i = len - 1; i >= 1; --i) { + var b = buffer[i]; + for (var j = skip; j < 8; ++j) + s.push((b >> j) & 1 ? '1' : '0'); + skip = 0; + } + object = s.reverse().join(''); + } + break; + case 0x04: // OCTET_STRING + object = new Uint8Array(buffer).buffer; + break; + // case 0x05: // NULL + case 0x06: // OBJECT_IDENTIFIER + var s = '', + n = 0, + bits = 0; + for (var i = 0; i < len; ++i) { + var v = buffer[i]; + n = (n << 7) + (v & 0x7F); + bits += 7; + if (!(v & 0x80)) { // finished + if (s === '') { + var m = n < 80 ? n < 40 ? 0 : 1 : 2; + s = m + "." + (n - m * 40); + } else + s += "." + n.toString(); + n = 0; + bits = 0; + } + } + if (bits > 0) + throw new DataError('Incompleted OID at offset ' + start); + object = s; + break; + //case 0x07: // ObjectDescriptor + //case 0x08: // EXTERNAL + //case 0x09: // REAL + //case 0x0A: // ENUMERATED + //case 0x0B: // EMBEDDED_PDV + case 0x10: // SEQUENCE + case 0x11: // SET + object = []; + break; + case 0x0C: // UTF8String + object = Chars.encode(buffer, 'utf8'); + break; + case 0x12: // NumericString + case 0x13: // PrintableString + case 0x14: // TeletexString + case 0x15: // VideotexString + case 0x16: // IA5String + case 0x19: // GraphicString + case 0x1A: // VisibleString + case 0x1B: // GeneralString + object = Chars.encode(buffer, 'ascii'); + break; + case 0x1C: // UniversalString + object = Chars.encode(buffer, 'utf32'); + break; + case 0x1E: // BMPString + object = Chars.encode(buffer, 'utf16'); + break; + case 0x17: // UTCTime + case 0x18: // GeneralizedTime + var shortYear = tagNumber === 0x17; + var s = Chars.encode(buffer, 'ascii'), + m = (shortYear ? + /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/ : + /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/).exec(s); + if (!m) + throw new DataError('Unrecognized time format "' + s + '" at offset ' + start); + if (shortYear) { + // Where YY is greater than or equal to 50, the year SHALL be interpreted as 19YY; and + // Where YY is less than 50, the year SHALL be interpreted as 20YY + m[1] = +m[1]; + m[1] += (m[1] < 50) ? 2000 : 1900; + } + var dt = new Date(m[1], +m[2] - 1, +m[3], +(m[4] || '0'), +(m[5] || '0'), +(m[6] || '0'), +(m[7] || '0')), + tz = dt.getTimezoneOffset(); + if (m[8] || tagNumber === 0x17) { + if (m[8].toUpperCase() !== 'Z' && m[9]) { + tz = tz + parseInt(m[9]); + } + dt.setMinutes(dt.getMinutes() - tz); + } + dt.original = s; + object = dt; + break; + } + } else // OCTET_STRING + object = new Uint8Array(buffer).buffer; + } else + object = sub; + + // result + return { + tagConstructed: tagConstructed, + tagClass: tagClass, + tagNumber: tagNumber, + header: header, + content: content, + object: object + }; + } + + return { + /** + * BER.decode(object, format) convert javascript object to ASN.1 format CryptoOperationData

+ * If object has members tagNumber, tagClass and tagConstructed + * it is clear define encoding rules. Else method use defaul rules: + *
    + *
  • Empty string or null - NULL
  • + *
  • String starts with '0x' and has 0-9 and a-f characters - INTEGER
  • + *
  • String like d.d.d.d (d - set of digits) - OBJECT IDENTIFIER
  • + *
  • String with characters 0 and 1 - BIT STRING
  • + *
  • Strings 'true' or 'false' - BOOLEAN
  • + *
  • String has only 0-9 and a-f characters - OCTET STRING
  • + *
  • String has only characters with code 0-255 - PrintableString
  • + *
  • Other strings - UTF8String
  • + *
  • Number - INTEGER
  • + *
  • Date - GeneralizedTime
  • + *
  • Boolean - SEQUENCE
  • + *
  • CryptoOperationData - OCTET STRING
  • + *
+ * SEQUENCE or SET arrays recursively encoded for each item.
+ * OCTET STRING and BIT STRING can presents as array with one item. + * It means encapsulates encoding for child element.
+ * + * If CONTEXT or APPLICATION classes item presents as array with one + * item we use EXPLICIT encoding for element, else IMPLICIT encoding.
+ * + * @memberOf GostCoding.BER + * @param {Object} object Object to encoding + * @param {string} format Encoding rule: 'DER' or 'CER', default 'DER' + * @param {boolean} onlyContent Encode content only, without header + * @returns {CryptoOperationData} BER encoded data + */ + encode: function (object, format, onlyContent) { + return encodeBER(object, format, onlyContent).buffer; + }, + /** + * BER.encode(data) convert ASN.1 format CryptoOperationData data to javascript object

+ * + * Conversion rules to javascript object: + *
    + *
  • BOOLEAN - Boolean object
  • + *
  • INTEGER, ENUMIRATED - Integer object if len <= 6 (48 bits) else Int16 encoded string
  • + *
  • BIT STRING - Integer object if len <= 5 (w/o unsedBit octet - 32 bits) else String like '10111100' or Array with one item in case of incapsulates encoding
  • + *
  • OCTET STRING - Hex encoded string or Array with one item in case of incapsulates encoding
  • + *
  • OBJECT IDENTIFIER - String with object identifier
  • + *
  • SEQUENCE, SET - Array of encoded items
  • + *
  • UTF8String, NumericString, PrintableString, TeletexString, VideotexString, + * IA5String, GraphicString, VisibleString, GeneralString, UniversalString, + * BMPString - encoded String
  • + *
  • UTCTime, GeneralizedTime - Date
  • + *
+ * @memberOf GostCoding.BER + * @param {(CryptoOperationData|GostCoding.BER)} data Binary data to decode + * @returns {Object} Javascript object with result of decoding + */ + decode: function (data) { + return decodeBER(data.object ? data : new Uint8Array(buffer(data)), 0); + } + }; //
+})(); + +/** + * BER, DER, CER conversion + * @memberOf GostCoding + * @insnance + * @type GostCoding.BER + */ +GostCoding.prototype.BER = BER; + +/** + * PEM conversion + * @class GostCoding.PEM + */ +var PEM = {// + /** + * PEM.encode(data, name) encode CryptoOperationData to PEM format with name label + * + * @memberOf GostCoding.PEM + * @param {(Object|CryptoOperationData)} data Java script object or BER-encoded binary data + * @param {string} name Name of PEM object: 'certificate', 'private key' etc. + * @returns {string} Encoded object + */ + encode: function (data, name) { + return (name ? '-----BEGIN ' + name.toUpperCase() + '-----\r\n' : '') + + Base64.encode(data instanceof CryptoOperationData ? data : BER.encode(data)) + + (name ? '\r\n-----END ' + name.toUpperCase() + '-----' : ''); + }, + /** + * PEM.decode(s, name, deep) decode PEM format s labeled name to CryptoOperationData or javascript object in according to deep parameter + * + * @memberOf GostCoding.PEM + * @param {string} s PEM encoded string + * @param {string} name Name of PEM object: 'certificate', 'private key' etc. + * @param {boolean} deep If true method do BER-decoding, else only BASE64 decoding + * @param {integer} index Index of decoded value + * @returns {(Object|CryptoOperationData)} Decoded javascript object if deep=true, else CryptoOperationData for father BER decoding + */ + decode: function (s, name, deep, index) { + // Try clear base64 + var re1 = /([A-Za-z0-9\+\/\s\=]+)/g, + valid = re1.exec(s); + if (valid[1].length !== s.length) + valid = false; + if (!valid && name) { + // Try with the name + var re2 = new RegExp( + '-----\\s?BEGIN ' + name.toUpperCase() + + '-----([A-Za-z0-9\\+\\/\\s\\=]+)-----\\s?END ' + + name.toUpperCase() + '-----', 'g'); + valid = re2.exec(s); + } + if (!valid) { + // Try with some name + var re3 = new RegExp( + '-----\\s?BEGIN [A-Z0-9\\s]+' + + '-----([A-Za-z0-9\\+\\/\\s\\=]+)-----\\s?END ' + + '[A-Z0-9\\s]+-----', 'g'); + valid = re3.exec(s); + } + var r = valid && valid[1 + (index || 0)]; + if (!r) + throw new DataError('Not valid PEM format'); + var out = Base64.decode(r); + if (deep) + out = BER.decode(out); + return out; + } // +}; + +/** + * PEM conversion + * @memberOf GostCoding + * @insnance + * @type GostCoding.PEM + */ +GostCoding.prototype.PEM = PEM; + +if (gostCrypto) + /** + * Coding algorithms: Base64, Hex, Int16, Chars, BER and PEM + * + * @memberOf gostCrypto + * @type GostCoding + */ + gostCrypto.coding = new GostCoding(); + +export default GostCoding; diff --git a/src/core/vendor/gost/gostCrypto.mjs b/src/core/vendor/gost/gostCrypto.mjs new file mode 100644 index 00000000..77132f6c --- /dev/null +++ b/src/core/vendor/gost/gostCrypto.mjs @@ -0,0 +1,1653 @@ +/** + * Implementation Web Crypto interfaces for GOST algorithms + * 1.76 + * 2014-2016, Rudolf Nickolaev. All rights reserved. + * + * Exported for CyberChef by mshwed [m@ttshwed.com] + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 HOLDER 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. + * + */ + +import GostRandom from './gostRandom.mjs'; +import gostEngine from './gostEngine.mjs'; + +import crypto from 'crypto' + +/* +* Algorithm normalization +* +*/ // + +var root = {}; +root.gostEngine = gostEngine; + +var rootCrypto = crypto + +var SyntaxError = Error, + DataError = Error, + NotSupportedError = Error, + OperationError = Error, + InvalidStateError = Error, + InvalidAccessError = Error; + +// Normalize algorithm +function normalize(algorithm, method) { + if (typeof algorithm === 'string' || algorithm instanceof String) + algorithm = {name: algorithm}; + var name = algorithm.name; + if (!name) + throw new SyntaxError('Algorithm name not defined'); + // Extract algorithm modes from name + var modes = name.split('/'), modes = modes[0].split('-').concat(modes.slice(1)); + // Normalize the name with default modes + var na = {}; + name = modes[0].replace(/[\.\s]/g, ''); + modes = modes.slice(1); + if (name.indexOf('28147') >= 0) { + na = { + name: 'GOST 28147', + version: 1989, + mode: (algorithm.mode || (// ES, MAC, KW + (method === 'sign' || method === 'verify') ? 'MAC' : + (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), + length: algorithm.length || 64 + }; + } else if (name.indexOf('3412') >= 0) { + na = { + name: 'GOST R 34.12', + version: 2015, + mode: (algorithm.mode || (// ES, MAC, KW + (method === 'sign' || method === 'verify') ? 'MAC' : + (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), + length: algorithm.length || 64 // 128 + }; + } else if (name.indexOf('3411') >= 0) { + na = { + name: 'GOST R 34.11', + version: 2012, // 1994 + mode: (algorithm.mode || (// HASH, KDF, HMAC, PBKDF2, PFXKDF, CPKDF + (method === 'deriveKey' || method === 'deriveBits') ? 'KDF' : + (method === 'sign' || method === 'verify') ? 'HMAC' : 'HASH')).toUpperCase(), + length: algorithm.length || 256 // 512 + }; + } else if (name.indexOf('3410') >= 0) { + na = { + name: 'GOST R 34.10', + version: 2012, // 1994, 2001 + mode: (algorithm.mode || (// SIGN, DH, MASK + (method === 'deriveKey' || method === 'deriveBits') ? 'DH' : 'SIGN')).toUpperCase(), + length: algorithm.length || 256 // 512 + }; + } else if (name.indexOf('SHA') >= 0) { + na = { + name: 'SHA', + version: (algorithm.length || 160) === 160 ? 1 : 2, // 1, 2 + mode: (algorithm.mode || (// HASH, KDF, HMAC, PBKDF2, PFXKDF + (method === 'deriveKey' || method === 'deriveBits') ? 'KDF' : + (method === 'sign' || method === 'verify') ? 'HMAC' : 'HASH')).toUpperCase(), + length: algorithm.length || 160 + }; + } else if (name.indexOf('RC2') >= 0) { + na = { + name: 'RC2', + version: 1, + mode: (algorithm.mode || (// ES, MAC, KW + (method === 'sign' || method === 'verify') ? 'MAC' : + (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), + length: algorithm.length || 32 // 1 - 1024 + }; + } else if (name.indexOf('PBKDF2') >= 0) { + na = normalize(algorithm.hash, 'digest'); + na.mode = 'PBKDF2'; + } else if (name.indexOf('PFXKDF') >= 0) { + na = normalize(algorithm.hash, 'digest'); + na.mode = 'PFXKDF'; + } else if (name.indexOf('CPKDF') >= 0) { + na = normalize(algorithm.hash, 'digest'); + na.mode = 'CPKDF'; + } else if (name.indexOf('HMAC') >= 0) { + na = normalize(algorithm.hash, 'digest'); + na.mode = 'HMAC'; + } else + throw new NotSupportedError('Algorithm not supported'); + + // Compile modes + modes.forEach(function (mode) { + mode = mode.toUpperCase(); + if (/^[0-9]+$/.test(mode)) { + if ((['8', '16', '32'].indexOf(mode) >= 0) || (na.length === '128' && mode === '64')) { // Shift bits + if (na.mode === 'ES') + na.shiftBits = parseInt(mode); + else if (na.mode === 'MAC') + na.macLength = parseInt(mode); + else + throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); + } else if (['89', '94', '01', '12', '15', '1989', '1994', '2001', '2012', '2015'].indexOf(mode) >= 0) { // GOST Year + var version = parseInt(mode); + version = version < 1900 ? (version < 80 ? 2000 + version : 1900 + version) : version; + na.version = version; + } else if (['1'].indexOf(mode) >= 0 && na.name === 'SHA') { // SHA-1 + na.version = 1; + na.length = 160; + } else if (['256', '384', '512'].indexOf(mode) >= 0 && na.name === 'SHA') { // SHA-2 + na.version = 2; + na.length = parseInt(mode); + } else if (['40', '128'].indexOf(mode) >= 0 && na.name === 'RC2') { // RC2 + na.version = 1; + na.length = parseInt(mode); // key size + } else if (['64', '128', '256', '512'].indexOf(mode) >= 0) // block size + na.length = parseInt(mode); + else if (['1000', '2000'].indexOf(mode) >= 0) // Iterations + na.iterations = parseInt(mode); + // Named Paramsets + } else if (['E-TEST', 'E-A', 'E-B', 'E-C', 'E-D', 'E-SC', 'E-Z', 'D-TEST', 'D-A', 'D-SC'].indexOf(mode) >= 0) { + na.sBox = mode; + } else if (['S-TEST', 'S-A', 'S-B', 'S-C', 'S-D', 'X-A', 'X-B', 'X-C'].indexOf(mode) >= 0) { + na.namedParam = mode; + } else if (['S-256-TEST', 'S-256-A', 'S-256-B', 'S-256-C', 'P-256', 'T-512-TEST', 'T-512-A', + 'T-512-B', 'X-256-A', 'X-256-B', 'T-256-TEST', 'T-256-A', 'T-256-B', 'S-256-B', 'T-256-C', 'S-256-C'].indexOf(mode) >= 0) { + na.namedCurve = mode; + } else if (['SC', 'CP', 'VN'].indexOf(mode) >= 0) { + na.procreator = mode; + + // Encription GOST 28147 or GOST R 34.12 + } else if (na.name === 'GOST 28147' || na.name === 'GOST R 34.12' || na.name === 'RC2') { + if (['ES', 'MAC', 'KW', 'MASK'].indexOf(mode) >= 0) { + na.mode = mode; + } else if (['ECB', 'CFB', 'OFB', 'CTR', 'CBC'].indexOf(mode) >= 0) { + na.mode = 'ES'; + na.block = mode; + } else if (['CPKW', 'NOKW', 'SCKW'].indexOf(mode) >= 0) { + na.mode = 'KW'; + na.keyWrapping = mode.replace('KW', ''); + } else if (['ZEROPADDING', 'PKCS5PADDING', 'NOPADDING', 'RANDOMPADDING', 'BITPADDING'].indexOf(mode) >= 0) { + na.padding = mode.replace('PADDING', ''); + } else if (['NOKM', 'CPKM'].indexOf(mode) >= 0) { + na.keyMeshing = mode.replace('KM', ''); + } else + throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); + + // Digesting GOST 34.11 + } else if (na.name === 'GOST R 34.11' || na.name === 'SHA') { + if (['HASH', 'KDF', 'HMAC', 'PBKDF2', 'PFXKDF', 'CPKDF'].indexOf(mode) >= 0) + na.mode = mode; + else + throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); + + // Signing GOST 34.10 + } else if (na.name === 'GOST R 34.10') { + var hash = mode.replace(/[\.\s]/g, ''); + if (hash.indexOf('GOST') >= 0 && hash.indexOf('3411') >= 0) + na.hash = mode; + else if (['SIGN', 'DH', 'MASK'].indexOf(mode)) + na.mode = mode; + else + throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); + } + }); + + // Procreator + na.procreator = algorithm.procreator || na.procreator || 'CP'; + + // Key size + switch (na.name) { + case 'GOST R 34.10': + na.keySize = na.length / (na.version === 1994 ? 4 : 8); + break; + case 'GOST R 34.11': + na.keySize = 32; + break; + case 'GOST 28147': + case 'GOST R 34.12': + na.keySize = 32; + break; + case 'RC2': + na.keySize = Math.ceil(na.length / 8); + break; + case 'SHA': + na.keySize = na.length / 8; + break; + } + + // Encrypt additional modes + if (na.mode === 'ES') { + if (algorithm.block) + na.block = algorithm.block; // ECB, CFB, OFB, CTR, CBC + if (na.block) + na.block = na.block.toUpperCase(); + if (algorithm.padding) + na.padding = algorithm.padding; // NO, ZERO, PKCS5, RANDOM, BIT + if (na.padding) + na.padding = na.padding.toUpperCase(); + if (algorithm.shiftBits) + na.shiftBits = algorithm.shiftBits; // 8, 16, 32, 64 + if (algorithm.keyMeshing) + na.keyMeshing = algorithm.keyMeshing; // NO, CP + if (na.keyMeshing) + na.keyMeshing = na.keyMeshing.toUpperCase(); + // Default values + if (method !== 'importKey' && method !== 'generateKey') { + na.block = na.block || 'ECB'; + na.padding = na.padding || (na.block === 'CBC' || na.block === 'ECB' ? 'ZERO' : 'NO'); + if (na.block === 'CFB' || na.block === 'OFB') + na.shiftBits = na.shiftBits || na.length; + na.keyMeshing = na.keyMeshing || 'NO'; + } + } + if (na.mode === 'KW') { + if (algorithm.keyWrapping) + na.keyWrapping = algorithm.keyWrapping; // NO, CP, SC + if (na.keyWrapping) + na.keyWrapping = na.keyWrapping.toUpperCase(); + if (method !== 'importKey' && method !== 'generateKey') + na.keyWrapping = na.keyWrapping || 'NO'; + } + + // Paramsets + ['sBox', 'namedParam', 'namedCurve', 'curve', 'param', 'modulusLength'].forEach(function (name) { + algorithm[name] && (na[name] = algorithm[name]); + }); + // Default values + if (method !== 'importKey' && method !== 'generateKey') { + if (na.name === 'GOST 28147') { + na.sBox = na.sBox || (na.procreator === 'SC' ? 'E-SC' : 'E-A'); // 'E-A', 'E-B', 'E-C', 'E-D', 'E-SC' + } else if (na.name === 'GOST R 34.12' && na.length === 64) { + na.sBox = 'E-Z'; + } else if (na.name === 'GOST R 34.11' && na.version === 1994) { + na.sBox = na.sBox || (na.procreator === 'SC' ? 'D-SC' : 'D-A'); // 'D-SC' + } else if (na.name === 'GOST R 34.10' && na.version === 1994) { + na.namedParam = na.namedParam || (na.mode === 'DH' ? 'X-A' : 'S-A'); // 'S-B', 'S-C', 'S-D', 'X-B', 'X-C' + } else if (na.name === 'GOST R 34.10' && na.version === 2001) { + na.namedCurve = na.namedCurve || (na.length === 256 ? + na.procreator === 'SC' ? 'P-256' : (na.mode === 'DH' ? 'X-256-A' : 'S-256-A') : // 'S-256-B', 'S-256-C', 'X-256-B', 'T-256-A', 'T-256-B', 'T-256-C', 'P-256' + na.mode === 'T-512-A'); // 'T-512-B', 'T-512-C' + } else if (na.name === 'GOST R 34.10' && na.version === 2012) { + na.namedCurve = na.namedCurve || (na.length === 256 ? + na.procreator === 'SC' ? 'P-256' : (na.mode === 'DH' ? 'X-256-A' : 'S-256-A') : // 'S-256-B', 'S-256-C', 'X-256-B', 'T-256-A', 'T-256-B', 'T-256-C', 'P-256' + na.mode === 'T-512-A'); // 'T-512-B', 'T-512-C' + } + } + + // Vectors + switch (na.mode) { + case 'DH': + algorithm.ukm && (na.ukm = algorithm.ukm); + algorithm['public'] && (na['public'] = algorithm['public']); + break; + case 'SIGN': + case 'KW': + algorithm.ukm && (na.ukm = algorithm.ukm); + break; + case 'ES': + case 'MAC': + algorithm.iv && (na.iv = algorithm.iv); + break; + case 'KDF': + algorithm.label && (na.label = algorithm.label); + algorithm.contex && (na.context = algorithm.contex); + break; + case 'PBKDF2': + algorithm.salt && (na.salt = algorithm.salt); + algorithm.iterations && (na.iterations = algorithm.iterations); + algorithm.diversifier && (na.diversifier = algorithm.diversifier); + break; + case 'PFXKDF': + algorithm.salt && (na.salt = algorithm.salt); + algorithm.iterations && (na.iterations = algorithm.iterations); + algorithm.diversifier && (na.diversifier = algorithm.diversifier); + break; + case 'CPKDF': + algorithm.salt && (na.salt = algorithm.salt); + algorithm.iterations && (na.iterations = algorithm.iterations); + break; + } + + // Verification method and modes + if (method && ( + ((na.mode !== 'ES' && na.mode !== 'SIGN' && na.mode !== 'MAC' && + na.mode !== 'HMAC' && na.mode !== 'KW' && na.mode !== 'DH' + && na.mode !== 'MASK') && + (method === 'generateKey')) || + ((na.mode !== 'ES') && + (method === 'encrypt' || method === 'decrypt')) || + ((na.mode !== 'SIGN' && na.mode !== 'MAC' && na.mode !== 'HMAC') && + (method === 'sign' || method === 'verify')) || + ((na.mode !== 'HASH') && + (method === 'digest')) || + ((na.mode !== 'KW' && na.mode !== 'MASK') && + (method === 'wrapKey' || method === 'unwrapKey')) || + ((na.mode !== 'DH' && na.mode !== 'PBKDF2' && na.mode !== 'PFXKDF' && + na.mode !== 'CPKDF' && na.mode !== 'KDF') && + (method === 'deriveKey' || method === 'deriveBits')))) + throw new NotSupportedError('Algorithm mode ' + na.mode + ' not valid for method ' + method); + + // Normalize hash algorithm + algorithm.hash && (na.hash = algorithm.hash); + if (na.hash) { + if ((typeof na.hash === 'string' || na.hash instanceof String) + && na.procreator) + na.hash = na.hash + '/' + na.procreator; + na.hash = normalize(na.hash, 'digest'); + } + + // Algorithm object identirifer + algorithm.id && (na.id = algorithm.id); + + return na; +} + +// Check for possibility use native crypto.subtle +function checkNative(algorithm) { + if (!rootCrypto || !rootCrypto.subtle || !algorithm) + return false; + // Prepare name + var name = (typeof algorithm === 'string' || algorithm instanceof String) ? + name = algorithm : algorithm.name; + if (!name) + return false; + name = name.toUpperCase(); + // Digest algorithm for key derivation + if ((name.indexOf('KDF') >= 0 || name.indexOf('HMAC') >= 0) && algorithm.hash) + return checkNative(algorithm.hash); + // True if no supported names + return name.indexOf('GOST') === -1 && + name.indexOf('SHA-1') === -1 && + name.indexOf('RC2') === -1 && + name.indexOf('?DES') === -1; +} +// + +/* + * Key conversion methods + * + */ // + +// Check key parameter +function checkKey(key, method) { + if (!key.algorithm) + throw new SyntaxError('Key algorithm not defined'); + + if (!key.algorithm.name) + throw new SyntaxError('Key algorithm name not defined'); + + var name = key.algorithm.name, + gostCipher = name === 'GOST 28147' || name === 'GOST R 34.12' || name === 'RC2', + gostDigest = name === 'GOST R 34.11' || name === 'SHA', + gostSign = name === 'GOST R 34.10'; + + if (!gostCipher && !gostSign && !gostDigest) + throw new NotSupportedError('Key algorithm ' + name + ' is unsupproted'); + + if (!key.type) + throw new SyntaxError('Key type not defined'); + + if (((gostCipher || gostDigest) && key.type !== 'secret') || + (gostSign && !(key.type === 'public' || key.type === 'private'))) + throw new DataError('Key type ' + key.type + ' is not valid for algorithm ' + name); + + if (!key.usages || !key.usages.indexOf) + throw new SyntaxError('Key usages not defined'); + + for (var i = 0, n = key.usages.length; i < n; i++) { + var md = key.usages[i]; + if (((md === 'encrypt' || md === 'decrypt') && key.type !== 'secret') || + (md === 'sign' && key.type === 'public') || + (md === 'verify' && key.type === 'private')) + throw new InvalidStateError('Key type ' + key.type + ' is not valid for ' + md); + } + + if (method) + if (key.usages.indexOf(method) === -1) + throw new InvalidAccessError('Key usages is not contain method ' + method); + + if (!key.buffer) + throw new SyntaxError('Key buffer is not defined'); + + var size = key.buffer.byteLength * 8, keySize = 8 * key.algorithm.keySize; + if ((key.type === 'secret' && size !== (keySize || 256) && + (key.usages.indexOf('encrypt') >= 0 || key.usages.indexOf('decrypt') >= 0)) || + (key.type === 'private' && !(size === 256 || size === 512)) || + (key.type === 'public' && !(size === 512 || size === 1024))) + throw new SyntaxError('Key buffer has wrong size ' + size + ' bit'); +} + +// Extract key and enrich cipher algorithm +function extractKey(method, algorithm, key) { + checkKey(key, method); + if (algorithm) { + var params; + switch (algorithm.mode) { + case 'ES': + params = ['sBox', 'keyMeshing', 'padding', 'block']; + break; + case 'SIGN': + params = ['namedCurve', 'namedParam', 'sBox', 'curve', 'param', 'modulusLength']; + break; + case 'MAC': + params = ['sBox']; + break; + case 'KW': + params = ['keyWrapping', 'ukm']; + break; + case 'DH': + params = ['namedCurve', 'namedParam', 'sBox', 'ukm', 'curve', 'param', 'modulusLength']; + break; + case 'KDF': + params = ['context', 'label']; + break; + case 'PBKDF2': + params = ['sBox', 'iterations', 'salt']; + break; + case 'PFXKDF': + params = ['sBox', 'iterations', 'salt', 'diversifier']; + break; + case 'CPKDF': + params = ['sBox', 'salt']; + break; + } + if (params) + params.forEach(function (name) { + key.algorithm[name] && (algorithm[name] = key.algorithm[name]); + }); + } + return key.buffer; +} + +// Make key definition +function convertKey(algorithm, extractable, keyUsages, keyData, keyType) { + var key = { + type: keyType || (algorithm.name === 'GOST R 34.10' ? 'private' : 'secret'), + extractable: extractable || 'false', + algorithm: algorithm, + usages: keyUsages || [], + buffer: keyData + }; + checkKey(key); + return key; +} + +function convertKeyPair(publicAlgorithm, privateAlgorithm, extractable, keyUsages, publicBuffer, privateBuffer) { + + if (!keyUsages || !keyUsages.indexOf) + throw new SyntaxError('Key usages not defined'); + + var publicUsages = keyUsages.filter(function (value) { + return value !== 'sign'; + }); + var privateUsages = keyUsages.filter(function (value) { + return value !== 'verify'; + }); + + return { + publicKey: convertKey(publicAlgorithm, extractable, publicUsages, publicBuffer, 'public'), + privateKey: convertKey(privateAlgorithm, extractable, privateUsages, privateBuffer, 'private') + }; +} + +// Swap bytes in buffer +function swapBytes(src) { + if (src instanceof CryptoOperationData) + src = new Uint8Array(src); + var dst = new Uint8Array(src.length); + for (var i = 0, n = src.length; i < n; i++) + dst[n - i - 1] = src[i]; + return dst.buffer; +} +// + +/** + * Promise stub object (not fulfill specification, only for internal use) + * Class not defined if Promise class already defined in root context

+ * + * The Promise object is used for deferred and asynchronous computations. A Promise is in one of the three states: + *
    + *
  • pending: initial state, not fulfilled or rejected.
  • + *
  • fulfilled: successful operation
  • + *
  • rejected: failed operation.
  • + *
+ * Another term describing the state is settled: the Promise is either fulfilled or rejected, but not pending.

+ * @class Promise + * @global + * @param {function} executor Function object with two arguments resolve and reject. + * The first argument fulfills the promise, the second argument rejects it. + * We can call these functions, once our operation is completed. + */ // +if (!Promise) { + + root.Promise = (function () { + + function mswrap(value) { + if (value && value.oncomplete === null && value.onerror === null) { + return new Promise(function (resolve, reject) { + value.oncomplete = function () { + resolve(value.result); + }; + value.onerror = function () { + reject(new OperationError(value.toString())); + }; + }); + } else + return value; + } + + function Promise(executor) { + + var state = 'pending', result, + resolveQueue = [], rejectQueue = []; + + function call(callback) { + try { + callback(); + } catch (e) { + } + } + + try { + executor(function (value) { + if (state === 'pending') { + state = 'fulfilled'; + result = value; + resolveQueue.forEach(call); + } + }, function (reason) { + if (state === 'pending') { + state = 'rejected'; + result = reason; + rejectQueue.forEach(call); + } + }); + } catch (error) { + if (state === 'pending') { + state = 'rejected'; + result = error; + rejectQueue.forEach(call); + } + } + /** + * The then() method returns a Promise. It takes two arguments, both are + * callback functions for the success and failure cases of the Promise. + * + * @method then + * @memberOf Promise + * @instance + * @param {function} onFulfilled A Function called when the Promise is fulfilled. This function has one argument, the fulfillment value. + * @param {function} onRejected A Function called when the Promise is rejected. This function has one argument, the rejection reason. + * @returns {Promise} + */ + this.then = function (onFulfilled, onRejected) { + + return new Promise(function (resolve, reject) { + + function asyncOnFulfilled() { + var value; + try { + value = onFulfilled ? onFulfilled(result) : result; + } catch (error) { + reject(error); + return; + } + value = mswrap(value); + if (value && value?.then?.call) { + value.then(resolve, reject); + } else { + resolve(value); + } + } + + function asyncOnRejected() { + var reason; + try { + reason = onRejected ? onRejected(result) : result; + } catch (error) { + reject(error); + return; + } + reason = mswrap(reason); + if (reason && reason?.then?.call) { + reason.then(resolve, reject); + } else { + reject(reason); + } + } + + if (state === 'fulfilled') { + asyncOnFulfilled(); + } else if (state === 'rejected') { + asyncOnRejected(); + } else { + resolveQueue.push(asyncOnFulfilled); + rejectQueue.push(asyncOnRejected); + } + + }); + + }; + /** + * The catch() method returns a Promise and deals with rejected cases only. + * It behaves the same as calling Promise.prototype.then(undefined, onRejected). + * + * @method catch + * @memberOf Promise + * @instance + * @param {function} onRejected A Function called when the Promise is rejected. This function has one argument, the rejection reason. + * @returns {Promise} + */ + this['catch'] = function (onRejected) { + return this.then(undefined, onRejected); + }; + } + + /** + * The Promise.all(iterable) method returns a promise that resolves when all + * of the promises in the iterable argument have resolved.

+ * + * The result is passed as an array of values from all the promises. + * If something passed in the iterable array is not a promise, it's converted to + * one by Promise.resolve. If any of the passed in promises rejects, the + * all Promise immediately rejects with the value of the promise that rejected, + * discarding all the other promises whether or not they have resolved. + * + * @method all + * @memberOf Promise + * @static + * @param {KeyUsages} promises Array with promises. + * @returns {Promise} + */ + Promise.all = function (promises) { + return new Promise(function (resolve, reject) { + var result = [], count = 0; + function asyncResolve(k) { + count++; + return function (data) { + result[k] = data; + count--; + if (count === 0) + resolve(result); + }; + } + + function asyncReject(reason) { + if (count > 0) + reject(reason); + count = 0; + } + + for (var i = 0, n = promises.length; i < n; i++) { + var data = promises[i]; + if (data?.then?.call) + data.then(asyncResolve(i), asyncReject); + else + result[i] = data; + } + + if (count === 0) + resolve(result); + }); + }; + + return Promise; + })(); +} //
+ +/* + * Worker executor + * + */ // + +var baseUrl = '', nameSuffix = ''; +// Try to define from DOM model +if (typeof document !== 'undefined') { + (function () { + var regs = /^(.*)gostCrypto(.*)\.js$/i; + var list = document.querySelectorAll('script'); + for (var i = 0, n = list.length; i < n; i++) { + var value = list[i].getAttribute('src'); + var test = regs.exec(value); + if (test) { + baseUrl = test[1]; + nameSuffix = test[2]; + } + } + })(); +} + +// Local importScripts procedure for include dependens +function importScripts() { + for (var i = 0, n = arguments.length; i < n; i++) { + var name = arguments[i].split('.'), + src = baseUrl + name[0] + nameSuffix + '.' + name[1]; + var el = document.querySelector('script[src="' + src + '"]'); + if (!el) { + el = document.createElement('script'); + el.setAttribute('src', src); + document.head.appendChild(el); + } + } +} + +// Create Worker +var worker = false, tasks = [], sequence = 0; +// Worker will create only for first child process and +// Gost implementation libraries not yet loaded +if (!root.importScripts && !root.gostEngine) { + + try { + worker = new Worker(baseUrl + 'gostEngine' + nameSuffix + '.js'); + + // Result of opertion + worker.onmessage = function (event) { + // Find task + var id = event.data.id; + for (var i = 0, n = tasks.length; i < n; i++) + if (tasks[i].id === id) + break; + if (i < n) { + var task = tasks[i]; + tasks.splice(i, 1); + // Reject if error or resolve with result + if (event.data.error) + task.reject(new OperationError(event.data.error)); + else + task.resolve(event.data.result); + } + }; + + // Worker error - reject all waiting tasks + worker.onerror = function (event) { + for (var i = 0, n = tasks.length; i < n; i++) + tasks[i].reject(event.error); + tasks = []; + }; + + } catch (e) { + // Worker is't supported + worker = false; + } +} + +if (!root.importScripts) { + // This procedure emulate load dependents as in Worker + root.importScripts = importScripts; + +} + +if (!worker) { + // Import main module + // Reason: we are already in worker process or Worker interface is not + // yet supported + root.gostEngine || require('./gostEngine'); +} + +// Executor for any method +function execute(algorithm, method, args) { + return new Promise(function (resolve, reject) { + try { + if (worker) { + var id = ++sequence; + tasks.push({ + id: id, + resolve: resolve, + reject: reject + }); + worker.postMessage({ + id: id, algorithm: algorithm, + method: method, args: args + }); + } else { + if (root.gostEngine) + resolve(root.gostEngine.execute(algorithm, method, args)); + else + reject(new OperationError('Module gostEngine not found')); + } + } catch (error) { + reject(error); + } + }); +} + +// Self resolver +function call(callback) { + try { + callback(); + } catch (e) { + } +} + +// + +/* + * WebCrypto common class references + * + */ // +/** + * The Algorithm object is a dictionary object [WebIDL] which is used to + * specify an algorithm and any additional parameters required to fully + * specify the desired operation.
+ *
+ *  dictionary Algorithm {
+ *      DOMString name;
+ *  };
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#algorithm-dictionary} + * @class Algorithm + * @param {DOMString} name The name of the registered algorithm to use. + */ + +/** + * AlgorithmIdentifier - Algorithm or DOMString name of algorithm
+ *
+ *  typedef (Algorithm or DOMString) AlgorithmIdentifier;
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#algorithm-dictionary} + * @class AlgorithmIdentifier + */ + +/** + * The KeyAlgorithm interface represents information about the contents of a + * given Key object. + *
+ *  interface KeyAlgorithm {
+ *      readonly attribute DOMString name
+ *  };
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-algorithm-interface} + * @class KeyAlgorithm + * @param {DOMString} name The name of the algorithm used to generate the Key + */ + +/** + * The type of a key. The recognized key type values are "public", "private" + * and "secret". Opaque keying material, including that used for symmetric + * algorithms, is represented by "secret", while keys used as part of asymmetric + * algorithms composed of public/private keypairs will be either "public" or "private". + *
+ *  typedef DOMString KeyType;
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} + * @class KeyType + */ + +/** + * Sequence of operation type that may be performed using a key. The recognized + * key usage values are "encrypt", "decrypt", "sign", "verify", "deriveKey", + * "deriveBits", "wrapKey" and "unwrapKey". + *
+ *  typedef DOMString[] KeyUsages;
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} + * @class KeyUsages + */ + +/** + * The Key object represents an opaque reference to keying material that is + * managed by the user agent.
+ * This specification provides a uniform interface for many different kinds of + * keying material managed by the user agent. This may include keys that have + * been generated by the user agent, derived from other keys by the user agent, + * imported to the user agent through user actions or using this API, + * pre-provisioned within software or hardware to which the user agent has + * access or made available to the user agent in other ways. The term key refers + * broadly to any keying material including actual keys for cryptographic + * operations and secret values obtained within key derivation or exchange operations.
+ * The Key object is not required to directly interface with the underlying key + * storage mechanism, and may instead simply be a reference for the user agent + * to understand how to obtain the keying material when needed, eg. when performing + * a cryptographic operation. + *
+ *  interface Key {
+ *      readonly attribute KeyType type;
+ *      readonly attribute boolean extractable;
+ *      readonly attribute KeyAlgorithm algorithm;
+ *      readonly attribute KeyUsages usages;
+ *  };
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} + * @class Key + * @param {KeyType} type The type of a key. The recognized key type values are "public", "private" and "secret". + * @param {boolean} extractable Whether or not the raw keying material may be exported by the application. + * @param {KeyAlgorithm} algorithm The Algorithm used to generate the key. + * @param {KeyUsages} usages Key usage array: type of operation that may be performed using a key. + */ + +/** + * The KeyPair interface represents an asymmetric key pair that is comprised of both public and private keys. + *
+ *  interface KeyPair {
+ *      readonly attribute Key publicKey;
+ *      readonly attribute Key privateKey;
+ *  };
+ * 
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#keypair} + * @class KeyPair + * @param {Key} privateKey Private key + * @param {Key} publicKey Public key + */ + +/** + * Specifies a serialization format for a key. The recognized key format values are: + *
    + *
  • 'raw' - An unformatted sequence of bytes. Intended for secret keys.
  • + *
  • 'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.
  • + *
  • 'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.
  • + *
  • 'jwk' - The key is represented as JSON according to the JSON Web Key format.
  • + *
+ *
+ *  typedef DOMString KeyFormat;
+ *  
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} + * @class KeyFormat + */ + +/** + * Binary data + *
+ *  typedef (ArrayBuffer or ArrayBufferView) CryptoOperationData;
+ *  
+ * @class CryptoOperationData + */ +var CryptoOperationData = ArrayBuffer; + +/** + * DER-encoded ArrayBuffer or PEM-encoded DOMString constains ASN.1 object
+ *
+ *  typedef (ArrayBuffer or DOMString) FormatedData;
+ * 
+ * @class FormatedData + */ +//
+ +/** + * The gostCrypto provide general purpose cryptographic functionality for + * GOST standards including a cryptographically strong pseudo-random number + * generator seeded with truly random values. + * + * @namespace gostCrypto + */ +var gostCrypto = {}; + +/** + * The SubtleCrypto class provides low-level cryptographic primitives and algorithms. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#subtlecrypto-interface} + * + * @class SubtleCrypto + */ // +function SubtleCrypto() { +} + +/** + * The encrypt method returns a new Promise object that will encrypt data + * using the specified algorithm identifier with the supplied Key. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-encrypt}

+ * + * Supported algorithm names: + *
    + *
  • GOST 28147-ECB "prostaya zamena" (ECB) mode (default)
  • + *
  • GOST 28147-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • + *
  • GOST 28147-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • + *
  • GOST 28147-CTR "gammirovanie" (counter) mode
  • + *
  • GOST 28147-CBC Cipher-Block-Chaining (CBC) mode
  • + *
  • GOST R 34.12-ECB "prostaya zamena" (ECB) mode (default)
  • + *
  • GOST R 34.12-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • + *
  • GOST R 34.12-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • + *
  • GOST R 34.12-CTR "gammirovanie" (counter) mode
  • + *
  • GOST R 34.12-CBC Cipher-Block-Chaining (CBC) mode
  • + *
+ * For more information see {@link GostCipher} + * + * @memberOf SubtleCrypto + * @method encrypt + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} key Key object + * @param {CryptoOperationData} data Operation data + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.encrypt = function (algorithm, key, data) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.encrypt(algorithm, key, data); + + algorithm = normalize(algorithm, 'encrypt'); + return execute(algorithm, 'encrypt', + [extractKey('encrypt', algorithm, key), data]); + }); +}; // + +/** + * The decrypt method returns a new Promise object that will decrypt data + * using the specified algorithm identifier with the supplied Key. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-decrypt}

+ * + * Supported algorithm names: + *
    + *
  • GOST 28147-ECB "prostaya zamena" (ECB) mode (default)
  • + *
  • GOST 28147-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • + *
  • GOST 28147-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • + *
  • GOST 28147-CTR "gammirovanie" (counter) mode
  • + *
  • GOST 28147-CBC Cipher-Block-Chaining (CBC) mode
  • + *
  • GOST R 34.12-ECB "prostaya zamena" (ECB) mode (default)
  • + *
  • GOST R 34.12-CFB "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode
  • + *
  • GOST R 34.12-OFB "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode
  • + *
  • GOST R 34.12-CTR "gammirovanie" (counter) mode
  • + *
  • GOST R 34.12-CBC Cipher-Block-Chaining (CBC) mode
  • + *
+ * For additional modes see {@link GostCipher} + * + * @memberOf SubtleCrypto + * @method decrypt + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} key Key object + * @param {CryptoOperationData} data Operation data + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.decrypt = function (algorithm, key, data) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.decrypt(algorithm, key, data); + + algorithm = normalize(algorithm, 'decrypt'); + return execute(algorithm, 'decrypt', + [extractKey('decrypt', algorithm, key), data]); + }); +}; // + +/** + * The sign method returns a new Promise object that will sign data using + * the specified algorithm identifier with the supplied Key. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-sign}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-94 GOST Signature
  • + *
  • GOST R 34.10-94/GOST R 34.11-94 GOST Signature with Hash
  • + *
  • GOST R 34.10 ECGOST Signature
  • + *
  • GOST R 34.10/GOST R 34.11-94 ECGOST Signature with Old-Style Hash
  • + *
  • GOST R 34.10/GOST R 34.11 ECGOST Signature with Streebog Hash
  • + *
  • GOST 28147-MAC MAC base on GOST 28147
  • + *
  • GOST R 34.12-MAC MAC base on GOST R 43.12
  • + *
  • GOST R 34.11-HMAC HMAC base on GOST 34.11
  • + *
  • SHA-HMAC HMAC base on SHA
  • + *
+ * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher} + * + * @memberOf SubtleCrypto + * @method sign + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} key Key object + * @param {CryptoOperationData} data Operation data + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.sign = function (algorithm, key, data) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.sign(algorithm, key, data); + + algorithm = normalize(algorithm, 'sign'); + var value = execute(algorithm, 'sign', + [extractKey('sign', algorithm, key), data]).then(function (data) { + if (algorithm.procreator === 'SC' && algorithm.mode === 'SIGN') { + data = gostCrypto.asn1.GostSignature.encode(data); + } + return data; + }); + return value; + }); +}; // + +/** + * The verify method returns a new Promise object that will verify data + * using the specified algorithm identifier with the supplied Key. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-verify}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-94 GOST Signature
  • + *
  • GOST R 34.10-94/GOST R 34.11-94 GOST Signature with Hash
  • + *
  • GOST R 34.10 ECGOST Signature
  • + *
  • GOST R 34.10/GOST R 34.11-94 ECGOST Signature with Old-Style Hash
  • + *
  • GOST R 34.10/GOST R 34.11 ECGOST Signature with Streebog Hash
  • + *
  • GOST 28147-MAC MAC base on GOST 28147
  • + *
  • GOST R 34.12-MAC MAC base on GOST R 34.12
  • + *
  • GOST R 34.11-HMAC HMAC base on GOST 34.11
  • + *
  • SHA-HMAC HMAC base on SHA
  • + *
+ * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher} + * + * @memberOf SubtleCrypto + * @method verify + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} key Key object + * @param {CryptoOperationData} signature Signature data + * @param {CryptoOperationData} data Operation data + * @returns {Promise} Promise that resolves with boolean value of verification result + */ +SubtleCrypto.prototype.verify = function (algorithm, key, signature, data) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.verify(algorithm, key, signature, data); + + algorithm = normalize(algorithm, 'verify'); + if (algorithm.procreator === 'SC' && algorithm.mode === 'SIGN') { + var obj = gostCrypto.asn1.GostSignature.decode(signature); + signature = {r: obj.r, s: obj.s}; + } + return execute(algorithm, 'verify', + [extractKey('verify', algorithm, key), signature, data]); + }); +}; // + +/** + * The digest method returns a new Promise object that will digest data + * using the specified algorithm identifier. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-digest}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.11-94 Old-Style GOST Hash
  • + *
  • GOST R 34.11 GOST Streebog Hash
  • + *
  • SHA SHA Hash
  • + *
+ * For additional modes see {@link GostDigest} + * + * @memberOf SubtleCrypto + * @method digest + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {CryptoOperationData} data Operation data + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.digest = function (algorithm, data) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.digest(algorithm, data); + + algorithm = normalize(algorithm, 'digest'); + return execute(algorithm, 'digest', [data]); + }); +}; // + +/** + * The generateKey method returns a new Promise object that will key(s) using + * the specified algorithm identifier. Key can be used in according with + * KeyUsages sequence. The recognized key usage values are "encrypt", "decrypt", + * "sign", "verify", "deriveKey", "deriveBits", "wrapKey" and "unwrapKey". + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-generateKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10 ECGOST Key Pairs
  • + *
  • GOST 28147 Key for encryption GOST 28147 modes
  • + *
  • GOST 28147-KW Key for wrapping GOST 28147 modes
  • + *
  • GOST R 34.12 Key for encryption GOST R 34.12 modes
  • + *
  • GOST R 34.12-KW Key for wrapping GOST R 34.12 modes
  • + *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • + *
+ * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}
+ * Note: Generation key for GOST R 34.10-94 not supported. + * + * @memberOf SubtleCrypto + * @method generateKey + * @instance + * @param {AlgorithmIdentifier} algorithm Key algorithm identifier + * @param {boolean} extractable Whether or not the raw keying material may be exported by the application + * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key + * @returns {Promise} Promise that resolves with {@link Key} or {@link KeyPair} in according to key algorithm + */ +SubtleCrypto.prototype.generateKey = function (algorithm, extractable, keyUsages) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.generateKey(algorithm, extractable, keyUsages); + + var privateAlgorithm = algorithm.privateKey, + publicAlgorithm = algorithm.publicKey; + algorithm = normalize(algorithm, 'generateKey'); + if (privateAlgorithm) + privateAlgorithm = normalize(privateAlgorithm, 'generateKey'); + else + privateAlgorithm = algorithm; + if (publicAlgorithm) + publicAlgorithm = normalize(publicAlgorithm, 'generateKey'); + else + publicAlgorithm = algorithm; + return execute(algorithm, 'generateKey', []).then(function (data) { + if (data.publicKey && data.privateKey) + return convertKeyPair(publicAlgorithm, privateAlgorithm, extractable, keyUsages, data.publicKey, data.privateKey); + else + return convertKey(algorithm, extractable, keyUsages, data); + }); + }); +}; // + +/** + * The deriveKey method returns a new Promise object that will key(s) using + * the specified algorithm identifier. Key can be used in according with + * KeyUsage sequence. The recognized key usage values are "encrypt", "decrypt", + * "sign", "verify", "deriveKey", "deriveBits", "wrapKey" and "unwrapKey". + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-DH ECDH Key Agreement mode
  • + *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PBKDF2 Password Based Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PFXKDF PFX Key for Derivation Algorithm
  • + *
  • GOST R 34.11-CPKDF Password Based Key for CryptoPro Derivation Algorithm
  • + *
  • SHA-PBKDF2 Password Based Key for Derivation Algorithm
  • + *
  • SHA-PFXKDF PFX Key for Derivation Algorithm
  • + *
+ * For additional modes see {@link GostSign} and {@link GostDigest} + * + * @memberOf SubtleCrypto + * @method deriveKey + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} baseKey Derivation key object + * @param {AlgorithmIdentifier} derivedKeyType Derived key algorithm identifier + * @param {boolean} extractable Whether or not the raw keying material may be exported by the application + * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key + * @returns {Promise} Promise that resolves with {@link Key} + */ +SubtleCrypto.prototype.deriveKey = function (algorithm, baseKey, + derivedKeyType, extractable, keyUsages) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.deriveKey(algorithm, baseKey, + derivedKeyType, extractable, keyUsages); + + algorithm = normalize(algorithm, 'deriveKey'); + derivedKeyType = normalize(derivedKeyType, 'generateKey'); + algorithm.keySize = derivedKeyType.keySize; + if (algorithm['public']) { + algorithm['public'].algorithm = normalize(algorithm['public'].algorithm); + algorithm['public'] = extractKey('deriveKey', algorithm, algorithm['public']); + } + return execute(algorithm, 'deriveKey', [extractKey('deriveKey', algorithm, baseKey)]).then(function (data) { + return convertKey(derivedKeyType, extractable, keyUsages, data); + }); + }); +}; // + +/** + * The deriveBits method returns length bits on baseKey using the + * specified algorithm identifier. + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-deriveBits}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-DH ECDH Key Agreement mode
  • + *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PBKDF2 Password Based Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PFXKDF PFX Key for Derivation Algorithm
  • + *
  • GOST R 34.11-CPKDF Password Based Key for CryptoPro Derivation Algorithm
  • + *
  • SHA-PBKDF2 Password Based Key for Derivation Algorithm
  • + *
  • SHA-PFXKDF PFX Key for Derivation Algorithm
  • + *
+ * For additional modes see {@link GostSign} and {@link GostDigest} + * + * @memberOf SubtleCrypto + * @method deriveBits + * @instance + * @param {AlgorithmIdentifier} algorithm Algorithm identifier + * @param {Key} baseKey Derivation key object + * @param {number} length Length bits + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.deriveBits = function (algorithm, baseKey, length) // +{ + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.deriveBits(algorithm, baseKey, length); + + algorithm = normalize(algorithm, 'deriveBits'); + if (algorithm['public']) + algorithm['public'] = extractKey('deriveBits', algorithm, algorithm['public']); + return execute(algorithm, 'deriveBits', [extractKey('deriveBits', algorithm, baseKey), length]); + }); +}; // + +/** + * The importKey method returns a new Promise object that will key(s) using + * the specified algorithm identifier. Key can be used in according with + * KeyUsage sequence. The recognized key usage values are "encrypt", "decrypt", + * "sign", "verify", "deriveKey", "deriveBits", "wrapKey" and "unwrapKey".

+ * Parameter keyData contains data in defined format. + * The suppored key format values are: + *
    + *
  • 'raw' - An unformatted sequence of bytes. Intended for secret keys.
  • + *
  • 'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.
  • + *
  • 'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.
  • + *
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-importKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-94 GOST Private and Public keys
  • + *
  • GOST R 34.10 ECGOST Private and Public keys
  • + *
  • GOST 28147 Key for encryption GOST 28147 modes
  • + *
  • GOST 28147-KW Key for key wrapping GOST 28147 modes
  • + *
  • GOST R 34.12 Key for encryption GOST 34.12 modes
  • + *
  • GOST R 34.12-KW Key for key wrapping GOST 34.12 modes
  • + *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • + *
+ * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}
+ * + * @memberOf SubtleCrypto + * @method importKey + * @instance + * @param {KeyFormat} format Key format Format specifies a serialization format for a key + * @param {CryptoOperationData} keyData + * @param {AlgorithmIdentifier} algorithm Key algorithm identifier + * @param {boolean} extractable Whether or not the raw keying material may be exported by the application + * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key + * @returns {Promise} Promise that resolves with {@link Key} + */ +SubtleCrypto.prototype.importKey = function (format, keyData, algorithm, extractable, keyUsages) // +{ + var type; + return new Promise(call).then(function () { + if (checkNative(algorithm)) + return rootCrypto.subtle.importKey(format, keyData, algorithm, extractable, keyUsages); + + if (format === 'raw') { + algorithm = normalize(algorithm, 'importKey'); + if (keyUsages && keyUsages.indexOf) { + var name = algorithm.name.toUpperCase().replace(/[\.\s]/g, ''); + if (name.indexOf('3410') >= 0 && keyUsages.indexOf('sign') >= 0) + type = 'private'; + else if (name.indexOf('3410') >= 0 && keyUsages.indexOf('verify') >= 0) + type = 'public'; + } + return keyData; + } else { + var key; + if (format === 'pkcs8') + key = gostCrypto.asn1.GostPrivateKeyInfo.decode(keyData).object; + else if (format === 'spki') + key = gostCrypto.asn1.GostSubjectPublicKeyInfo.decode(keyData).object; + else + throw new NotSupportedError('Key format not supported'); + + algorithm = normalize(key.algorithm, 'importKey'); + type = key.type; + if (extractable !== false) + extractable = extractable || key.extractable; + if (keyUsages) { + for (var i = 0; i < keyUsages.length; i++) { + if (key.usages.indexOf(keyUsages[i]) < 0) + throw DataError('Key usage not valid for this key'); + } + } else + keyUsages = key.usages; + var data = key.buffer, keySize = algorithm.keySize, dataLen = data.byteLength; + if (type === 'public' || keySize === dataLen) + return data; + else { + // Remove private key masks + if (dataLen % keySize > 0) + throw new DataError('Invalid key size'); + algorithm.mode = 'MASK'; + algorithm.procreator = 'VN'; + var chain = []; + for (var i = keySize; i < dataLen; i += keySize) { + chain.push((function (mask) { + return function (data) { + return execute(algorithm, 'unwrapKey', [mask, data]).then(function (data) { + var next = chain.pop(); + if (next) + return next(data); + else { + delete algorithm.mode; + return data; + } + }); + }; + })(new Uint8Array(data, i, keySize))); + } + return chain.pop()(new Uint8Array(data, 0, keySize)); + } + } + }).then(function (data) { + return convertKey(algorithm, extractable, keyUsages, data, type); + }); +}; // + +/** + * The exportKey method returns a new Promise object that will key data in + * defined format.

+ * The suppored key format values are: + *
    + *
  • 'raw' - An unformatted sequence of bytes. Intended for secret keys.
  • + *
  • 'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.
  • + *
  • 'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.
  • + *
+ * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-exportKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST R 34.10-94 GOST Private and Public keys
  • + *
  • GOST R 34.10 ECGOST Private and Public keys
  • + *
  • GOST 28147 Key for encryption GOST 28147 modes
  • + *
  • GOST 28147-KW Key for key wrapping GOST 28147 modes
  • + *
  • GOST R 34.12 Key for encryption GOST R 34.12 modes
  • + *
  • GOST R 34.12-KW Key for key wrapping GOST R 34.12 modes
  • + *
  • GOST R 34.11-KDF Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PBKDF2 Import Password for Key for Derivation Algorithm
  • + *
  • GOST R 34.11-PFXKDF Import PFX Key for Derivation Algorithm
  • + *
  • GOST R 34.11-CPKDF Import Password Key for CryptoPro Derivation Algorithm
  • + *
  • SHA-PBKDF2 Import Password for Key for Derivation Algorithm
  • + *
  • SHA-PFXKDF Import PFX Key for Derivation Algorithm
  • + *
+ * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}
+ * + * @memberOf SubtleCrypto + * @method exportKey + * @instance + * @param {KeyFormat} format Format specifies a serialization format for a key + * @param {Key} key Key object + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.exportKey = function (format, key) // +{ + return new Promise(call).then(function () { + if (key && checkNative(key.algorithm)) + return rootCrypto.subtle.exportKey(format, key); + + if (!key.extractable) + throw new InvalidAccessError('Key not extractable'); + + var raw = extractKey(null, null, key); + if (format === 'raw') + return raw; + else if (format === 'pkcs8' && key?.algorithm?.id) { + if (key.algorithm.procreator === 'VN') { + // Add masks for ViPNet + var algorithm = key.algorithm, mask; + algorithm.mode = 'MASK'; + return execute(algorithm, 'generateKey').then(function (data) { + mask = data; + return execute(algorithm, 'wrapKey', [mask, key.buffer]); + }).then(function (data) { + delete algorithm.mode; + var d = new Uint8Array(data.byteLength + mask.byteLength); + d.set(new Uint8Array(data, 0, data.byteLength)); + d.set(new Uint8Array(mask, 0, mask.byteLength), data.byteLength); + var buffer = d.buffer; + buffer.enclosed = true; + return gostCrypto.asn1.GostPrivateKeyInfo.encode({ + algorithm: algorithm, + buffer: buffer + }); + }); + } else + return gostCrypto.asn1.GostPrivateKeyInfo.encode(key); + } else if (format === 'spki' && key?.algorithm?.id) + return gostCrypto.asn1.GostSubjectPublicKeyInfo.encode(key); + else + throw new NotSupportedError('Key format not supported'); + }); +}; // + +/** + * The wrapKey method returns a new Promise object that will wrapped key(s). + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-wrapKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST 28147-KW Key Wrapping GOST 28147 modes
  • + *
  • GOST R 34.12-KW Key Wrapping GOST R 34.12 modes
  • + *
  • GOST 28147-MASK Key Mask GOST 28147 modes
  • + *
  • GOST R 34.12-MASK Key Mask GOST R 34.12 modes
  • + *
  • GOST R 34.10-MASK Key Mask GOST R 34.10 modes
  • + *
+ * For additional modes see {@link GostCipher}
+ * + * @memberOf SubtleCrypto + * @method wrapKey + * @instance + * @param {KeyFormat} format Format specifies a serialization format for a key. Now suppored only 'raw' key format. + * @param {Key} key Key object + * @param {Key} wrappingKey Wrapping key object + * @param {AlgorithmIdentifier} wrapAlgorithm Algorithm identifier + * @returns {Promise} Promise that resolves with {@link CryptoOperationData} + */ +SubtleCrypto.prototype.wrapKey = function (format, key, wrappingKey, wrapAlgorithm) // +{ + return new Promise(call).then(function () { + if (checkNative(wrapAlgorithm)) + return rootCrypto.subtle.wrapKey(format, key, wrappingKey, wrapAlgorithm); + + wrapAlgorithm = normalize(wrapAlgorithm, 'wrapKey'); + var keyData = extractKey(null, null, key); + if (wrapAlgorithm.procreator === 'SC' && key.type === 'private') + keyData = swapBytes(keyData); + return execute(wrapAlgorithm, 'wrapKey', + [extractKey('wrapKey', wrapAlgorithm, wrappingKey), keyData]).then(function (data) { + if (format === 'raw') + return data; + else + throw new NotSupportedError('Key format not supported'); + }); + }); +}; // + +/** + * The unwrapKey method returns a new Promise object that will unwrapped key(s). + * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-unwrapKey}

+ * + * Supported algorithm names: + *
    + *
  • GOST 28147-KW Key Wrapping GOST 28147 modes
  • + *
  • GOST R 34.12-KW Key Wrapping GOST R 34.12 modes
  • + *
  • GOST 28147-MASK Key Mask GOST 28147 modes
  • + *
  • GOST R 34.12-MASK Key Mask GOST R 34.12 modes
  • + *
  • GOST R 34.10-MASK Key Mask GOST R 34.10 modes
  • + *
+ * For additional modes see {@link GostCipher}
+ * + * @memberOf SubtleCrypto + * @method unwrapKey + * @instance + * @param {KeyFormat} format Format specifies a serialization format for a key. Now suppored only 'raw' key format. + * @param {CryptoOperationData} wrappedKey Wrapped key data + * @param {Key} unwrappingKey Unwrapping key object + * @param {AlgorithmIdentifier} unwrapAlgorithm Algorithm identifier + * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm Key algorithm identifier + * @param {boolean} extractable Whether or not the raw keying material may be exported by the application + * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key + * @returns {Promise} Promise that resolves with {@link Key} + */ +SubtleCrypto.prototype.unwrapKey = function (format, wrappedKey, unwrappingKey, + unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) // +{ + return new Promise(call).then(function () { + if (checkNative(unwrapAlgorithm)) + return rootCrypto.subtle.unwrapKey(format, wrappedKey, unwrappingKey, + unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages); + + unwrapAlgorithm = normalize(unwrapAlgorithm, 'unwrapKey'); + unwrappedKeyAlgorithm = normalize(unwrappedKeyAlgorithm, 'importKey'); + if (format !== 'raw') + throw new NotSupportedError('Key format not supported'); + + return execute(unwrapAlgorithm, 'unwrapKey', [extractKey('unwrapKey', unwrapAlgorithm, unwrappingKey), wrappedKey]).then(function (data) { + var type; + if (unwrappedKeyAlgorithm && unwrappedKeyAlgorithm.name) { + var name = unwrappedKeyAlgorithm.name.toUpperCase().replace(/[\.\s]/g, ''); + if (name.indexOf('3410') >= 0 && keyUsages.indexOf('sign') >= 0) + type = 'private'; + else if (name.indexOf('3410') >= 0 && keyUsages.indexOf('verify') >= 0) + type = 'public'; + } + if (unwrapAlgorithm.procreator === 'SC' && type === 'private') + data = swapBytes(data); + return convertKey(unwrappedKeyAlgorithm, extractable, keyUsages, data, type); + }); + }); +}; // + +/** + * The subtle attribute provides an instance of the SubtleCrypto + * interface which provides low-level cryptographic primitives and + * algorithms. + * + * @memberOf gostCrypto + * @type SubtleCrypto + */ +gostCrypto.subtle = new SubtleCrypto(); + +/** + * The getRandomValues method generates cryptographically random values. + * + * First try to use Web Crypto random genereator. Next make random + * bytes based on standart Math.random mixed with time and mouse pointer + * + * @memberOf gostCrypto + * @param {(CryptoOperationData)} array Destination buffer for random data + */ +gostCrypto.getRandomValues = function (array) // +{ + // Execute randomizer + GostRandom = GostRandom || root.GostRandom; + var randomSource = GostRandom ? new GostRandom() : rootCrypto; + if (randomSource.getRandomValues) + randomSource.getRandomValues(array); + else + throw new NotSupportedError('Random generator not found'); +}; // +//
+ +export default gostCrypto; diff --git a/src/core/vendor/gost/gostDigest.mjs b/src/core/vendor/gost/gostDigest.mjs new file mode 100644 index 00000000..4d5a04e1 --- /dev/null +++ b/src/core/vendor/gost/gostDigest.mjs @@ -0,0 +1,1260 @@ +/** + * GOST R 34.11-94 / GOST R 34.11-12 implementation + * 1.76 + * 2014-2016, Rudolf Nickolaev. All rights reserved. + * + * Exported for CyberChef by mshwed [m@ttshwed.com] + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Converted to JavaScript from source https://www.streebog.net/ + * Copyright (c) 2013, Alexey Degtyarev. + * All rights reserved. + * + * 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 HOLDER 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. + * + */ + + import GostRandom from './gostRandom.mjs'; + import GostCipher from './gostCipher.mjs'; + import crypto from 'crypto'; + +/* + * GOST R 34.11 + * Common methods + * + */ // + +var root = {}; +var rootCrypto = crypto + +var DataError = Error, + NotSupportedError = Error; + +// Copy len values from s[sOfs] to d[dOfs] +function arraycopy(s, sOfs, d, dOfs, len) { + for (var i = 0; i < len; i++) + d[dOfs + i] = s[sOfs + i]; +} + +// Swap bytes in buffer +function swap(s) { + var src = new Uint8Array(s), + dst = new Uint8Array(src.length); + for (var i = 0, n = src.length; i < n; i++) + dst[n - i - 1] = src[i]; + return dst.buffer; +} + +// Convert BASE64 string to Uint8Array +// for decompression of constants and precalc values +function b64decode(s) { + // s = s.replace(/[^A-Za-z0-9\+\/]/g, ''); + var n = s.length, + k = n * 3 + 1 >> 2, r = new Uint8Array(k); + + for (var m3, m4, u24 = 0, j = 0, i = 0; i < n; i++) { + m4 = i & 3; + var c = s.charCodeAt(i); + + c = c > 64 && c < 91 ? + c - 65 : c > 96 && c < 123 ? + c - 71 : c > 47 && c < 58 ? + c + 4 : c === 43 ? + 62 : c === 47 ? + 63 : 0; + + u24 |= c << 18 - 6 * m4; + if (m4 === 3 || n - i === 1) { + for (m3 = 0; m3 < 3 && j < k; m3++, j++) { + r[j] = u24 >>> (16 >>> m3 & 24) & 255; + } + u24 = 0; + + } + } + return r.buffer; +} + +// Random seed +function getSeed(length) { + GostRandom = GostRandom || root.GostRandom; + var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto; + if (randomSource.getRandomValues) { + var d = new Uint8Array(Math.ceil(length / 8)); + randomSource.getRandomValues(d); + return d; + } else + throw new NotSupportedError('Random generator not found'); +} + +// Check buffer +function buffer(d) { + if (d instanceof ArrayBuffer) + return d; + else if (d && d?.buffer instanceof ArrayBuffer) + return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? + d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; + else + throw new DataError('ArrayBuffer or ArrayBufferView required'); +} // + +/** + * Algorithm name GOST R 34.11 or GOST R 34.11-12

+ * + * http://tools.ietf.org/html/rfc6986 + * + * The digest method returns digest data in according to GOST R 4311-2012.
+ * Size of digest also defines in algorithm name. + *
    + *
  • GOST R 34.11-256-12 - 256 bits digest
  • + *
  • GOST R 34.11-512-12 - 512 bits digest
  • + *
+ * + * @memberOf GostDigest + * @method digest + * @instance + * @param {(ArrayBuffer|TypedArray)} data Data + * @returns {ArrayBuffer} Digest of data + */ +var digest2012 = (function () // +{ + // Constants + var buffer0 = new Int32Array(16); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + var buffer512 = new Int32Array(16); // [512, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + buffer512[0] = 512; + + // Constant C + var C = (function (s) { + var h = new Int32Array(b64decode(s)), + r = new Array(12); + for (var i = 0; i < 12; i++) + r[i] = new Int32Array(h.buffer, i * 64, 16); + return r; + })( + 'B0Wm8lllgN0jTXTMNnR2BRXTYKQIKkKiAWlnkpHgfEv8xIV1jbhOcRbQRS5DdmovH3xlwIEvy+vp2soe2lsIsbebsSFwBHnmVs3L1xui3VXKpwrbwmG1XFiZ1hJrF7WaMQG1Fg9e1WGYKyMKcur+89e1cA9GneNPGi+dqYq1o2+yCroK9ZYemTHbeoZD9LbCCdtiYDc6ycGxnjWQ5A/i03t7KbEUderyix+cUl9e8QY1hD1qKPw5Cscvzius3HT1LtHjhLy+DCLxN+iToepTNL4DUpMzE7fYddYD7YIs16k/NV5orRxynX08XDN+hY5I3eRxXaDhSPnSZhXos98f71f+bHz9WBdg9WPqqX6iVnoWGicjtwD/36P1OiVHF82/vf8PgNc1njVKEIYWHxwVf2MjqWwMQT+amUdHraxr6ktufWRGekBo+jVPkDZyxXG/tsa+wmYf8gq0t5oct6b6z8aO8Jq0mn8YbKRCUfnEZi3AOTB6O8Okb9nTOh2urk+uk9QUOk1WhojzSjyiTEUXNQQFSiiDaUcGNyyCLcWrkgnJk3oZMz5H08mHv+bHxp45VAkkv/6GrFHsxaruFg7H9B7nAr/UDX+k' + + '2ahRWTXCrDYvxKXRK43RaZAGm5LLK4n0msTbTTtEtIke3jaccfi3TkFBbgwCqucDp8mTTUJbH5vbWiODUURhcmAqH8uS3DgOVJwHppqKK3uxzrLbC0QKgIQJDeC3Vdk8JEKJJRs6fTreXxbs2JpMlJsiMRZUWo837ZxFmPvHtHTDtjsV0fqYNvRSdjswbB56SzNprwJn558DYTMbiuH/H9t4iv8c50GJ8/PkskjlKjhSbwWApt6+qxst84HNpMprXdhvwEpZot6Ybkd9Hc2678q5SOrvcR2KeWaEFCGAASBhB6vru2v62JT+WmPNxgIw+4nI79CezXsg1xvxSpK8SJkbstnVF/T6UijhiKqkHeeGzJEYne+AXZufITDUEiD4dx3fvDI8pM16sUkEsIAT0roxFvFn5443'); + + // Precalc Ax + var Ax = (function (s) { + return new Int32Array(b64decode(s)); + })( + '5vh+XFtxH9Alg3eACST6FshJ4H6FLqSoW0aGoY8GwWoLMumi13tBbqvaN6RngVxm9heWqBpoZnb13AtwY5GVS0hi84235kvx/1ximmi9hcXLgn2m/NdXlWbTba9pufCJNWyfdEg9g7B8vOyxI4yZoTanAqwxxHCNnrao0C+839aLGfpR5bOuN5zPtUCKEn0LvAx4tQggj1rlM+OEIojs7c7Cx9N3wV/S7HgXtlBdD165TMLAgzaHHYwgXbTLCwStdjyFWyigiS9YjRt59v8yVz/s9p5DEZM+D8DTn4A6GMnuAQom9fOtgxDv6PRBGXmmXc2hDH3pOhBKG+4dEkjpLFO/8tshhHM5tPUMz6aiPQlftLyc2EeYzeiKLYsHHFb5f3dxaVp1apzF8C5xoLoevKZj+atCFeZyLrGeIt5fu3gNuc4PJZS6FIJSDmOXZk2ELwMeagII6phcfyFEob5r8Ho3yxzRY2Lbg+COK0sxHGTPcEebq5YOMoVrqYa53ucetUeMh3r1bOm4/kKIX2HW/RvdAVaWYjjIYiFXkj74qS78l/9CEUR2+J19NQhWRSzrTJDJsOCnElYjCFAt+8sBbC16A/qnpkhF' + + '9G6LOL/GxKu9vvj91HfeujqsTOvIB5t58JyxBeiHnQwn+moQrIpYy4lg58FAHQzqGm+BHko1aSiQxPsHc9GW/0NQGi9gnQqf96UW4MY/N5Yc5KazuNqSUhMkdSw44IqbpahkczvsFU8r8SRXVUmzP9dm2xVEDcXHp9F5455Ct5La3xUaYZl/04agNF7AJxQjONVRe22pOaRlGPB3EEADtAJ5HZClrqLdiNJniZxKXQqTD2bfCihlwk7p1CBFCbCLMlU4kWaFKSpBKQe/xTOoQrJ+K2JUTcZzbFMERWKV4Ada9AbpU1GQih8vO2vBI2Fvw3sJ3FJV5cY5Z9Ezsf5oRCmIOcfw5xHiQJuH9xlk+aLpOK3D20sHGQwLTkf5w+v0VTTVdtNriENGEKBa64sC2CDDzfWCMvJRbeGEDb7Cseeg6N4GsPodCHuFS1QNNDM7QuKaZ7zKW3/YpgiKxDfdDsY7s6nZQ+2BIXFNvV5lo7FnYe3nte6haSQx98jVc6v21R/GheGjZxpeBjzUBBDJLSg6uY8ssEACj+vAbLLy95AX1k8Rb6HTPOBzWfGpnuSqeE7WjHTNwAZuKhnVxztC2ocStBYccEXD' + + 'NxWC5O2TIW2s45BBSTn2/H7F8SGGIjt8wLCUBCusFvv510U3mlJ+v3N8Py6jtoFoM+e42brSeMqpoyo0wi/+u+SBY8z+370NjllAJG6lpnBRxu9LhCrR5CK60GUnnFCM2RSIwhhgjO4xnqVJH3zaF9OU4SgTTJxgCUv0MnLV47Ob9hKlpKrXkcy72kPSb/0PNN4fPJRq0lBPW1RomV7ha9+fr2/qj3eUJkjqWHDdCSu/x+Vtcdl8Z93msv9PIdVJPCdrRjroYAORdntPr4bHH2ihPng11LmgtowRXwMMn9QUHdLJFlggAZg9j33dUySsZKpwP8wXUlTCyYmUjgK0Jj5edtafRsLeUHRvA1h9gARF2z2CknLx5WBYSgKbVgvz+65Ypz/83GKhWl5ObK1M6EupblXOH7jMCPl0eq6CslPBAhRM9/tHG58EKJjz6442BosnrfLv+3rtypf+jApevneOBRP099jPMCwlAcMri/eNkt38F1xVTfhlxX9GBS9f6vMwG6Ky9CSqaLfsu9YNhpmPDzUBBHVMAAAAAAAAAADxLjFNNNDM7HEFIr4GGCO1rygNmTDABcGX/VziXWk8ZRmkHMYzzJoV' + + 'lYRBcvjHnrjcVDK3k3aEqZQ2wTokkM9YgCsT8zLI71nEQq45fO1PXPoc2O/jq42C8uWslU0pP9Fq2CPokHobfU0iSfg88EO2A8ud2Hn58z3eLS8nNtgmdCpDpB+JHuLfb5iZnRtsEzrUrUbNPfQ2+rs131AmmCXAlk/cqoE+bYXrQbBTfuWlxAVAunWLFghHpBrkO+e7RK/juMQp0GcXl4GZk7vun765rpqN0eyXVCHzVyzdkX5uMWOT19rir/jOR6IgEjfcUzijI0PeyQPuNXn8VsSompHmAbKASNxXUeASlvVk5Lfbe3X3GINRWXoS222VUr3OLjMenbsjHXQwj1INcpP90yLZ4gpEYQwwRnf+7uLStOrUJcow/e4ggAZ1YerKSkcBWhPnSv4UhyZOMCzIg7J78RmlFmTPWbP2gtyoEap8HnivWx1WJvtkjcOytz6RF99bzjTQX3zwarVvXf0lfwrNEycYV03I5nbFKp4HOaflLriqmlSGVT4PPNmjVv9IrqqSe36+dWUlrY4th30ObPn/28hBOx7MoxRQyplpE74w6YPoQK1REAmVbqccsbW2ui20NU5Eab3KTiWgBRWvUoHKD3Hh' + + 'dEWYy40OK/JZP5sxKqhjt++zim4ppPxja2qjoEwtSp09lesO5r8x46KRw5YVVL/VGBacju+by/URXWi8nU4oRrqHXxj6z3Qg0e38uLbiPr2wBzby8eNkroTZKc5libb+cLei9tpPclUOclPXXG1JKQTyOj1XQVmnCoBp6gssEI5J0HPFa7EaEYqrehk55P/XzQlaCw44rO/J+2A2WXn1SJK95pfWfzQix4kz4QUUvGHhwdm5dcm1StImYWDPG82AmkSS7Xj9hnGzzKsqiBqXk3LOv2Z/4dCI1tRbXZhalCfIEagFjD9V3mX1tDGWtQYZ90+WsdZwbkOFnR6Ly0PTNlqrioXM+j2E+ce/mcKV/P2iH9Wh3ktjD82z73Y7i0VtgD9Z+Hz3w4WyfHO+XzGRPJjjrGYzsEghv2FnTCa4+BgP+8mVxMEwyKqghiAQdhqYYFfzQiEBFqr2PHYMBlTMNS3bRcxmfZBCvPRalkvUA4Jo6KDD7zxvPae9ktJp/3O8KQriAgHtIoe33jTN6IWBj9kB7qfdYQWb1vonMhmgNVPVbxrodMzOyeoxJFwug/VUcDRVXaB75JnOJtKsVue+9/0WGFelBU44' + + 'ag59pFJ0NtFb2Go4HN6f8sr3dWIxdwwysJqu2eJ5yNBd7xCRxgZ02xEQRqJRXlBFI1Ns5HKYAvzFDLz39bY8+nOhaIfNFx8DfSlBr9nyjb0/Xj60Wk87nYTu/jYbZ3FAPbjj0+cHYnEaOij58g/SSH68fHW0nnYndOXyk8frVlwY3PWeT0eLpAxu9E+prctSxpmBLZjax2B4iwbcbkadDvxl+Op1IexOMKX3IZ6OC1Ur7D9lvKV7a93QSWm68bdemZBM2+OU6lcUsgHR5upA9ruwwIJBKErdUPIEY7+PHf/o1/k7k8usuE2Mto5HfIbowd0bOZImjj98WqESCdYvyy89mKvbNcmuZxNpViv9X/UVweFsNs7igB1+su3485sX2pTTfbAN/gGHe8PsdguK2suEld/hU65EBaJHc7e0ELMShXt4PDKr3463cNBoElE7U2c5udLj5mVYTVficbJkaNeJx4/JhJclqTW7+n0a4QKLFTej36ZBiNDNXZvDeN56Ssgsmk2Az7dCd38bg722IHLSiDodM711XnotS6tqj0H02qtruxyV2ZBc/+f9jTG2g6pkIhGbOB/ArvuEQgIsSaD5CMZjAzrj' + + 'pCivCASTiCat5Bw0GopTx65xIe535qhdxH9cSiWSnoy1OOmqVc3YYwY3eqna2OspoYroe7MnmJVu39pqNeSEFGt9nRmCUJSn1Bz6VaTobL/lyu3J6kLFnKNsNRwOb8F5UYHk3m+rv4n/8MUwGE0X1J1B6xWEBFiSHA1SUCjXOWHxeOwYDKiFapoFcQGO+BHNQJGifD7178wZrxUjn2Mp0jR0UO/5HrmQ4RtKB43Sd1m5Vh3l/GATMZEvH1otqZPAFlTctluiGRo+Ld4JimuZ64pm1x4PguP+jFGtt9VaCNdFM+UPiUH/fwLm3We9SFns4Giqul321S/CSCbj/0p1pWw5Bw2IrN34ZIZUjEaRpG/Rvr0mE1x8DLMPkwOPFTNKgtmEn8G/mmmcMguoVCD65PpSgkOv+QdnntTWz+loowi4Jf1YLESxR5t2kbxe3LO7x+phkEj+ZRYQY6YfgXryM0fVOGg0CaaTY8LOmExt7TAqn9/YbIHZHXseOwYDKmaUZmCJ6/vZ/YMKWY7mc3UgewdEmhQK/ElfLKilcbZZMjQfmG+KRbvC+zgapKBQs3LCVCOjrdgfrzoXJzwLi4a7bP6DJY3IabWi' + + 'KHkCv9HJgPH1qUvWazg3r4iACnmyyroSVVBDEAg7DUzfNpQOB7nusgTRp85nkLLFYSQT//EltNwm8SuXxSwST4YII1GmLyis75NjL5k35ec1B7BSKTob5ucsMK5XCpxw01hgQa4UJeDeRXSz151MxJK6IoBAxWha8AsMpdyMJxy+Eofx9pxabvOeMX+x4NyGSV0RQCDsNC1pm0B+PxjNS9yjqdRq1RUoDR0U8nmJaSQAAAAAAAAAAFk+t1+hlsYeLk54FgsRa9htSuewWIh/juZf0BOHLj4Gem3bu9MOxOKsl/yJyq7xsQnMszweGdvhifPqxGLuGGR3cM9JqoetxlbFfsplV/bWA5U92m1s+5o2ko2IRFbgfB7rjzeVn2CNMdYXnE6qqSNvrDrX5cAmYkMEn6ZTmRRWq9NmncBSuO6vAsFTp8IKKzzLA243I8AHk8nCPZDhyizDO8ZeL27X00z/VjOXWCSeselOZDJdaqY34W01lHJCCnn45mG+Yj94UhTZBALHRBNILvH98MiWWxP2m8XsFgmpDogpKBTlkr5OGYtUKhB9cszAD8vrr+cbG0nIRCIrcD4lZBZNqEDp1SDGUT4f9Plm' + + 'usMgP5EM6Kvy7dHCYcR+8IFMuUWs02Hzlf64lEo5IQVcnPAsFiLWrZcYZfP3cXjpvYe6K5vwofREQAWyWWVdCe11vkgkf7wLdZYSLhfP9Cq0SwkXhel6FZZrhU4nVdqf7uCDkkkTR5EyQypGI8ZSuahGW0etPkN0+LRfJBKxXoskF/bweGRLo/shYv5/3aURS7vMJ52kbcEBc+C90CSidiIgjFmivKCKj8SQbbg2803kuQ10OmZn6nFHteBwX0bvJ4LLKhUIsDnsBl719FsefSG1sYPP0FsQ2+czwGApXHefpzZyOUwBfs9VMhGGwxyB2HIOGg1Fp+07j5l6Pd+JWDr8ecft+ysu6aQZhkPvDs5fCc32e04tN09qa+n6NN8Etq3UcDihI/mNIk0KBX6qocliSLhcG/eo4/2XYDCaLrULKm5bo1GCDetCxOH+p1cilI1YKZodg3N/z5zIZLrUUaVbT7XUtypQCL9Tgc49eZdGptjV5C0E5dIrgPx+MIeWV7aed7VzVKA5aUQdgJfQtDMwyvvz4vDP4o533eC+jMNisS4lnElPRqbOcm+529HKQeJCwe7RTbp2Ay/0eqMPsEWyaKk6zeTM' + + 'r38L6IRUnQgEg1SzwUaCY5JUNcLIDv7S7k438n/f+6cWejOSDGDxTfsSO1LqA+WESgyrU/27kAed6vY4D3iKGctI7FWPDLMqtZ3Estb+9+Dc28oi9PPsthHfWBNUmpxA4z/e31aKztOgwcgSQyLpwwela4FY+m0NdyeVebHh893ZsYt0QirABLjsLZ//q8KU9Kz4qC11kU97v2mx7ytoeMT2L69Iesfhds6AnMZ+XQxnEdiPkuTBTGJ7mdkkPe3+I0qlw9+2i1GQmx8VJi2/bU9m6gVLYry1GuLPWlKqaui+oFP70M4BSO1oCMDmYxTJQ/4WzRWoJxDNBJIxoGlw9ue8imyXzEywM3zoNfyzucBl3vJYfMeA81IhTt5BMrtQlfFeQ5D0k9+HCDliXdLg8UExPBr7i2avkXIK8FGyEbxHfUJ+1O6lcy47TO72474lgmJ4NOsLzEOcA+PdeOckyCh3MorZhn35FLUZReJDsPJXSw+I9+uX4oi2+piapJQ6GcTwaMsWhYZQ7mQJrxH6733zF9XATqukelZ8VJi0xqm2u/uAT0IYjjzCK887xc0L0EM26qo5dxPwL6wb7DMTLCUG26fw00iN' + + '1+Zda/LDGh5eubIWH/gg9YQuBlDEbg+fcWvrHZ6EMAGpM3WMqzFe1D/kFP2ieSJlJ8nxcB7wCTJzpMHKcKdxvpQYS6bnaz0OQNgp/4wUyH4PvsP6x3Z0yzYWqWNKapVyjxORGcJe+Tf1Re1NWuo/nugCSZZQujh7ZDfnvQtYLiLmVZ+J4FPiYYCtUuMFKI38bcVaI+NLmTXeFOD1GtCtCcY5BXimWYZeltdhcQlIfLHi1ss6IRVgAgHpFeV3n67RrbAhP2p33LeYgLduuaGmq12fjSSGRM+b/V5FNsVmJljxxrn+m6y9/erNY0G+mXnE76ciFwhAVXZRB3Hs2I5UPsK6UctnHwQ9CtSCrHGvWHn+eHoEXNrJNrI4rzOOBJrtvYZsyUly7iZhXabrvYECkDKV/dCLLBcR+DQEYHO/CurzCZMpdY/8QhyusT59z6k0uiMHSBGIgysk785Ch0zmXA5X1h+w6doas9G61vmbNDzAdXsciTxFgitRDbhAOpKXXHaYwfHbYUo+DQEY1eaMtNYPSI6FXLTPrpYeDfPLM9k6jlWrFKAO10IXAyhiN4nBg4tt0ZyUYpKJX+997Ts668/LuOZOSjFJ' + + 'Bkx+ZC9lw9w9Kz4qTFpj2lvT80CpIQxHtHTRV6FhWTGsWTTaHehyZm7jZRF693ZbyG7TZxawXESbpohcIB1JxbkFOHqINGxFExByxLq53f+/SUYep1GvmdUpd7wc4FuhsPeF5GAn21JUbTC6bld4jDBa1wdlD1auyYfGgmEv8pWlq4lE9fvFcX7VKOdZ8kTKjdy7zix9uIiqFUq+Mo2xuh5hm+mT7OiLCfK9nugTtxd0AapLKF0csyGFjxQxlcruSMOBhBOY0bj8t1DTsvmIiTmoapmNHOG5H4iODORzRlp4mVaDdpeHFgLPKtfuI0G/hccTtbPxoU7/kW/hK0Vn53waAjC30QV1DJj8yF7Km6Wj5/cg2p4GrWpgMaK7sfQ4lz50lH7X0mAs9GY5GMD/ml9Qp/NoZ44kNNmDtKRJ1M1orxt1VZK1h388PQIubeobq/xfW0USH2sNcektKVU1dN/99RBtTwPYCBuoe5+MGcbbfqGjrAmBu7vKEq1mFy36eXBDZgEIKccXkyZ3e/9fnAAAAAAAAAAA6yR2pMkG1xVyTdQvBzjfb7dS7mU43bZfN/+8hj31O6OO+oT8tcFX5unrXHMnJZaq' + + 'GwvavyU1xDmG4SyHKk1OIJlpoovOPgh6+vsut52cS1UFakFWttksslo65qXevqKWIqOwJqgpJYBTyFs7Nq0VgbEekAEXuHWDxR86Sj/laTDgGeHtzzYhveyBHSWR/LoYRFt9TE1SSh2o2mBp3K7wBVj1zHIwneMp1MBiWWt/9XDOIq0DOdWfmFkc2ZdHAk34i5DFqgMYe1T2Y9J/w1bQ8NhYnpE1tW7VNTCWUdPWehwS+WchzSZzLtKMHD1EGjasSSqUYWQHf2ktHXPcb19RS28KcPQNaNiKYLSzDsoerEHTZQnYM4WYfQs9l0kGMPaonszJCpbEZXeiDuLFrQGofOSatV4OcKPepEKcoYJka6Dal7RG25Yvaszth9TX9t4nKrgYXTelPEafJdzv4VvLpsGcbvn+o+tTp2SjkxvYhM4v0lkLgXwQ9FaiGm2AdDkz5XOgu3nvDQ8VXAygldweI2wsT8aU1DfkEDZN9iMFMpHdMt/Hg2xCZwMmPzKZvO9uZvjNauV7b52MNa4rW+IWWTGzwuISkPh/k70gJ7+RUANpRg6QIg0bVimeJ2+uGdMoY5KMPFOiQy9wgv746Rue0LxveSw+7UD3' + + 'TEDVN9LeU9t16L+uX8KyYk2pwNKlQf0KTo//4Dz9EmQmIOSVaW+n4+Hw9Ai4qY9s0aojD92m2cLH0BCd0cYoj4p50E90h9WFRpRXm6NxC6I4QX98+oNPaB1HpNsKUAflIGya8UYKZD+hKN33NL1HEoFERwZytyMt8uCGzAIQUpMYLeWNvIkrV8qh+bD4kx37a4kkR8wuWun53RGFBCCkO0vlvraKJD7WVYQlXxnI1l07Z0BOYz+gBqaNtnZsRyof94rHmrTJfiHDU0QuEICq7JpPnblXgucUBbp7yCybMiAxpUZl+LZeT7G2Ufd1R/TUi/oNhXukZoKFqWxaoWqYu5kPrvkI63nJoV43okf0pi12hX3NXSd0HvjFC4AKGCC8vmXcsgH3orRmbRuYb5Qm50zJIb9TxOZIlUEKD5PZykIgzcyqZHuk70KaQGCJChhxDE6k9psys4vM2jYt3jVM05bcI7x8Wy+pwwm7aKqFGrPSYTGnNkjgEwIdxSlB/E2yzVrat3BL5IqneWXZhO1x5jI4b9YXNLuk6C1t1TirckVcIUfqYXe0sV2hq3DPCRzorJB/znK4vf9XyF39lyJ4qKTkTGprb5QN' + + 'OFGZW08f3+RiV4zK7XG8ntmIK7DAHSwKkXudXRE8UDuiwx4RqHZDxuRjySOjmcHO9xaGxX6odtyHtKlz4JbVCa8NVn2dOlgUtAwqP1ncxvQ2AviEldEh3dPh3T2YNkhK+UXnGqRmiOV1GFR+sqWR9ZNmWHRQwB2JnqgQGGWMBltPVAgMvEYDoy0DhMZRN7893DJQeOyGHirqMKj8eVc/9yFNIDDKBQy2ZfAyK4AWwwxpvpbdGyRwh9uV7pmB4WG40fwYFNnKBfiCDtK7zA3nKWPXYFBDDxTHO8yw6KCdOg+OQHZNVz9UojnRdcHhYXe9EvWjfHNPH0urN8EvH9/CbVZIsWc5XNDxbATtFTe/QqftlxYdFDBAZX1sZ9qrcrgH7Bf6h7pO6Dzfr3nLAwT7wXM/BgVxvEY+eNYcEofpiifQfPSOd7StobnCYlNskN0m4kSbWGCAFgWPwJrX+UH8+/rYzqlL5G0Oo0PyiwYI65+bEmvQSRc0e5qSh0rnaZwiGwF8QsTmnuA6TFxyDuOSVktun14+o5naa6NT9FrYPTXn/uCQTBskJSLQCYMlh+ldhCmAwA8UMOLGs8Cghh4okwh0M6QZ1yny' + + 'NB89rdQtbG/uCj+u+7Kljkruc8SQ3TGDqrcttbGhajSpKgQGXiOP33tLNaFoa2/MaiO/bvSmlWwZHLlrhRrTUlXVmNTW3jUayWBN5fKufvMcpsKjqYHhct4vlVGtelOYMCWq/1bI9hYVUh2dHihg2VBv4xz6RQc6GJxV8StkewsBgOyarn6oWXzsi0AFDBBeI1DlGYv5QQTvitM0VcwN1wenvuFtZ3+S5eMluQ3naZdaBhWRom5jerYR7xYYIItGCfTfPrepgaseuweK6H2swLeRA4y2XiMfD9ONRXSwVmBn7fcCweqOvrpfS+CDEjjN48R3ws7+vlwNzkhsNUwb0oxds2QWwxkQJuqe0adicyQDnSmz74Ll658o/ILL8q4CqKronPBdJ4ZDGqz6J3SwKM9HH54xt6k4WBvQuOOSLsi8eBmbQAvvBpD7cce/QvhiHzvrEEYDBJloPnpHtVrY3piPQmOmldGQ2AjHKm5jhFMGJ1J7wxnXy+uwRGbXKZeu5n4MCuJljHwU0vEHsFbIgHEiwywwQAuMinrhH9Xaztug3ts46YoOdK0Qk1TcxhWmC+kaF/ZVzBmN3V/+uL2xSb/lMCiviQrt' + + '1lum9bStemp5VvCIKZcifhDoZlUys1L5DlNh39rO/jnOx/MEn8kBYf9itWFnf18ul1zPJtIlh/BR7w+GVDuvYy8eQe8Qy/KPUnImNbu5SoiujbrnM0TwTUEHadNmiP2as6uU3jS7uWaAExeSjfGqm6VkoPDFETxU8THUvr2xoRd/caLz6o71tUCHhUnI9lXDfvFOaUTwXezURmPc9VE32PKs/Q1SM0T8AAAAAAAAAABfvG5ZjvVRWhbPNC7xqoUysDa9bds5XI0TdU/m3TG3Ervfp3otbJCUiefIrDpYKzA8aw4JzfpFncSuBYnH4mUhSXNad39f1GjK/WRWHSybGNoVAgMvn8nhiGckNpQmg2k3ghQeO6+JhJy11TEkcEvp19tKbxrT0jOm+YlDKpPZv501OauKDuOwU/LKrxXH4tFuGSg8dkMPFT3r4pNjhO3EXjyCwyCL+QMzuINMuUoT/WRw3rEuaGtVNZ/RN3pTxDZhyqV5AvNZdQQ6l1KC5Zp5/X9wSCaDEpzFLukTaZzNeCi5/w59rI0dVFV0TnignUPLfYjMs1IzQUS9EhtKE8+6TUnNJf26ThE+dssgjAYILz/2J7oieKB2' + + 'wolX8gT7supFPf6B5G1n45TB5pU9p2IbLINoXP9JF2TzLBGX/E3spSsk1r2SLmj2sit4RJrFET9I87bt0SF8MS6erXW+tVrWF0/YtF/ULWtO1OSWEjir+pLmtO7+vrXQRqDXMgvvgghHIDuopZEqUST3W/jmnj6W8LE4JBPPCU7+4ln7yQH3dydqcksJHNt9vfj1Ae51R19ZmzwiTeyGkW2EAY+Zwer+dJi45BzbOazgWV5xIXxbtyqkOic8UMCv9QtD7D9UO26Djj4hYnNPcMCUkttFB/9Ycr/qn9/C7mcRaIrPnM36oBqBkNhqmDa5esvZO8YVx5XHMyw6KGCAyoY0RelO6H1Q9pZqX9DW3oXprYFPltXaHHCiL7aePqPVCmn2jVgrZEC4Qo7Jwu51f2BKSeOsjfEsW4b5CwwQyyPh2bLrjwLz7ik5E5TT0iVEyOChf1zQ1qq1jMal96JurYGT+wgjjwLC1caPRlsvn4H8/5zSiP26xXcFkVfzWdxHHSYuOQf/SSv7WCIz5ZrFV92yvOJC+LZzJXe3Ykjgls9vmcSm2D2nTMEUfkHreVcB9IuvdpEqkzc+8p0kmywKGenhYyK2+GIv' + + 'VTaZQEd1f3qfTVbVpHsLM4IlZ0ZqoRdMuPUFfesIL7LMSMEL9EdfUzcwiNQnXew6lo9DJRgK7RAXPSMs9wFhUa5O0J+Ub8wT/UtHQcRTmHMbWz8N2ZM3ZS/8sJZ7ZEBS4CN20gqJhAyjrjpwMpsY10GcvSM13oUm+v6/EVt8MZkDlwdPhaqbDcWK1PtINrlwvsYL4/xBBKge/zbcS3CHchMf3DPthFO2CETjPjQXZNMP8RtuqzjNOWQ1Hwp3YbhaO1aU9QnPug4whXCEuHJF0Eevs70il6488rpcL29rVUp0vcR2H09w4c/fxkRx7cRe5hB4TB3ArxZ6yinWPBE/KC3tQRd2qFmvrF8hHpmj1e7UhPlJqH7zOzzjbKWW4BPk0SDwmDqdQyxrxARk3Fl1Y2nV9eXRlWyemulfBDaYuyTJ7MjaZqTvRNaVCMilsurGxAwiNcBQO4A4wZO6jGUhAxzux11GvJ6P0zEBGTdRWtHY4uVohuylD7E3EI1XecmRcJ87aQXKQgZP61CDFoDK7+xFavMkG9I4WNZzr+GBq74kL1Tnytm/jAIR8YENzBn9kLxNuw9DxgqVGERqnaB2HaG/y/E/VwEq' + + 'K95PiWHhcrUnuFOoT3MkgbCx5kPfH0thGMw4Qlw5rGjSt/fXvzfYITEDhkowFMcgFKokY3Kr+lxuYA21TrrFdDlHZXQEA6PzCcIV8Lxx5iMqWLlH6YfwRXtM3xi0d73Ylwm165Bsb+BzCDwmgGDZC/7cQA5B+QN+KElIxuRL6bhyjsroCAZb+wYzDp4XSSsaWVCFYWnnKU665PT85sQ2T8p7z5XjDnRJfX/RhqM+lsJSg2EQ2FrWkE36oQIbTNMSkTq7dYclRPrdRuy5FA8VGD1lmmsehpEUwj8sq9cZEJrXE/4GLdRoNtCmBlay+8HcIhxaed2QlJbv0m28obFJNQ537aAjXk/Jy/05W2to9rkN4OrvpvTUxAQi/x8ahTLn+Wm4Xt7WqpR/biAHrvKPPzrQYjuBqTj+ZiTui3qtoae2gujdyFZge6eMxW8oHiowx5slekX6oI1bQXTgZCsws19ji/9+rgJUS8mvnAwF+AjOWTCK+YtGro/FjanMVcOIgDSWx2dtDrHzPKrh5w3XurtiAjJuorS/1QIPhyAYccudXKdUqbcSzoQWadh96DxWimGEeF62c59CC7pssHQeK/EtW2Dqwc5H' + + 'dqw19xKDaRwsa7fZ/s7bX/zNsY9MNRqDH3nAEsMWBYLwq62uYqdMt+GlgByC7wb8Z6IYRfLLI1dRFGZfXfBNnb9A/S10J4ZYoDk9P7cxg9oFpAnRkuOwF6n7KM8LQGX5JamiKUK/PXzbdeInA0Y+ArMm4QxatdBs55aOgpWmLea5c/OzY26tQt9XHTgZwwzl7lSbcinXy8USmSr9ZeLRRvjvTpBWsChktwQeE0Aw4ovALt0q2tUJZ5MrSvSK6V0Hb+b7e8bcR4Qjmqy3VfYWZkAaS+29uAfWSF6o04mvYwWkG8IgrbSxPXU7MriXKfIRmX5YS7MyICkdaDGTztocf/9atsDJn4GOFrvV4n9n46GlnTTuJdIzzZj4roU7VKLZbfcK+ssQXnl5XS6ZubukJY5De2dEM0F4AYb2zohmgvDr8JKjuzR70rzX+mLxjR1VrdnX0BHFVx4L0+Rxsb3/3qpsL4CO6v70XuV9MfbIgKT1D6R/8ET8oBrdycNR9bWV6nZkbTNS+SIAAAAAAAAAAIWQnxb1jr6mRilFc6rxLMwKVRK/Odt9Lnjb2Fcx3SbVKc++CGwta0ghi102WDoPmxUs0q36zXis' + + 'g6ORiOLHlbzDudplX3+Sap7LoBssHYnDB7X4UJ8vqep+6NbJJpQNzza2fhqvO27KhgeYWXAkJav7eEnf0xqzaUx8V8yTKlHi2WQTpg6KJ/8mPqVmxxWmcWxx/DRDdtyJSk9ZUoRjevja8xTpiyC88lcnaMFKuWaHEIjbfGguyLuIcHX5U3pqYi56RljzAsKiYZEW2+WCCE2ofd4BgybnCdzAGnecaZfo7cOcPax9UMimCjOhoHiowMGoK+RSs4uXP3Rr6hNKiOmiKMy+uv2aJ6vq2U4GjHwE9IlSsXgiflBc9Iyw+wSZWWAX4BVt5Iq9RDi08qc9NTGMUormSf9YhbUV75JN/Pt2DGYcIS6SVjS0kxlcxZp5hpzaUZoh0ZA+MpSBBbW+XC0ZSs6M1F8umEONTKI4Epzbm2+pyr7+OdSBsmAJ7wuMQd7R6/aRpY4VTm2mTZ7mSB9UsG+OzxP9iknYXh0ByeH1r8gmURwJTuP2mKMwde5nrVrHgi7sTbJDjdR8KMGZ2nWJ9oM32xzoks3ON8V8Id2jUwWX3lA8VGBqQvKqVD/3k11yen5zYhup4jKHUwdFnfFWoZ4Pwt/kd8Yd07TNnCJ9' + + '5Yd/A5hqNBuUnrKkFcb07WIGEZRgKJNAY4DnWuhOEbCL53K21tDxb1CSkJHVls9t6GeV7D6e4N98+SdIK1gUMshqPhTuwm20cRnNp42swPbkAYnNEAy265KtvDoCj9/3sqAXwtLTUpwgDav40FyNazSnj5ui93c347RxnY8jHwFFvkI8L1u3wfceVf79iOVdaFMDK1nz7m5ls+nE/wc6qncqwzma5evsh4Ful/hCp1sRDi2y4EhKSzMSd8s92N7dvVEMrHnrn6U1IXlVKpH1x4qwqWhG4GptQ8foC0vwszoIybNUaxYe5TnxwjXrqZC+wb7yN2YGx7IsIJIzYUVpqusBUjtvwyialGlTq5Nazt0nKDj2PhM0DosEVeyhK6BSd6GyxJeP+KKlUSLKE+VAhiJ2E1hi0/HN243f3gi3bP5dHhLInkoXig5WgWsDlphn7l95lTMD7Vmv7XSLq3jXHW2Sny35PlPu9dio+Lp5jCr2GbFpjjnPa5Xdry90kQTi7CqcgOCIZCfOXI/YgluV6sTg2Zk6xgJxRpnDpRcwdvk9GxUfUKKfQp7VBeorx1lGNGZaz9x/S5hhsftTKSNC98chwAgOhkEw' + + 'hpPNFpb9e3SHJzGScTaxS9NEbIpjoXIbZpo16KZoDkrKtljyOVCaFqTl3k70Loq5N6dDXug/CNkTTmI54mx/loJ5Gjwt9nSIP27wCoMpFjyOWn5C/etlkVyq7kx5gd21GfI0eFrx6A0lXd3j7Zi9cFCJijKpnMysKMpFGdpOZlauWYgPTLMdIg2XmPo31tsmMvlo8LT/zRqgDwlkTyWFRfo61RdeJN5y9GxUfF2yRhVxPoD7/w9+IHhDzytz0qr6vRfqNq7fYrT9ERus0W+Sz0q6p9vHLWfgs0FrXa1J+tO8oxaySRSoixXRUAaK7PkU4nwd6+Me/EBP5Ix1m+2iI37c/RQbUix4TlBw8XwmaBzmlsrBWBXzvDXSpks7tIGngAz/Kf59/fYe2frD1bqksGwmY6ke9ZnRA8EZkTRAQ0H3rU3tafIFVM2dlkm2G9aryMO95+rbE2jRMYmfsCr7ZR0Y41Lh+ufx2jkjWu98psGhu/XgqO5PepE3eAXPmgseMThxYYC/jlvZ+DrL2zzlgAJ15RXTi4l+Ry0/IfD7vMYtlG63ho6jlbo8JI0hlC4J5yI2Rb/eOYP/ZP65AuQbscl3QWMNENlX' + + 'w8sXIrWNTsyieuxxnK4MO5n+y1GkjBX7FGWsgm0nMyvhvQR6116/AXn3M6+UGWDFZy7JbEGjxHXCf+umUkaE82Tv0P1144c07Z5gBAdDrhj7jimTue8UTThFPrEMYlqBaXhIB0I1XBJIz0LOFKbunhysH9YGMS3Oe4LWukeS6budFBx7H4caB1YWuA3BHEouuEnBmPIfp3d8qRgByNmlBrE0jkh+wnOtQbINHph7OkR0YKtVo8+744TmKANFdvIKG4fRbYl6YXMP4n3v5F1SWIPN5rjKPb63DCNkftAdERl6Nio+oFkjhLYfQPPxiT8QddRX0UQEcdxFWNo0I3A1uNymEWWH/CBDjZtn08mrJtArC1yI7g4lF2/nejgqtdqQJpzEctnY/jFjxB5G+qjLibervHcWQvUvfR3khS8SbzmoxrowJDOboGAFB9fO6IjIj+6Cxhogr65XokSJJteAEfyl5yg2pFjwByvOu49LTL1Je75K820koTyv6Zu3aVV9EvqevQWntanowEuqW4Nr20JzFI+sO3kFkIOEgShRwSHlV9NQbFWw/XL/mWrLTz1hPtoMjmTi3APwhoNW5rlJ6QTq1yq7Cw/8' + + 'F6S1E1lncGrjyOFvBNU2f/hPMAKNr1cMGEbI/L06IjJbgSD39sqRCNRvojHs6j6mM02UdFM0ByVYQDlmworSSb7W86eanyH1aMy0g6X+li3QhXUbV+ExWv7QAj3lL9GOSw5bXyDmrd8aMy3pbrGrTKPOEPV7ZcYEEI97qNYsPNerB6OhEHPY4WsNrRKRvtVs8vNmQzUywJcuVXcmss7g1AAAAAAAAAAAywKkdt6bUCnk4y/Ui556wnNLZe4shPdeblOGvM1+EK8BtPyE58vKP8/oc1xlkF/VNhO/2g/0wuYRO4csMef26C/hi6JVBSrr6XS3LrxIoeQKvFZBuJ2Xm7RqpeYiArZuROwmsMS7/4emkDtbJ6UDx39oAZD8meZHl6hKOqcajZzdEu3hYDfqfMVUJR3dDchOiMVMfZVr4xNNkWlgSGYrXbCAcsyZCbmStd5ZYsXJfFGBuAOtGbY3ybL1l9lKgjDsCwiqxV9WXaTxMn/SAXKD1q2YkZ54815jarlRlnZ1H1Mk6SFnClN3T7n9PRwV1G1IkvZhlPvaSF9aNdxzEQFbN97T9HBUd6k9wAoOs4HNDY27iNgJxl/kNhYQSZe+rLpV' + + 'IbcKyVaTsoxZ9MXiJUEYdtXbXrULIfSZVdehnPVcCW+pcka0w/hRn4VS1IeivTg1VGNdGBKXw1Ajwu/chRg78p9h+W7MDJN5U0iTo53cj+1e3wtZqgpUy6wsbRqfOJRc1667oNiqfecqv6AMCcXvKNhMxk889y+/IAP2TbFYeLOnJMffwG7J+AafMj9ogIaCzClqzVHQHJQFXiuuXMDFw2Jw4sIdYwG2O4QnIDgiGcDS8JAOhGq4JFL8byd6F0XSxpU8jOlNiw/gCfj+MJV1PmVbLHmSKE0LmEo31UNH38Tqta6/iAjipZo/0sCQzFa6nKDg//hM0DhMJZXkr63hYt9nCPSzvGMCv2IPI31U68qTQp0QHBGCYAl9T9CM3dTajC+bVy5g7O9winx/GMS0Hzow26Tf6dP/QAbxmn+w8Htfa/fdTcGe9B9tBkcycW6P+fvMhmpknTMwjI3lZ3REZIlxsPlyoCks1hpHJD9ht9jv64UR1MgnZpYctr5A0UejqrNfJfe4Et52FU5AcEQynVE9drZOVwaT80eax9L5Cqibiy5EdwechSl+uZ09haxpfjfmLfx9QMN3byWk7pOeW+BFyFDdj7Wt' + + 'hu1bpxH/GVLpHQvZz2FrNTfgqyVuQI/7lgf2wDECWnoLAvXhFtI8nfPYSGv7UGUMYhz/J8QIdfV9QMtx+l/TSm2qZhbaopBin181SSPshOLshHw9xQfDswJaNmgEPOIFqL+ebE2sCxn6gIvi6b67lLW5nFJ3x0+jeNm8lfA5e8zjMuUM260mJMdPzhKTMnl+Fyns6y6nCavC1rn2mVTR+F2JjL+6uFUahZp2+xfditsb6FiGNi9/tfZBP4/xNs2K0xEPpbu341wKL+7VFMxNEegwEO3Nfxq5oedd5V9C1YHu3kpVwTshtvL1U1/5ThSADMG0bRiIdh684V/bZSmROy0l6JdacYHCcYF/HOLXpVQuUsXLXFMSS/n3pr7vnCgdnnIufSHy9W7OFw2bgdyn5g6bggUctJQbHnEvYjxJ1zMh5Fz6Qvn33MuOen+Lug9gjpiDGgEPtkZHTM8NjolbI6mShVhPsnqVjMK1cgUzVENC1bjphO/zpQEtGzQCHnGMV6Ziaq50GAv/GfwG49gTEjW6nU1qfG3+ydRMF4+G7WVQZSPmoC5SiAN3LVwGIpOJiwH0/gtpHsD42r2K7YJZkUxOOuyYW2e+' + + 'sQ3wgn+/lqlqaSea1Pja4eeGidzT1f8ugS4aKx+lU9H7rZDW66DKGBrFQ7I0MQ45FgT33yy5eCemJBxpURifAnU1E8zqr3xeZPKln8hMTvokfSseSJ9fWttk1xirR0xIefSnofInCkAVc9qDKpvrrjSXhnloYhxyUUg40qIwIwTwr2U3/XL2hR0GAj46a0S6Z4WIw85u3XNmqJP3zHCs/9TSTim17anfOFYyFHDqamwHw0GMDlpKgyvLsi9WNbrNBLRs0Ah42QoG7lq4DEQ7DzshH0h2yPnlCVjDiRLu3pjRSznNv4sBWTl7KSBy9Bvgh8BAkxPhaN6tJumIR8qjn04UDIScZ4W71f9VHbfz2FOgykbRXVykDc1gIMeH/jRvhLdtzxXD+1fe/aD8oSHkzkuNe2CWAS09msZCrSmKLGQIddi9EPCvFLNXxup7g3SsTWMh2JpFFjLtqWcJxxmyP/dsJLvzKLwGxmLVJpEsCPI84l7EeJKzZrl4KD9vTzm9wIyPnp1oM/1PORewnnn0N1k94G+ywIwQ1oh4QbHRS9oZsm7uMhOdsLSUh2Z12T4vglk3dxmHwFiQ6ax4PUZhdfGCfgP/bIcJ' + + 'lF3AqDU+uH9FFvllirW5Jj+Vc5h+sCDvuFUzC21RSDEq5qkbVCvLQWMx5BPGFgR5QI+OgYDTEaDv81FhwyVQOtBmIvm9lXDViHbZog1LjUmlUzE1VzoMi+Fo02TfkcQh9BsJ5/UKL48SsJsPJMGhLdpJzCypWT3EH1w0Vj5Xpr9U0U82qFaLgq983+BD9kGa6momhclD+Lzl3L+01+kdK7J63d55nQUga0Q8rtbmq217rpHJ9hvoRT64aKx8rlFjEce2UyLjMqTSPBSRuamS0I+1mC4DEcfKcKxkKODJ1NiJW8KWD1X8xXZCPpDsje/Xb/BQft6ecmc9z0XweozC6kqgYFSUH1yxWBD7W7De/Zxe/qHjvJrGk27dS0rcgAPrdBgI+OixDdIUXsG3KIWaIii8n3NQFylEJwoGQk69zNOXKu30Mxwr9gWZd+QKZqiGJVAwKkqBLtbdio2gpwN3R8UV+HqXDpt7MCPqqWAaxXi346o6c/utpg+2mTEequWXAAAAAAAAAAAxDvGdYgS09CKTcaZE22RVDeyvWRqWB5JcpJeLuKYklhwrGQo4dTU2QaKVtYLNYCwyedzBZCYnfcGhlKqfdkJx' + + 'E52AOybf0KGuUcTUQegwFtgT+kStZd/BrAvyvEXU0hMjvmqSRsUV2UnXTQiSPc84nQUDISfQZucvf97/Xk1jx6R+KgFVJH0HmbFv8S+ov+1GYdQ5jJcqr9/Qu8ijP5VC3KeWlKUdBsuwIOu2faHnJboPBWNpbao05PGkgNX3bKfEOONOlRDq95OegSQ7ZPL8je+uRgctJc8sCPOjWG/wTtelY3WzzzpWIMlHzkDnhlBD+KPdhvGCKVaLeV6sammHgAMBHx27Il31NhLT9xReAxifddowDew8lXDbnDcgyfO7Ih5Xa3PbuHL2UkDk9TbdRDviUYiryKriH/442bNXqP1Dym7n5PEXyqNhS4mkfuz+NOcy4cZinoN0LEMbmbHUzzoWr4PC1mqq5agESZDpHCYnHXZMo71fkcS3TD9YEPl8bdBF+EGixn8a/Rn+YzFPyPlXI42YnOmnCQddUwbujlX8VAKqSPoOSPpWPJAjvrRl376rylI/dmyHfSLYvOHuzE0784XgReO+u2mzYRVzPhDqrWcg/UMots6xDnHl3Cq9zETvZzfgt1I/FY6kErCNmJx0xS22zmGb61mZK5Rd6Ios78oJd29M' + + 'o71rjVt+N4TrRz2xy12JMMP7osKbSqB0nCgYFSXOF2toMxHy0MQ45F/Tute+hLcf/G7RWuX6gJs2zbARbF7+dymRhEdSCVjIopBwuVlgRghTEg66pgzBAToMBHx01ohpaR4KxtLaSWhz20l05utHUXqDiv30BZnJWkrNM7TiH5lgRslPwDSX8OarkujRy46iM1TH9WY4VvHZPuFwr3uuTWFr0nvCKuZ8krOaEDl6g3CryLMwS46YkL+WcodjCwKyW2fWB7b8bhXQMcOXzlU/5ha6WwGwBrUlqJut5ilucMhqH1Jdd9NDW24QNXBXPfoLZg77Khf8lat2Mnqel2NL9kutnWRiRYv18YMMrtvD90jFyPVCZpEx/5UEShzcSLDLiSli3zz4uGawueII6TDBNaFPs/BhGnZ8jSYF8hwWATbWtxki/sxUnjcIlDilkH2LC12jjlgD1JxaW8yc6m88vO2uJG07c//l0rh+D94i7c5eVKuxyoGF7B3n+I/oBWG5rV4ahwE1oIwvKtvWZc7MdleAtaeC9YNYPtyKLu3kez/J2Vw1Br7nD4O+ER1sTgXupgO5CVk2dBAQPIG0gJ/eXSxptgJ9DHdK' + + 'OZCA19XIeVMJ1B4WSHQGtM3WOxgmUF5f+Z3C9JsCmOic0FQKlDy2f7yoS3+JHxfFcj0ds7eN8qZ4qm5x5ztPLhQz5pmgcWcNhPIb5FRiB4KY3zMntNIPL/BJ3OLTdp5c22xgGZZW63pkh0ayB4tHgzLNI1mNy63PHqSVW/DH2oXpoUNAG51Gtf2Spdm77CG4yBOMeQ4Ljhsu4AuabXulYvhXEriTt/H86yj+2AvqlJ1WSmXrikDqTGyZiOhHSigjRTWJixIdjy2r2MAyMazL9Loukcq5hny9eWC+Pe+OJjoMEal3YC/W8MtQ4a0WyTUn6uIulANf/YkoZtEvXeLOGv8bGEGrm/OQn5M53oz+DUOWRyfIxIoL91JFAsaqrlMcm5xe86wQtBNPovpJQqsypT8WWmLlURIrx0FI2nbm49eSSEDl5GSyp9NyrkPWl4TaIztyoQXhGoakigSRSUGmOLS2hSXJ3nhl3eq6rKbPgAIKl3PCULa9iMKE/7tevTOTi6DfRyyPak4q72y3TZUcMkJ5g3IqMY1Bc/fN/784m7IHTAr5OCwCbIpqDwskOgNab9rlPF+Ikx/Gi5iWflOKw0T/WccaqOY5' + + '4vzgzkOekimiDN4kedjNQBnon6LI69jp9Ea7z/OYJwxDs1M+IoTkVdgvDc2OlFBGUQZvErJs6CDnOVeva8VCbQgezlpAwW+gOxk9T8W/q3t/5mSI3xdNQg6YFO9wWATYgTeshXw518axczJE4YWoIWlcP4lvEfhn9s8GV+Pv9SQaq/J20Clj1S2jZk51uR5eAom9mBB30iiQwf199BNgjzxVN7b9k6kXqhIQfjkZouAGhtq1MJlreNqmsFWe44Juw04v91YIWodtU1ikT/9BN/xYdZWzWUisfKUJXMfV9n77FH9si3VKwL/rJquR3az5aJbvxWekkXPKmjHhHnxcM7vkQYaxMxWpDdt5O2iav+RwtKArp/ogjuR6OntzB/lRjOzVvhSjaCLu7Um5I7FE2Rdwi024s9wxYIghnydl/tOz+o/c8fJ6CZELLTH8pgmbD1LEo3jtbcxQzL9eutmBNGvVghF/ZipPlM6aUNT92d8rJbz7RSB1JmfEK2YfSfy/SSQg/HIyWd0DQ23UGMK7PB9uRRf4crORoIVjvGmvH2jUPqS67ruGtgHK0EwItWkUrJTKywmAyZhUw9hzmjc4ZCb+xcAtusrC' + + '3qnXeL4NOz4ED2ctIO65UOWw6jd7spBF8wqxNsu0JWBiAZwHNxIs++hrkwwTKC+hzBzrVC7lN0tTj9KKohs6CBthIjrYnArBNsJEdK0lFJ96I9Pp90ydBr4h9ueZaMXtz1+GgDYnjHf3BdYb61qcME0rR9FS3OCNX557/cI07Pgkd3hYPc0Y6oZ7pnxEFdWqTOGXnVppiZkAAAAAAAAAAOxk9CEzxpbxtXxVacFrEXHBx5JvRn+Ir2VNlv4PPi6XFfk21ajEDhm4pyxSqfGulalRfaoh2xncWNJxBPoY7pRZGKFI8q2HgFzdFina9lfEgnTBUWT7bPrR+xPbxuBW8n1v2RDPYJ9qtj84vdmpqk09n+f69SbAA3S7xwaHFJne32MHNLa4Uio60+0DzQrCb/reryCDwCPUwA1CI07K4buFOMuoXNdulsQCJQ5uJFjrR7w0EwJqXQWv16cfEUJypJeN94TMP2LjuW38HqFEx4Ehss85FZbIrjGOTo2VCRbzzpVWzD6S5WM4WlCb3X0QRzWBKaC156+j5vOH42NwK3ngdV1WU+lAAXvpA6X/+fQSErU8LJDoDHUzB/MVhX7E24+vuGoMYdMe' + + '2eXdgYYhOVJ3+KrSn9Yi4iW9qBQ1eHH+dXEXSo+h8MoTf+xgmF1lYTBEnsGdvH/npUDU3UH0zyzcIGrgrnrpFluRHNDi2lWosjBfkPlHEx00S/nsvVLGt10XxmXSQz7QGCJP7sBesf2eWemShEtkV5pWjr+kpd0Ho8YOaHFtpFR+LLTE16IkVoexdjBMoLy+QTrupjLzNn2ZFeNrvGdmO0DwPuo6Rl9pHC0ow+CwCK1OaCoFSh5bsQXFt2EoW9BE4b+NGltcKRXywGF6wwFMdLf16PHRHMNZY8tMSz+nRe+dGoRGnInfa+M2MIJLK/s91fR09uYO76L1jGuD+y1OGEZ25F8K3zQRIHgfdR0jobq9Ypszgap+0a4dd1MZ9xuw/tHIDaMumoRVCQg/koJRcCmsAWNVV6cOp8lpRVGDHQSOZWgmBNS6ChH2UfiIKrdJ133JbvZ5PYrvJ5n1KwQtzUju8LB6hzDJIvGi7Q1Uc5JhQvHTL9CXx0pnTShq8OLhgP18yXSMvtJxfnBnr09JmpOCkKns0duziOOykzRN0XInNBWMJQ+j1g'); //== + + // Variables + var sigma, N, h; + + // 64bit tools + function get8(x, i) { + return (x[i >> 2] >> ((i & 3) << 3)) & 0xff; + } + + // 512bit tools + function add512(x, y) { + var CF = 0, w0, w1; + for (var i = 0; i < 16; i++) { + w0 = (x[i] & 0xffff) + (y[i] & 0xffff) + (CF || 0); + w1 = (x[i] >>> 16) + (y[i] >>> 16) + (w0 >>> 16); + x[i] = (w0 & 0xffff) | (w1 << 16); + CF = (w1 >>> 16); + } + } + + function get512(d) { + return new Int32Array(d.buffer, d.byteOffset, 16); + } + + + function copy512(r, d) { + for (var i = 0; i < 16; i++) + r[i] = d[i]; + } + + function new512() { + return new Int32Array(16); + } + + // Core private algorithms + function xor512(x, y) { + for (var i = 0; i < 16; i++) + x[i] = x[i] ^ y[i]; + } + + + var r = new512(); + function XLPS(x, y) { + copy512(r, x); + xor512(r, y); + for (var i = 0; i < 8; i++) { + var z0, z1, k = get8(r, i) << 1; + z0 = Ax[k]; + z1 = Ax[k + 1]; + for (var j = 1; j < 8; j++) { + k = (j << 9) + (get8(r, (j << 3) + i) << 1); + z0 = z0 ^ Ax[k]; + z1 = z1 ^ Ax[k + 1]; + } + x[i << 1] = z0; + x[(i << 1) + 1] = z1; + } + } + + var data = new512(), Ki = new512(); + function g(h, N, m) + { + var i; + + copy512(data, h); + XLPS(data, N); + + /* Starting E() */ + copy512(Ki, data); + XLPS(data, m); + + for (i = 0; i < 11; i++) { + XLPS(Ki, C[i]); + XLPS(data, Ki); + } + + XLPS(Ki, C[11]); + xor512(data, Ki); + /* E() done */ + + xor512(h, data); + xor512(h, m); + } + + // Stages + function stage2(d) { + var m = get512(d); + g(h, N, m); + + add512(N, buffer512); + add512(sigma, m); + } + + function stage3(d) { + var n = d.length; + if (n > 63) + return; + + var b0 = new Int32Array(16); + b0[0] = n << 3; + + var b = new Uint8Array(64); + for (var i = 0; i < n; i++) + b[i] = d[i]; + b[n] = 0x01; + + var m = get512(b), m0 = get512(b0); + g(h, N, m); + + add512(N, m0); + add512(sigma, m); + + g(h, buffer0, N); + g(h, buffer0, sigma); + } + + return function (data) { + + // Cleanup + sigma = new512(); + N = new512(); + + // Initial vector + h = new512(); + for (var i = 0; i < 16; i++) + if (this.bitLength === 256) + h[i] = 0x01010101; + + // Make data + var d = new Uint8Array(buffer(data)); + + var n = d.length; + var r = n % 64, q = (n - r) / 64; + + for (var i = 0; i < q; i++) + stage2.call(this, new Uint8Array(d.buffer, i * 64, 64)); + + stage3.call(this, new Uint8Array(d.buffer, q * 64, r)); + + var digest; + if (this.bitLength === 256) { + digest = new Int32Array(8); + for (var i = 0; i < 8; i++) + digest[i] = h[8 + i]; + } else { + digest = new Int32Array(16); + for (var i = 0; i < 16; i++) + digest[i] = h[i]; + } + // Swap hash for SignalCom + if (this.procreator === 'SC' || this.procreator === 'VN') + return swap(digest.buffer); + else + return digest.buffer; + }; +} // +)(); + +/** + * Algorithm name GOST R 34.11-94

+ * + * http://tools.ietf.org/html/rfc5831 + * + * The digest method returns digest data in according to GOST R 34.11-94. + * @memberOf GostDigest + * @method digest + * @instance + * @param {(ArrayBuffer|TypedArray)} data Data + * @returns {ArrayBuffer} Digest of data + */ +var digest94 = (function () // +{ + var C, H, M, Sum; + + // (i + 1 + 4(k - 1)) = 8i + k i = 0-3, k = 1-8 + function P(d) { + var K = new Uint8Array(32); + + for (var k = 0; k < 8; k++) { + K[4 * k] = d[k]; + K[1 + 4 * k] = d[ 8 + k]; + K[2 + 4 * k] = d[16 + k]; + K[3 + 4 * k] = d[24 + k]; + } + + return K; + } + + //A (x) = (x0 ^ x1) || x3 || x2 || x1 + function A(d) + { + var a = new Uint8Array(8); + + for (var j = 0; j < 8; j++) + { + a[j] = (d[j] ^ d[j + 8]); + } + + arraycopy(d, 8, d, 0, 24); + arraycopy(a, 0, d, 24, 8); + + return d; + } + + // (in:) n16||..||n1 ==> (out:) n1^n2^n3^n4^n13^n16||n16||..||n2 + function fw(d) { + var wS = new Uint16Array(d.buffer, 0, 16); + var wS15 = wS[0] ^ wS[1] ^ wS[2] ^ wS[3] ^ wS[12] ^ wS[15]; + arraycopy(wS, 1, wS, 0, 15); + wS[15] = wS15; + } + + //Encrypt function, ECB mode + function encrypt(key, s, sOff, d, dOff) { + var t = new Uint8Array(8); + arraycopy(d, dOff, t, 0, 8); + var r = new Uint8Array(this.cipher.encrypt(key, t)); + arraycopy(r, 0, s, sOff, 8); + } + + // block processing + function process(d, dOff) { + var S = new Uint8Array(32), U = new Uint8Array(32), + V = new Uint8Array(32), W = new Uint8Array(32); + + arraycopy(d, dOff, M, 0, 32); + + //key step 1 + + // H = h3 || h2 || h1 || h0 + // S = s3 || s2 || s1 || s0 + arraycopy(H, 0, U, 0, 32); + arraycopy(M, 0, V, 0, 32); + for (var j = 0; j < 32; j++) + { + W[j] = (U[j] ^ V[j]); + } + // Encrypt GOST 28147-ECB + encrypt.call(this, P(W), S, 0, H, 0); // s0 = EK0 [h0] + + //keys step 2,3,4 + for (var i = 1; i < 4; i++) { + var tmpA = A(U); + for (var j = 0; j < 32; j++) { + U[j] = (tmpA[j] ^ C[i][j]); + } + V = A(A(V)); + for (var j = 0; j < 32; j++) { + W[j] = (U[j] ^ V[j]); + } + // Encrypt GOST 28147-ECB + encrypt.call(this, P(W), S, i * 8, H, i * 8); // si = EKi [hi] + } + + // x(M, H) = y61(H^y(M^y12(S))) + for (var n = 0; n < 12; n++) { + fw(S); + } + for (var n = 0; n < 32; n++) { + S[n] = (S[n] ^ M[n]); + } + + fw(S); + + for (var n = 0; n < 32; n++) { + S[n] = (H[n] ^ S[n]); + } + for (var n = 0; n < 61; n++) { + fw(S); + } + arraycopy(S, 0, H, 0, H.length); + } + + + // 256 bitsblock modul -> (Sum + a mod (2^256)) + function summing(d) + { + var carry = 0; + for (var i = 0; i < Sum.length; i++) + { + var sum = (Sum[i] & 0xff) + (d[i] & 0xff) + carry; + + Sum[i] = sum; + + carry = sum >>> 8; + } + } + + // reset the chaining variables to the IV values. + var C2 = new Uint8Array([ + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF + ]); + + return function (data) { + + // Reset buffers + H = new Uint8Array(32); + M = new Uint8Array(32); + Sum = new Uint8Array(32); + + // Reset IV value + C = new Array(4); + for (var i = 0; i < 4; i++) + C[i] = new Uint8Array(32); + arraycopy(C2, 0, C[2], 0, C2.length); + + // Make data + var d = new Uint8Array(buffer(data)); + + var n = d.length; + var r = n % 32, q = (n - r) / 32; + + // Proccess full blocks + for (var i = 0; i < q; i++) { + var b = new Uint8Array(d.buffer, i * 32, 32); + + summing.call(this, b); // calc sum M + process.call(this, b, 0); + } + + // load d the remadder with padding zero; + if (r > 0) { + var b = new Uint8Array(d.buffer, q * 32), + c = new Uint8Array(32); + arraycopy(b, 0, c, 0, r); + summing.call(this, c); // calc sum M + process.call(this, c, 0); + + } + + // get length into L (byteCount * 8 = bitCount) in little endian. + var L = new Uint8Array(32), n8 = n * 8, k = 0; + while (n8 > 0) { + L[k++] = n8 & 0xff; + n8 = Math.floor(n8 / 256); + } + process.call(this, L, 0); + process.call(this, Sum, 0); + + var h = H.buffer; + + // Swap hash for SignalCom + if (this.procreator === 'SC') + h = swap(h); + + return h; + }; + +} // +)(); + +/** + * Algorithm name SHA-1

+ * + * https://tools.ietf.org/html/rfc3174 + * + * The digest method returns digest data in according to SHA-1.
+ * + * @memberOf GostDigest + * @method digest + * @instance + * @param {(ArrayBuffer|TypedArray)} data Data + * @returns {ArrayBuffer} Digest of data + */ +var digestSHA1 = (function () // +{ + + // Create a buffer for each 80 word block. + var state, block = new Uint32Array(80); + + function common(a, e, w, k, f) { + return (f + e + w + k + ((a << 5) | (a >>> 27))) >>> 0; + } + + function f1(a, b, c, d, e, w) { + return common(a, e, w, 0x5A827999, d ^ (b & (c ^ d))); + } + + function f2(a, b, c, d, e, w) { + return common(a, e, w, 0x6ED9EBA1, b ^ c ^ d); + } + + function f3(a, b, c, d, e, w) { + return common(a, e, w, 0x8F1BBCDC, (b & c) | (d & (b | c))); + } + + function f4(a, b, c, d, e, w) { + return common(a, e, w, 0xCA62C1D6, b ^ c ^ d); + } + + function cycle(state, block) { + var a = state[0], + b = state[1], + c = state[2], + d = state[3], + e = state[4]; + + // Partially unroll loops so we don't have to shift variables. + var fn = f1; + for (var i = 0; i < 80; i += 5) { + if (i === 20) { + fn = f2; + } + else if (i === 40) { + fn = f3; + } + else if (i === 60) { + fn = f4; + } + e = fn(a, b, c, d, e, block[i]); + b = ((b << 30) | (b >>> 2)) >>> 0; + d = fn(e, a, b, c, d, block[i + 1]); + a = ((a << 30) | (a >>> 2)) >>> 0; + c = fn(d, e, a, b, c, block[i + 2]); + e = ((e << 30) | (e >>> 2)) >>> 0; + b = fn(c, d, e, a, b, block[i + 3]); + d = ((d << 30) | (d >>> 2)) >>> 0; + a = fn(b, c, d, e, a, block[i + 4]); + c = ((c << 30) | (c >>> 2)) >>> 0; + } + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + } + + // Swap bytes for 32bits word + function swap32(b) { + return ((b & 0xff) << 24) + | ((b & 0xff00) << 8) + | ((b >> 8) & 0xff00) + | ((b >> 24) & 0xff); + } + + // input is a Uint8Array bitstream of the data + return function (data) { + var d = new Uint8Array(buffer(data)), dlen = d.length; + + // Pad the input string length. + var len = dlen + 9; + if (len % 64) { + len += 64 - (len % 64); + } + + state = new Uint32Array(5); + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; + state[4] = 0xc3d2e1f0; + + for (var ofs = 0; ofs < len; ofs += 64) { + + // Copy input to block and write padding as needed + for (var i = 0; i < 64; i++) { + var b = 0, + o = ofs + i; + if (o < dlen) { + b = d[o]; + } + else if (o === dlen) { + b = 0x80; + } + else { + // Write original bit length as a 64bit big-endian integer to the end. + var x = len - o - 1; + if (x >= 0 && x < 4) { + b = (dlen << 3 >>> (x * 8)) & 0xff; + } + } + + // Interpret the input bytes as big-endian per the spec + if (i % 4 === 0) { + block[i >> 2] = b << 24; + } + else { + block[i >> 2] |= b << ((3 - (i % 4)) * 8); + } + } + + // Extend the block + for (var i = 16; i < 80; i++) { + var w = block[i - 3] ^ block[i - 8] ^ block[i - 14] ^ block[i - 16]; + block[i] = (w << 1) | (w >>> 31); + } + + cycle(state, block); + + } + + // Swap the bytes around since they are big endian internally + for (var i = 0; i < 5; i++) + state[i] = swap32(state[i]); + return state.buffer; + }; + +} // +)(); + +/** + * Algorithm name GOST R 34.11-HMAC

+ * + * HMAC with the specified hash function. + * @memberOf GostDigest + * @method sign + * @instance + * @param {ArrayBuffer} key The key for HMAC. + * @param {Hash} data Data + */ +function signHMAC(key, data) // +{ + // GOST R 34.11-94 - B=32b, L=32b + // GOST R 34.11-256 - B=64b, L=32b + // GOST R 34.11-512 - B=64b, L=64b + var b = (this.digest === digest94) ? 32 : 64, + l = this.bitLength / 8, + k = buffer(key), + d = buffer(data), k0; + if (k.byteLength === b) + k0 = new Uint8Array(k); + else { + var k0 = new Uint8Array(b); + if (k.byteLength > b) { + k0.set(new Uint8Array(this.digest(k))); + } else { + k0.set(new Uint8Array(k)); + } + } + var s0 = new Uint8Array(b + d.byteLength), + s1 = new Uint8Array(b + l); + for (var i = 0; i < b; i++) { + s0[i] = k0[i] ^ 0x36; + s1[i] = k0[i] ^ 0x5C; + } + s0.set(new Uint8Array(d), b); + s1.set(new Uint8Array(this.digest(s0)), b); + return this.digest(s1); +} // + +/** + * Algorithm name GOST R 34.11-HMAC

+ * + * Verify HMAC based on GOST R 34.11 hash + * + * @memberOf GostDigest + * @method verify + * @instance + * @param {(ArrayBuffer|TypedArray)} key Key which used for HMAC generation + * @param {(ArrayBuffer|TypedArray)} signature generated HMAC + * @param {(ArrayBuffer|TypedArray)} data Data + * @returns {boolean} HMAC verified = true + */ +function verifyHMAC(key, signature, data) // +{ + var hmac = new Uint8Array(this.sign(key, data)), + test = new Uint8Array(signature); + if (hmac.length !== test.length) + return false; + for (var i = 0, n = hmac.length; i < n; i++) + if (hmac[i] !== test[i]) + return false; + return true; +} // + + +/** + * Algorithm name GOST R 34.11-KDF

+ * + * Simple generate key 256/512 bit random seed for derivation algorithms + * + * @memberOf GostDigest + * @method generateKey + * @instance + * @returns {ArrayBuffer} Generated key + */ +function generateKey() // +{ + return getSeed(this.bitLength).buffer; +} // + +/** + * Algorithm name GOST R 34.11-PFXKDF

+ * + * Derive bits from password (PKCS12 mode) + *
    + *
  • algorithm.salt - random value, salt
  • + *
  • algorithm.iterations - number of iterations
  • + *
+ * @memberOf GostDigest + * @method deriveBits + * @instance + * @param {ArrayBuffer} baseKey - password after UTF-8 decoding + * @param {number} length output bit-length + * @returns {ArrayBuffer} result + */ +function deriveBitsPFXKDF(baseKey, length) // +{ + if (length % 8 > 0) + throw new DataError('Length must multiple of 8'); + var u = this.bitLength / 8, v = (this.digest === digest94) ? 32 : 64, + n = length / 8, r = this.iterations; + // 1. Construct a string, D (the "diversifier"), by concatenating v/8 + // copies of ID. + var ID = this.diversifier, D = new Uint8Array(v); + for (var i = 0; i < v; i++) + D[i] = ID; + // 2. Concatenate copies of the salt together to create a string S of + // length v(ceiling(s/v)) bits (the final copy of the salt may be + // truncated to create S). Note that if the salt is the empty + // string, then so is S. + var S0 = new Uint8Array(buffer(this.salt)), s = S0.length, + slen = v * Math.ceil(s / v), S = new Uint8Array(slen); + for (var i = 0; i < slen; i++) + S[i] = S0[i % s]; + // 3. Concatenate copies of the password together to create a string P + // of length v(ceiling(p/v)) bits (the final copy of the password + // may be truncated to create P). Note that if the password is the + // empty string, then so is P. + var P0 = new Uint8Array(buffer(baseKey)), p = P0.length, + plen = v * Math.ceil(p / v), P = new Uint8Array(plen); + for (var i = 0; i < plen; i++) + P[i] = P0[i % p]; + // 4. Set I=S||P to be the concatenation of S and P. + var I = new Uint8Array(slen + plen); + arraycopy(S, 0, I, 0, slen); + arraycopy(P, 0, I, slen, plen); + // 5. Set c=ceiling(n/u). + var c = Math.ceil(n / u); + // 6. For i=1, 2, ..., c, do the following: + var A = new Uint8Array(c * u); + for (var i = 0; i < c; i++) { + // A. Set A2=H^r(D||I). (i.e., the r-th hash of D||1, + // H(H(H(... H(D||I)))) + var H = new Uint8Array(v + slen + plen); + arraycopy(D, 0, H, 0, v); + arraycopy(I, 0, H, v, slen + plen); + for (var j = 0; j < r; j++) + H = new Uint8Array(this.digest(H)); + arraycopy(H, 0, A, i * u, u); + // B. Concatenate copies of Ai to create a string B of length v + // bits (the final copy of Ai may be truncated to create B). + var B = new Uint8Array(v); + for (var j = 0; j < v; j++) + B[j] = H[j % u]; + // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit + // blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by + // setting I_j=(I_j+B+1) mod 2^v for each j. + var k = (slen + plen) / v; + for (j = 0; j < k; j++) { + var cf = 1, w; + for (var l = v - 1; l >= 0; --l) { + w = I[v * j + l] + B[l] + cf; + cf = w >>> 8; + I[v * j + l] = w & 0xff; + } + } + } + // 7. Concatenate A_1, A_2, ..., A_c together to form a pseudorandom + // bit string, A. + // 8. Use the first n bits of A as the output of this entire process. + var R = new Uint8Array(n); + arraycopy(A, 0, R, 0, n); + return R.buffer; +} // + +/** + * Algorithm name GOST R 34.11-KDF

+ * + * Derive bits for KEK deversification in 34.10-2012 algorithm + * KDF(KEK, UKM, label) = HMAC256 (KEK, 0x01|label|0x00|UKM|0x01|0x00) + * Default label = 0x26|0xBD|0xB8|0x78 + * + * @memberOf GostDigest + * @method deriveBits + * @instance + * @param {(ArrayBuffer|TypedArray)} baseKey base key for deriviation + * @param {number} length output bit-length + * @returns {ArrayBuffer} result + */ +function deriveBitsKDF(baseKey, length) // +{ + if (length % 8 > 0) + throw new DataError('Length must be multiple of 8'); + var rlen = length / 8, label, context = new Uint8Array(buffer(this.context)), + blen = this.bitLength / 8, n = Math.ceil(rlen / blen); + if (this.label) + label = new Uint8Array(buffer(this.label)); + else + label = new Uint8Array([0x26, 0xBD, 0xB8, 0x78]); + var result = new Uint8Array(rlen); + for (var i = 0; i < n; i++) { + var data = new Uint8Array(label.length + context.length + 4); + data[0] = i + 1; + data.set(label, 1); + data[label.length + 1] = 0x00; + data.set(context, label.length + 2); + data[data.length - 2] = length >>> 8; + data[data.length - 1] = length & 0xff; + result.set(new Uint8Array(signHMAC.call(this, baseKey, data), 0, + i < n - 1 ? blen : rlen - i * blen), i * blen); + } + return result.buffer; +} // + +/** + * Algorithm name GOST R 34.11-PBKDF1

+ * + * Derive bits from password + *
    + *
  • algorithm.salt - random value, salt
  • + *
  • algorithm.iterations - number of iterations
  • + *
+ * @memberOf GostDigest + * @method deriveBits + * @instance + * @param {ArrayBuffer} baseKey - password after UTF-8 decoding + * @param {number} length output bit-length + * @returns {ArrayBuffer} result + */ +function deriveBitsPBKDF1(baseKey, length) // +{ + if (length < this.bitLength / 2 || length % 8 > 0) + throw new DataError('Length must be more than ' + this.bitLength / 2 + ' bits and multiple of 8'); + var hLen = this.bitLength / 8, dkLen = length / 8, + c = this.iterations, + P = new Uint8Array(buffer(baseKey)), + S = new Uint8Array(buffer(this.salt)), + slen = S.length, plen = P.length, + T = new Uint8Array(plen + slen), + DK = new Uint8Array(dkLen); + if (dkLen > hLen) + throw new DataError('Invalid parameters: Length value'); + arraycopy(P, 0, T, 0, plen); + arraycopy(S, 0, T, plen, slen); + for (var i = 0; i < c; i++) + T = new Uint8Array(this.digest(T)); + arraycopy(T, 0, DK, 0, dkLen); + return DK.buffer; +} // + +/** + * Algorithm name GOST R 34.11-PBKDF2

+ * + * Derive bits from password + *
    + *
  • algorithm.salt - random value, salt
  • + *
  • algorithm.iterations - number of iterations
  • + *
+ * @memberOf GostDigest + * @method deriveBits + * @instance + * @param {ArrayBuffer} baseKey - password after UTF-8 decoding + * @param {number} length output bit-length + * @returns {ArrayBuffer} result + */ +function deriveBitsPBKDF2(baseKey, length) // +{ + var diversifier = this.diversifier || 1; // For PKCS12 MAC required 3*length + length = length * diversifier; + if (length < this.bitLength / 2 || length % 8 > 0) + throw new DataError('Length must be more than ' + this.bitLength / 2 + ' bits and multiple of 8'); + var hLen = this.bitLength / 8, dkLen = length / 8, + c = this.iterations, + P = new Uint8Array(buffer(baseKey)), + S = new Uint8Array(buffer(this.salt)); + var slen = S.byteLength, + data = new Uint8Array(slen + 4); + arraycopy(S, 0, data, 0, slen); + + if (dkLen > (0xffffffff - 1) * 32) + throw new DataError('Invalid parameters: Length value'); + var n = Math.ceil(dkLen / hLen), + DK = new Uint8Array(dkLen); + for (var i = 1; i <= n; i++) { + data[slen] = i >>> 24 & 0xff; + data[slen + 1] = i >>> 16 & 0xff; + data[slen + 2] = i >>> 8 & 0xff; + data[slen + 3] = i & 0xff; + + var U = new Uint8Array(signHMAC.call(this, P, data)), Z = U; + for (var j = 1; j < c; j++) { + U = new Uint8Array(signHMAC.call(this, P, U)); + for (var k = 0; k < hLen; k++) + Z[k] = U[k] ^ Z[k]; + } + var ofs = (i - 1) * hLen; + arraycopy(Z, 0, DK, ofs, Math.min(hLen, dkLen - ofs)); + } + if (diversifier > 1) { + var rLen = dkLen / diversifier, R = new Uint8Array(rLen); + arraycopy(DK, dkLen - rLen, R, 0, rLen); + return R.buffer; + } else + return DK.buffer; +} // + +/** + * Algorithm name GOST R 34.11-CPKDF

+ * + * Derive bits from password. CryptoPro algorithm + *
    + *
  • algorithm.salt - random value, salt
  • + *
  • algorithm.iterations - number of iterations
  • + *
+ * @memberOf GostDigest + * @method deriveBits + * @instance + * @param {ArrayBuffer} baseKey - password after UTF-8 decoding + * @param {number} length output bit-length + * @returns {ArrayBuffer} result + */ +function deriveBitsCP(baseKey, length) { + if (length > this.bitLength || length % 8 > 0) + throw new DataError('Length can\'t be more than ' + this.bitLength + ' bits and multiple of 8'); + // GOST R 34.11-94 - B=32b, L=32b + // GOST R 34.11-256 - B=64b, L=32b + // GOST R 34.11-512 - B=64b, L=64b + var b = (this.digest === digest94) ? 32 : 64, + l = this.bitLength / 8, + p = baseKey && baseKey.byteLength > 0 ? new Uint8Array(buffer(baseKey)) : false, + plen = p ? p.length : 0, + iterations = this.iterations, + salt = new Uint8Array(buffer(this.salt)), + slen = salt.length, + d = new Uint8Array(slen + plen); + arraycopy(salt, 0, d, 0, slen); + if (p) + arraycopy(p, 0, d, slen, plen); + + var h = new Uint8Array(this.digest(d)), + k = new Uint8Array(b), + s0 = new Uint8Array(b), + s1 = new Uint8Array(b); + var c = 'DENEFH028.760246785.IUEFHWUIO.EF'; + for (var i = 0; i < c.length; i++) + k[i] = c.charCodeAt(i); + + d = new Uint8Array(2 * (b + l)); + for (var j = 0; j < iterations; j++) { + for (var i = 0; i < b; i++) { + s0[i] = k[i] ^ 0x36; + s1[i] = k[i] ^ 0x5C; + k[i] = 0; + } + arraycopy(s0, 0, d, 0, b); + arraycopy(h, 0, d, b, l); + arraycopy(s1, 0, d, b + l, b); + arraycopy(h, 0, d, b + l + b, l); + arraycopy(new Uint8Array(this.digest(d)), 0, k, 0, l); + } + for (var i = 0; i < l; i++) { + s0[i] = k[i] ^ 0x36; + s1[i] = k[i] ^ 0x5C; + k[i] = 0; + } + d = new Uint8Array(2 * l + slen + plen); + arraycopy(s0, 0, d, 0, l); + arraycopy(salt, 0, d, l, slen); + arraycopy(s1, 0, d, l + slen, l); + if (p) + arraycopy(p, 0, d, l + slen + l, plen); + h = this.digest(this.digest(d)); + if (length === this.bitLength) + return h; + else { + var rlen = length / 8, r = new Uint8Array(rlen); + arraycopy(h, 0, r, 0, rlen); + return r.buffer; + } +} + +/** + * Algorithm name GOST R 34.11-KDF or GOST R 34.11-PBKDF2 or other

+ * + * Derive key from derive bits subset + * + * @memberOf GostDigest + * @method deriveKey + * @instance + * @param {ArrayBuffer} baseKey + * @returns {ArrayBuffer} + */ +function deriveKey(baseKey) // +{ + return this.deriveBits(baseKey, this.keySize * 8); +} // + +/** + * GOST R 34.11 Algorithm

+ * + * References: {@link http://tools.ietf.org/html/rfc6986} and {@link http://tools.ietf.org/html/rfc5831}

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST R 34.11'
  • + *
  • version Algorithm version + *
      + *
    • 1994 old-style 256 bits digest based on GOST 28147-89
    • + *
    • 2012 256 ro 512 bits digest algorithm "Streebog" GOST R 34.11-2012 (default)
    • + *
    + *
  • + *
  • length Digest length + *
      + *
    • 256 256 bits digest
    • + *
    • 512 512 bits digest, valid only for algorithm "Streebog"
    • + *
    + *
  • + *
  • mode Algorithm mode + *
      + *
    • HASH simple digest mode (default)
    • + *
    • HMAC HMAC algorithm based on GOST R 34.11
    • + *
    • KDF Derive bits for KEK deversification
    • + *
    • PBKDF2 Password based key dirivation algorithms PBKDF2 (based on HMAC)
    • + *
    • PFXKDF Password based PFX key dirivation algorithms
    • + *
    • CPKDF CpyptoPro Password based key dirivation algorithms
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 28147-89. Used only if version = 1994
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Digest HASH mode (default)
  • + *
  • Sign/Verify HMAC modes parameters depends on version and length + *
      + *
    • version: 1994 HMAC parameters (B = 32, L = 32)
    • + *
    • version: 2012, length: 256 HMAC parameters (B = 64, L = 32)
    • + *
    • version: 2012, length: 512 HMAC parameters (B = 64, L = 64)
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey KDF mode + *
      + *
    • context {@link CryptoOperationData} Context of the key derivation
    • + *
    • label {@link CryptoOperationData} Label that identifies the purpose for the derived keying material
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey PBKDF2 mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    • diversifier Deversifier, ID=1 - key material for performing encryption or decryption, ID=3 - integrity key for MACing
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey PFXKDF mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    • diversifier Deversifier, ID=1 - key material for performing encryption or decryption, + * ID=2 - IV (Initial Value) for encryption or decryption, ID=3 - integrity key for MACing
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey CPKDF mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    + *
  • + *
+ * + * @class GostDigest + * @param {AlgorithmIdentifier} algorithm WebCryptoAPI algorithm identifier + */ +function GostDigest(algorithm) // +{ + + algorithm = algorithm || {}; + + this.name = (algorithm.name || 'GOST R 34.10') + '-' + ((algorithm.version || 2012) % 100) + + ((algorithm.version || 2012) > 1 ? '-' + (algorithm.length || 256) : '') + + (((algorithm.mode || 'HASH') !== 'HASH') ? '-' + algorithm.mode : '') + + (algorithm.procreator ? '/' + algorithm.procreator : '') + + (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : ''); + + // Algorithm procreator + this.procreator = algorithm.procreator; + + // Bit length + this.bitLength = algorithm.length || 256; + + switch (algorithm.version || 2012) { + case 1: // SHA-1 + this.digest = digestSHA1; + this.bitLength = 160; + break; + case 1994: + this.digest = digest94; + // Define chiper algorithm + this.sBox = (algorithm.sBox || (algorithm.procreator === 'SC' ? 'D-SC' : 'D-A')).toUpperCase(); + + //if (!GostCipher) + // GostCipher = root.GostCipher; + if (!GostCipher) + throw new NotSupportedError('Object GostCipher not found'); + + this.cipher = new GostCipher({ + name: 'GOST 28147', + block: 'ECB', + sBox: this.sBox, + procreator: this.procreator + }); + + break; + case 2012: + this.digest = digest2012; + break; + default: + throw new NotSupportedError('Algorithm version ' + algorithm.version + ' not supported'); + } + + // Key size + this.keySize = algorithm.keySize || (algorithm.version <= 2 ? this.bitLength / 8 : 32); + + switch (algorithm.mode || 'HASH') { + case 'HASH': + break; + case 'HMAC': + this.sign = signHMAC; + this.verify = verifyHMAC; + this.generateKey = generateKey; + break; + case 'KDF': + this.deriveKey = deriveKey; + this.deriveBits = deriveBitsKDF; + this.label = algorithm.label; + this.context = algorithm.context; + break; + case 'PBKDF2': + this.deriveKey = deriveKey; + this.deriveBits = deriveBitsPBKDF2; + this.generateKey = generateKey; + this.salt = algorithm.salt; + this.iterations = algorithm.iterations || 2000; + this.diversifier = algorithm.diversifier || 1; + break; + case 'PFXKDF': + this.deriveKey = deriveKey; + this.deriveBits = deriveBitsPFXKDF; + this.generateKey = generateKey; + this.salt = algorithm.salt; + this.iterations = algorithm.iterations || 2000; + this.diversifier = algorithm.diversifier || 1; + break; + case 'CPKDF': + this.deriveKey = deriveKey; + this.deriveBits = deriveBitsCP; + this.generateKey = generateKey; + this.salt = algorithm.salt; + this.iterations = algorithm.iterations || 2000; + break; + default: + throw new NotSupportedError('Algorithm mode ' + algorithm.mode + ' not supported'); + } +} // + +export default GostDigest; diff --git a/src/core/vendor/gost/gostEngine.mjs b/src/core/vendor/gost/gostEngine.mjs new file mode 100755 index 00000000..b54819c6 --- /dev/null +++ b/src/core/vendor/gost/gostEngine.mjs @@ -0,0 +1,451 @@ +/** + * @file GOST 34.10-2012 signature function with 1024/512 bits digest + * @version 1.76 + * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved. + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 HOLDER 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. + * + */ + +import GostRandom from './gostRandom.mjs'; +import GostCipher from './gostCipher.mjs'; +import GostDigest from './gostDigest.mjs'; +import GostSign from './gostSign.mjs'; + +/* + * Engine definition base on normalized algorithm identifier + * + */ // + +var root = {}; + +// Define engine +function defineEngine(method, algorithm) { + if (!algorithm) + throw new (root.SyntaxError || Error)('Algorithm not defined'); + + if (!algorithm.name) + throw new (root.SyntaxError || Error)('Algorithm name not defined'); + + var name = algorithm.name, mode = algorithm.mode; + if ((name === 'GOST 28147' || name === 'GOST R 34.12' || name === 'RC2') && (method === 'generateKey' || + (mode === 'MAC' && (method === 'sign' || method === 'verify')) || + ((mode === 'KW' || mode === 'MASK') && (method === 'wrapKey' || method === 'unwrapKey')) || + ((!mode || mode === 'ES') && (method === 'encrypt' || method === 'decrypt')))) { + return 'GostCipher'; + + } else if ((name === 'GOST R 34.11' || name === 'SHA') && (method === 'digest' || + (mode === 'HMAC' && (method === 'sign' || method === 'verify' || method === 'generateKey')) || + ((mode === 'KDF' || mode === 'PBKDF2' || mode === 'PFXKDF' || mode === 'CPKDF') && + (method === 'deriveKey' || method === 'deriveBits' || method === 'generateKey')))) { + return 'GostDigest'; + + } else if (name === 'GOST R 34.10' && (method === 'generateKey' || + ((!mode || mode === 'SIGN') && (method === 'sign' || method === 'verify')) || + (mode === 'MASK' && (method === 'wrapKey' || method === 'unwrapKey')) || + (mode === 'DH' && (method === 'deriveKey' || method === 'deriveBits')))) { + return 'GostSign'; + } else + throw new (root.NotSupportedError || Error)('Algorithm ' + name + '-' + mode + ' is not valid for ' + method); +} // + +/** + * Object implements dedicated Web Workers and provide a simple way to create + * and run GOST cryptographic algorithms in background thread. + * + * Object provide interface to GOST low-level cryptogric classes: + *
    + *
  • GostCipher - implementation of GOST 28147, GOST R 34.12, GOST R 34.13 Encryption algorithms. Reference {@link http://tools.ietf.org/html/rfc5830}
  • + *
  • GostDigest - implementation of GOST R 34.11 Hash Function algorithms. References {@link http://tools.ietf.org/html/rfc5831} and {@link http://tools.ietf.org/html/rfc6986}
  • + *
  • GostSign - implementation of GOST R 34.10 Digital Signature algorithms. References {@link http://tools.ietf.org/html/rfc5832} and {@link http://tools.ietf.org/html/rfc7091}
  • + *
+ * @namespace gostEngine + */ +var gostEngine = { + /** + * gostEngine.execute(algorithm, method, args) Entry point to execution + * all low-level GOST cryptographic methods + * + *
    + *
  • Determine the appropriate engine for a given execution method
  • + *
  • Create cipher object for determineted engine
  • + *
  • Execute method of cipher with given args
  • + *
+ * + * @memberOf gostEngine + * @param {AlgorithmIndentifier} algorithm Algorithm identifier + * @param {string} method Crypto method for execution + * @param {Array} args Method arguments (keys, data, additional parameters) + * @returns {(CryptoOperationData|Key|KeyPair|boolean)} Result of method execution + */ + execute: function (algorithm, method, args) // + { + // Define engine for GOST algorithms + var engine = defineEngine(method, algorithm); + // Create cipher + var cipher = this['get' + engine](algorithm); + // Execute method + return cipher[method].apply(cipher, args); + }, // + /** + * gostEngine.getGostCipher(algorithm) returns GOST 28147 / GOST R 34.12 cipher instance

+ * + * GOST 28147-89 / GOST R 34.12-15 Encryption Algorithm

+ * When keys and initialization vectors are converted to/from byte arrays, + * little-endian byte order is assumed.

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST 28147' or 'GOST R 34.12'
  • + *
  • version Algorithm version, number + *
      + *
    • 1989 Current version of standard
    • + *
    • 2015 New draft version of standard
    • + *
    + *
  • + *
  • length Block length + *
      + *
    • 64 64 bits length (default)
    • + *
    • 128 128 bits length (only for version 2015)
    • + *
    + *
  • + *
  • mode Algorithm mode, string + *
      + *
    • ES Encryption mode (default)
    • + *
    • MAC "imitovstavka" (MAC) mode
    • + *
    • KW Key wrapping mode
    • + *
    • MASK Key mask mode
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 28147-89, string. Used only if version = 1989
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Encript/Decrypt mode (ES) + *
      + *
    • block Block mode, string. Default ECB
    • + *
    • keyMeshing Key meshing mode, string. Default NO
    • + *
    • padding Padding mode, string. Default NO for CFB and CTR modes, or ZERO for others
    • + *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • + *
    + *
  • + *
  • Sign/Verify mode (MAC) + *
      + *
    • macLength Length of mac in bits (default - 32 bits)
    • + *
    • iv {@link CryptoOperationData} Initial vector with length of block. Default - zero block
    • + *
    + *
  • + *
  • Wrap/Unwrap key mode (KW) + *
      + *
    • keyWrapping Mode of keywrapping, string. Default NO - standard GOST key wrapping
    • + *
    • ukm {@link CryptoOperationData} User key material. Default - random generated value
    • + *
    + *
  • + *
  • Wrap/Unwrap key mode (MASK)
  • + *
+ * + * Supported paramters values: + * + *
    + *
  • Block modes (parameter 'block') + *
      + *
    • ECB "prostaya zamena" (ECB) mode (default)
    • + *
    • CFB "gammirovanie s obratnoj svyaziyu" (64-bit CFB) mode
    • + *
    • CTR "gammirovanie" (counter) mode
    • + *
    • CBC Cipher-Block-Chaining (CBC) mode
    • + *
    + *
  • + *
  • Key meshing modes (parameter 'keyMeshing') + *
      + *
    • NO No key wrapping (default)
    • + *
    • CP CryptoPor Key key meshing
    • + *
    + *
  • + *
  • Padding modes (parameter 'padding') + *
      + *
    • NO No padding only for CFB and CTR modes
    • + *
    • PKCS5 PKCS#5 padding mode
    • + *
    • ZERO Zero bits padding mode
    • + *
    • RANDOM Random bits padding mode
    • + *
    + *
  • + *
  • Wrapping key modes (parameter 'keyWrapping') + *
      + *
    • NO Ref. rfc4357 6.1 GOST 28147-89 Key wrapping
    • + *
    • CP CryptoPro Key wrapping mode
    • + *
    • SC SignalCom Key wrapping mode
    • + *
    + *
  • + *
+ * + * @memberOf gostEngine + * @param {AlgorithmIndentifier} algorithm Algorithm identifier + * @returns {GostCipher} Instance of GostCipher + */ + getGostCipher: function (algorithm) // + { + return new (GostCipher || (GostCipher = root.GostCipher))(algorithm); + }, // + /** + * gostEngine.getGostDigest(algorithm) returns GOST R 34.11 cipher instance

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST R 34.11'
  • + *
  • version Algorithm version + *
      + *
    • 1994 old-style 256 bits digest based on GOST 28147-89
    • + *
    • 2012 256 ro 512 bits digest algorithm "Streebog" GOST R 34.11-2012 (default)
    • + *
    + *
  • + *
  • length Digest length + *
      + *
    • 256 256 bits digest
    • + *
    • 512 512 bits digest, valid only for algorithm "Streebog"
    • + *
    + *
  • + *
  • mode Algorithm mode + *
      + *
    • HASH simple digest mode (default)
    • + *
    • HMAC HMAC algorithm based on GOST R 34.11
    • + *
    • KDF Derive bits for KEK deversification
    • + *
    • PBKDF2 Password based key dirivation algorithms PBKDF2 (based on HMAC)
    • + *
    • PFXKDF PFX key dirivation algorithms PFXKDF
    • + *
    • CPKDF CryptoPro Password based key dirivation algorithms
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 28147-89. Used only if version = 1994
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Digest HASH mode (default)
  • + *
  • Sign/Verify HMAC modes parameters depends on version and length + *
      + *
    • version: 1994 HMAC parameters (B = 32, L = 32)
    • + *
    • version: 2012, length: 256 HMAC parameters (B = 64, L = 32)
    • + *
    • version: 2012, length: 512 HMAC parameters (B = 64, L = 64)
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey KDF mode + *
      + *
    • context {@link CryptoOperationData} Context of the key derivation
    • + *
    • label {@link CryptoOperationData} Label that identifies the purpose for the derived keying material
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey PBKDF2 mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey PFXKDF mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    • diversifier Deversifier, ID=1 - key material for performing encryption or decryption, + * ID=2 - IV (Initial Value) for encryption or decryption, ID=3 - integrity key for MACing
    • + *
    + *
  • + *
  • DeriveBits/DeriveKey CPKDF mode + *
      + *
    • salt {@link CryptoOperationData} Random salt as input for HMAC algorithm
    • + *
    • iterations Iteration count. GOST recomended value 1000 (default) or 2000
    • + *
    + *
  • + *
+ * + * @memberOf gostEngine + * @param {AlgorithmIndentifier} algorithm Algorithm identifier + * @returns {GostDigest} Instance of GostDigest + */ + getGostDigest: function (algorithm) // + { + return new (GostDigest || (GostDigest = root.GostDigest))(algorithm); + }, // + /** + * gostEngine.getGostSign(algorithm) returns GOST R 34.10 cipher instance

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST R 34.10'
  • + *
  • version Algorithm version + *
      + *
    • 1994 - Old-style GOST R 34.10-94 ExpMod algorithm with GOST R 34.11-94 hash
    • + *
    • 2001 - GOST R 34.10-2001 Eliptic curve algorithm with old GOST R 34.11-94 hash
    • + *
    • 2012 - GOST R 34.10-2012 Eliptic curve algorithm with GOST R 34.11-12 hash, default mode
    • + *
    + *
  • + *
  • length Length of hash and signature. Key length == hash length for EC algorithms and 2 * hash length for ExpMod algorithm + *
      + *
    • GOST R 34.10-256 - 256 bits digest, default mode
    • + *
    • GOST R 34.10-512 - 512 bits digest only for GOST R 34.11-2012 hash
    • + *
    + *
  • + *
  • mode Algorithm mode + *
      + *
    • SIGN Digital signature mode (default)
    • + *
    • DH Diffie-Hellman key generation and key agreement mode
    • + *
    • MASK Key mask mode
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 34.11-94. Used only if version = 1994 or 2001
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Sign/Verify mode (SIGN)
  • + *
  • Wrap/Unwrap mode (MASK)
  • + *
  • DeriveKey/DeriveBits mode (DH) + *
      + *
    • {@link CryptoOperationData} ukm User key material. Default - random generated value
    • + *
    • {@link CryptoOperationData} public The peer's EC public key data
    • + *
    + *
  • + *
  • GenerateKey mode (SIGN and DH and MASK) version = 1994 + *
      + *
    • namedParam Paramset for key generation algorithm. If specified no additianal parameters required
    • + *
    + * Additional parameters, if namedParam not specified + *
      + *
    • modulusLength Bit length of p (512 or 1024 bits). Default = 1024
    • + *
    • p {@link CryptoOperationData} Modulus, prime number, 2^(t-1) + *
    • q {@link CryptoOperationData} Order of cyclic group, prime number, 2^254 + *
    • a {@link CryptoOperationData} Generator, integer, 1 + *
    + *
  • + *
  • GenerateKey mode (SIGN and DH and MASK) version = 2001 or 2012 + *
      + *
    • namedCurve Paramset for key generation algorithm. If specified no additianal parameters required
    • + *
    + * Additional EC parameters, if namedCurve not specified + *
      + *
    • p {@link CryptoOperationData} Prime number - elliptic curve modulus
    • + *
    • a {@link CryptoOperationData} Coefficients a of the elliptic curve E
    • + *
    • b {@link CryptoOperationData} Coefficients b of the elliptic curve E
    • + *
    • q {@link CryptoOperationData} Prime number - order of cyclic group
    • + *
    • x {@link CryptoOperationData} Base point p x-coordinate
    • + *
    • y {@link CryptoOperationData} Base point p y-coordinate
    • + *
    + *
  • + *
+ * + * @memberOf gostEngine + * @param {AlgorithmIndentifier} algorithm Algorithm identifier + * @returns {GostSign} Instance of GostSign + */ + getGostSign: function (algorithm) // + { + return new (GostSign || (GostSign = root.GostSign))(algorithm); + } // +}; + +/* + * Worker method execution + * + */ // + +// Worker for gostCripto method execution +if (root.importScripts) { + + /** + * Method called when {@link SubtleCrypto} calls its own postMessage() + * method with data parameter: algorithm, method and arg.
+ * Call method execute and postMessage() results to onmessage event handler + * in the main process.
+ * If error occurred onerror event handler executed in main process. + * + * @memberOf gostEngine + * @name onmessage + * @param {MessageEvent} event Message event with data {algorithm, method, args} + */ + root.onmessage = function (event) { + try { + postMessage({ + id: event.data.id, + result: gostEngine.execute(event.data.algorithm, + event.data.method, event.data.args)}); + } catch (e) { + postMessage({ + id: event.data.id, + error: e.message + }); + } + }; +} else { + + // Load dependens + var baseUrl = '', nameSuffix = ''; + // Try to define from DOM model + if (typeof document !== 'undefined') { + (function () { + var regs = /^(.*)gostCrypto(.*)\.js$/i; + var list = document.querySelectorAll('script'); + for (var i = 0, n = list.length; i < n; i++) { + var value = list[i].getAttribute('src'); + var test = regs.exec(value); + if (test) { + baseUrl = test[1]; + nameSuffix = test[2]; + } + } + })(); + } + + // Local importScripts procedure for include dependens + var importScripts = function () { + for (var i = 0, n = arguments.length; i < n; i++) { + var name = arguments[i].split('.'), + src = baseUrl + name[0] + nameSuffix + '.' + name[1]; + var el = document.querySelector('script[src="' + src + '"]'); + if (!el) { + el = document.createElement('script'); + el.setAttribute('src', src); + document.head.appendChild(el); + } + } + }; + + // Import engines + if (!GostRandom) + importScripts('gostRandom.js'); + if (!GostCipher) + importScripts('gostCipher.js'); + if (!GostDigest) + importScripts('gostDigest.js'); + if (!GostSign) + importScripts('gostSign.js'); +} //
+ +export default gostEngine; + diff --git a/src/core/vendor/gost/gostRandom.mjs b/src/core/vendor/gost/gostRandom.mjs new file mode 100644 index 00000000..f9a38380 --- /dev/null +++ b/src/core/vendor/gost/gostRandom.mjs @@ -0,0 +1,128 @@ +/** + * Implementation Web Crypto random generatore for GOST algorithms + * 1.76 + * 2014-2016, Rudolf Nickolaev. All rights reserved. + * + * Exported for CyberChef by mshwed [m@ttshwed.com] + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * 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 HOLDER 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. + * + */ +import crypto from 'crypto'; + + +/** + * The gostCrypto provide general purpose cryptographic functionality for + * GOST standards including a cryptographically strong pseudo-random number + * generator seeded with truly random values. + * + * @Class GostRandom + * + */ // + +var root = {}; +var rootCrypto = crypto; + +var TypeMismatchError = Error; +var QuotaExceededError = Error; + +// Initialize mouse and time counters for random generator +var randomRing = { + seed: new Uint8Array(1024), + getIndex: 0, + setIndex: 0, + set: function (x) { + if (this.setIndex >= 1024) + this.setIndex = 0; + this.seed[this.setIndex++] = x; + }, + get: function () { + if (this.getIndex >= 1024) + this.getIndex = 0; + return this.seed[this.getIndex++]; + } +}; + +if (typeof document !== 'undefined') { + try { + // Mouse move event to fill random array + document.addEventListener('mousemove', function (e) { + randomRing.set((Date.now() & 255) ^ + ((e.clientX || e.pageX) & 255) ^ + ((e.clientY || e.pageY) & 255)); + }, false); + } catch (e) { + } + + try { + // Keypress event to fill random array + document.addEventListener('keydown', function (e) { + randomRing.set((Date.now() & 255) ^ + (e.keyCode & 255)); + }, false); + } catch (e) { + } +} // + +function GostRandom() { +} + +/** + * The getRandomValues method generates cryptographically random values.

+ * + * Random generator based on JavaScript Web Crypto random genereator + * (if it is possible) or Math.random mixed with time and parameters of + * mouse and keyboard events + * + * @memberOf GostRandom + * @param {(ArrayBuffer|ArrayBufferView)} array Destination buffer for random data + */ +GostRandom.prototype.getRandomValues = function (array) // +{ + + if (!array.byteLength) + throw new TypeMismatchError('Array is not of an integer type (Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, or Uint32Array)'); + + if (array.byteLength > 65536) + throw new QuotaExceededError('Byte length of array can\'t be greate then 65536'); + + var u8 = new Uint8Array(array.buffer, array.byteOffset, array.byteLength); + if (rootCrypto && rootCrypto.getRandomValues) { + // Native window cryptographic interface + rootCrypto.getRandomValues(u8); + } else { + // Standard Javascript method + for (var i = 0, n = u8.length; i < n; i++) + u8[i] = Math.floor(256 * Math.random()) & 255; + } + + // Mix bio randomizator + for (var i = 0, n = u8.length; i < n; i++) + u8[i] = u8[i] ^ randomRing.get(); + return array; +}; // + +export default GostRandom; diff --git a/src/core/vendor/gost/gostSign.mjs b/src/core/vendor/gost/gostSign.mjs new file mode 100755 index 00000000..36a87f63 --- /dev/null +++ b/src/core/vendor/gost/gostSign.mjs @@ -0,0 +1,2023 @@ +/** + * @file GOST 34.10-2012 signature function with 1024/512 bits digest + * @version 1.76 + * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved. + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Used library JSBN http://www-cs-students.stanford.edu/~tjw/jsbn/ + * Copyright (c) 2003-2005 Tom Wu (tjw@cs.Stanford.EDU) + * + * 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 HOLDER 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. + * + */ + + import GostRandom from './gostRandom.mjs'; + import GostDigest from './gostDigest.mjs'; + + import crypto from 'crypto'; + + /* + * Predefined curves and params collection + * + * http://tools.ietf.org/html/rfc5832 + * http://tools.ietf.org/html/rfc7091 + * http://tools.ietf.org/html/rfc4357 + * + */ // + +var root = {}; +var rootCrypto = crypto; +var CryptoOperationData = ArrayBuffer; + +var OperationError = Error, + DataError = Error, + NotSupportedError = Error; + +// Predefined named curve collection +var ECGostParams = { + 'S-256-TEST': { + a: 7, + b: '0x5FBFF498AA938CE739B8E022FBAFEF40563F6E6A3472FC2A514C0CE9DAE23B7E', + p: '0x8000000000000000000000000000000000000000000000000000000000000431', + q: '0x8000000000000000000000000000000150FE8A1892976154C59CFC193ACCF5B3', + x: 2, + y: '0x8E2A8A0E65147D4BD6316030E16D19C85C97F0A9CA267122B96ABBCEA7E8FC8' + }, + 'S-256-A': { + a: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94', + b: 166, + p: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97', + q: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893', + x: 1, + y: '0x8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14' + }, + 'S-256-B': { + a: '0x8000000000000000000000000000000000000000000000000000000000000C96', + b: '0x3E1AF419A269A5F866A7D3C25C3DF80AE979259373FF2B182F49D4CE7E1BBC8B', + p: '0x8000000000000000000000000000000000000000000000000000000000000C99', + q: '0x800000000000000000000000000000015F700CFFF1A624E5E497161BCC8A198F', + x: 1, + y: '0x3FA8124359F96680B83D1C3EB2C070E5C545C9858D03ECFB744BF8D717717EFC' + }, + 'S-256-C': { + a: '0x9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D7598', + b: 32858, + p: '0x9B9F605F5A858107AB1EC85E6B41C8AACF846E86789051D37998F7B9022D759B', + q: '0x9B9F605F5A858107AB1EC85E6B41C8AA582CA3511EDDFB74F02F3A6598980BB9', + x: 0, + y: '0x41ECE55743711A8C3CBF3783CD08C0EE4D4DC440D4641A8F366E550DFDB3BB67' + }, + 'P-256': { + p: '0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF', + a: '0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC', + b: '0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B', + x: '0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296', + y: '0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5', + q: '0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551' + }, + 'T-512-TEST': { + a: 7, + b: '0x1CFF0806A31116DA29D8CFA54E57EB748BC5F377E49400FDD788B649ECA1AC4361834013B2AD7322480A89CA58E0CF74BC9E540C2ADD6897FAD0A3084F302ADC', + p: '0x4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DF1D852741AF4704A0458047E80E4546D35B8336FAC224DD81664BBF528BE6373', + q: '0x4531ACD1FE0023C7550D267B6B2FEE80922B14B2FFB90F04D4EB7C09B5D2D15DA82F2D7ECB1DBAC719905C5EECC423F1D86E25EDBE23C595D644AAF187E6E6DF', + x: '0x24D19CC64572EE30F396BF6EBBFD7A6C5213B3B3D7057CC825F91093A68CD762FD60611262CD838DC6B60AA7EEE804E28BC849977FAC33B4B530F1B120248A9A', + y: '0x2BB312A43BD2CE6E0D020613C857ACDDCFBF061E91E5F2C3F32447C259F39B2C83AB156D77F1496BF7EB3351E1EE4E43DC1A18B91B24640B6DBB92CB1ADD371E' + }, + 'T-512-A': { + p: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC7', + a: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC4', + b: '0xE8C2505DEDFC86DDC1BD0B2B6667F1DA34B82574761CB0E879BD081CFD0B6265EE3CB090F30D27614CB4574010DA90DD862EF9D4EBEE4761503190785A71C760', + q: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27E69532F48D89116FF22B8D4E0560609B4B38ABFAD2B85DCACDB1411F10B275', + x: 3, + y: '0x7503CFE87A836AE3A61B8816E25450E6CE5E1C93ACF1ABC1778064FDCBEFA921DF1626BE4FD036E93D75E6A50E3A41E98028FE5FC235F5B889A589CB5215F2A4' + }, + 'T-512-B': { + p: '0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006F', + a: '0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006C', + b: '0x687D1B459DC841457E3E06CF6F5E2517B97C7D614AF138BCBF85DC806C4B289F3E965D2DB1416D217F8B276FAD1AB69C50F78BEE1FA3106EFB8CCBC7C5140116', + q: '0x800000000000000000000000000000000000000000000000000000000000000149A1EC142565A545ACFDB77BD9D40CFA8B996712101BEA0EC6346C54374F25BD', + x: 2, + y: '0x1A8F7EDA389B094C2C071E3647A8940F3C123B697578C213BE6DD9E6C8EC7335DCB228FD1EDF4A39152CBCAAF8C0398828041055F94CEEEC7E21340780FE41BD' + } +}; +ECGostParams['X-256-A'] = ECGostParams['S-256-A']; +ECGostParams['X-256-B'] = ECGostParams['S-256-C']; +ECGostParams['T-256-TEST'] = ECGostParams['S-256-TEST']; +ECGostParams['T-256-A'] = ECGostParams['S-256-A']; +ECGostParams['T-256-B'] = ECGostParams['S-256-B']; +ECGostParams['T-256-C'] = ECGostParams['S-256-C']; + + +var GostParams = { + 'S-TEST': { + modulusLength: 512, // bit length of p (512 or 1024 bits) + p: '0xEE8172AE8996608FB69359B89EB82A69854510E2977A4D63BC97322CE5DC3386EA0A12B343E9190F23177539845839786BB0C345D165976EF2195EC9B1C379E3', + q: '0x98915E7EC8265EDFCDA31E88F24809DDB064BDC7285DD50D7289F0AC6F49DD2D', + a: '0x9e96031500c8774a869582d4afde2127afad2538b4b6270a6f7c8837b50d50f206755984a49e509304d648be2ab5aab18ebe2cd46ac3d8495b142aa6ce23e21c' + }, + 'S-A': { + modulusLength: 1024, + p: '0xB4E25EFB018E3C8B87505E2A67553C5EDC56C2914B7E4F89D23F03F03377E70A2903489DD60E78418D3D851EDB5317C4871E40B04228C3B7902963C4B7D85D52B9AA88F2AFDBEB28DA8869D6DF846A1D98924E925561BD69300B9DDD05D247B5922D967CBB02671881C57D10E5EF72D3E6DAD4223DC82AA1F7D0294651A480DF', + q: '0x972432A437178B30BD96195B773789AB2FFF15594B176DD175B63256EE5AF2CF', + a: '0x8FD36731237654BBE41F5F1F8453E71CA414FFC22C25D915309E5D2E62A2A26C7111F3FC79568DAFA028042FE1A52A0489805C0DE9A1A469C844C7CABBEE625C3078888C1D85EEA883F1AD5BC4E6776E8E1A0750912DF64F79956499F1E182475B0B60E2632ADCD8CF94E9C54FD1F3B109D81F00BF2AB8CB862ADF7D40B9369A' + }, + 'S-B': { + modulusLength: 1024, + p: '0xC6971FC57524B30C9018C5E621DE15499736854F56A6F8AEE65A7A404632B1BCF0349FFCAFCB0A103177971FC1612ADCDB8C8CC938C70225C8FD12AFF01B1D064E0AD6FDE6AB9159166CB9F2FC171D92F0CC7B6A6B2CD7FA342ACBE2C9315A42D576B1ECCE77A963157F3D0BD96A8EB0B0F3502AD238101B05116334F1E5B7AB', + q: '0xB09D634C10899CD7D4C3A7657403E05810B07C61A688BAB2C37F475E308B0607', + a: '0x3D26B467D94A3FFC9D71BF8DB8934084137264F3C2E9EB16DCA214B8BC7C872485336744934FD2EF5943F9ED0B745B90AA3EC8D70CDC91682478B664A2E1F8FB56CEF2972FEE7EDB084AF746419B854FAD02CC3E3646FF2E1A18DD4BEB3C44F7F2745588029649674546CC9187C207FB8F2CECE8E2293F68395C4704AF04BAB5' + }, + 'S-C': { + modulusLength: 1024, + p: '0x9D88E6D7FE3313BD2E745C7CDD2AB9EE4AF3C8899E847DE74A33783EA68BC30588BA1F738C6AAF8AB350531F1854C3837CC3C860FFD7E2E106C3F63B3D8A4C034CE73942A6C3D585B599CF695ED7A3C4A93B2B947B7157BB1A1C043AB41EC8566C6145E938A611906DE0D32E562494569D7E999A0DDA5C879BDD91FE124DF1E9', + q: '0xFADD197ABD19A1B4653EECF7ECA4D6A22B1F7F893B641F901641FBB555354FAF', + a: '0x7447ED7156310599070B12609947A5C8C8A8625CF1CF252B407B331F93D639DDD1BA392656DECA992DD035354329A1E95A6E32D6F47882D960B8F10ACAFF796D13CD9611F853DAB6D2623483E46788708493937A1A29442598AEC2E0742022563440FE9C18740ECE6765AC05FAF024A64B026E7E408840819E962E7E5F401AE3' + }, + 'S-D': { + modulusLength: 1024, + p: '0x80F102D32B0FD167D069C27A307ADAD2C466091904DBAA55D5B8CC7026F2F7A1919B890CB652C40E054E1E9306735B43D7B279EDDF9102001CD9E1A831FE8A163EED89AB07CF2ABE8242AC9DEDDDBF98D62CDDD1EA4F5F15D3A42A6677BDD293B24260C0F27C0F1D15948614D567B66FA902BAA11A69AE3BCEADBB83E399C9B5', + q: '0xF0F544C418AAC234F683F033511B65C21651A6078BDA2D69BB9F732867502149', + a: '0x6BCC0B4FADB3889C1E06ADD23CC09B8AB6ECDEDF73F04632595EE4250005D6AF5F5ADE44CB1E26E6263C672347CFA26F9E9393681E6B759733784CDE5DBD9A14A39369DFD99FA85CC0D10241C4010343F34A91393A706CF12677CBFA1F578D6B6CFBE8A1242CFCC94B3B653A476E145E3862C18CC3FED8257CFEF74CDB205BF1' + }, + 'X-A': { + modulusLength: 1024, + p: '0xCA3B3F2EEE9FD46317D49595A9E7518E6C63D8F4EB4D22D10D28AF0B8839F079F8289E603B03530784B9BB5A1E76859E4850C670C7B71C0DF84CA3E0D6C177FE9F78A9D8433230A883CD82A2B2B5C7A3306980278570CDB79BF01074A69C9623348824B0C53791D53C6A78CAB69E1CFB28368611A397F50F541E16DB348DBE5F', + q: '0xCAE4D85F80C147704B0CA48E85FB00A9057AA4ACC44668E17F1996D7152690D9', + a: '0xBE27D652F2F1E339DA734211B85B06AE4DE236AA8FBEEB3F1ADCC52CD43853777E834A6A518138678A8ADBD3A55C70A7EAB1BA7A0719548677AAF4E609FFB47F6B9D7E45B0D06D83D7ADC53310ABD85783E7317F7EC73268B6A9C08D260B85D8485696CA39C17B17F044D1E050489036ABD381C5E6BF82BA352A1AFF136601AF' + }, + 'X-B': { + modulusLength: 1024, + p: '0x9286DBDA91ECCFC3060AA5598318E2A639F5BA90A4CA656157B2673FB191CD0589EE05F4CEF1BD13508408271458C30851CE7A4EF534742BFB11F4743C8F787B11193BA304C0E6BCA25701BF88AF1CB9B8FD4711D89F88E32B37D95316541BF1E5DBB4989B3DF13659B88C0F97A3C1087B9F2D5317D557DCD4AFC6D0A754E279', + q: '0xC966E9B3B8B7CDD82FF0F83AF87036C38F42238EC50A876CD390E43D67B6013F', + a: '0x7E9C3096676F51E3B2F9884CF0AC2156779496F410E049CED7E53D8B7B5B366B1A6008E5196605A55E89C3190DABF80B9F1163C979FCD18328DAE5E9048811B370107BB7715F82091BB9DE0E33EE2FED6255474F8769FCE5EAFAEEF1CB5A32E0D5C6C2F0FC0B3447072947F5B4C387666993A333FC06568E534AD56D2338D729' + }, + 'X-C': { + modulusLength: 1024, + p: '0xB194036ACE14139D36D64295AE6C50FC4B7D65D8B340711366CA93F383653908EE637BE428051D86612670AD7B402C09B820FA77D9DA29C8111A8496DA6C261A53ED252E4D8A69A20376E6ADDB3BDCD331749A491A184B8FDA6D84C31CF05F9119B5ED35246EA4562D85928BA1136A8D0E5A7E5C764BA8902029A1336C631A1D', + q: '0x96120477DF0F3896628E6F4A88D83C93204C210FF262BCCB7DAE450355125259', + a: '0x3F1817052BAA7598FE3E4F4FC5C5F616E122CFF9EBD89EF81DC7CE8BF56CC64B43586C80F1C4F56DD5718FDD76300BE336784259CA25AADE5A483F64C02A20CF4A10F9C189C433DEFE31D263E6C9764660A731ECCAECB74C8279303731E8CF69205BC73E5A70BDF93E5BB681DAB4EEB9C733CAAB2F673C475E0ECA921D29782E' + } +}; // + +/* + * BigInteger arithmetic tools + * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js + * + */ // + +// Bits per one element +var DB = 28, DM = (1 << DB) - 1, DV = 1 << DB, + FV = Math.pow(2, 52), F1 = 52 - DB, F2 = 2 * DB - 52; + +function am(y, i, x, w, j, c, n) { + var xl = x & 0x3fff, xh = x >> 14; + while (--n >= 0) { + var l = y[i] & 0x3fff; + var h = y[i++] >> 14; + var m = xh * l + h * xl; + l = xl * l + ((m & 0x3fff) << 14) + w[j] + c; + c = (l >> 28) + (m >> 14) + xh * h; + w[j++] = l & 0xfffffff; + } + return c; +} + +function nbi(words) { + var r = new Array(Math.ceil(words)); + r.s = 0; + r.t = 0; + return r; +} + +function copyTo(x, r) { + for (var i = x.t - 1; i >= 0; --i) + r[i] = x[i]; + r.t = x.t; + r.s = x.s; + return r; +} + +function copy(x) { + return copyTo(x, nbi(x.t)); +} + +function setInt(x, i) { + x.t = 1; + x.s = (i < 0) ? -1 : 0; + if (i > 0) + x[0] = i; + else if (i < -1) + x[0] = i + DV; + else + x.t = 0; + return x; +} + +function nbv(i) { + var r = nbi(1); + setInt(r, i); + return r; +} + +var ZERO = nbv(0), ONE = nbv(1), THREE = nbv(3); + +function clamp(x) { + var c = x.s & DM; + while (x.t > 0 && x[x.t - 1] === c) + --x.t; + return x; +} + +function subTo(x, a, r) { + var i = 0, c = 0, m = Math.min(a.t, x.t); + while (i < m) { + c += x[i] - a[i]; + r[i++] = c & DM; + c >>= DB; + } + if (a.t < x.t) { + c -= a.s; + while (i < x.t) { + c += x[i]; + r[i++] = c & DM; + c >>= DB; + } + c += x.s; + } + else { + c += x.s; + while (i < a.t) { + c -= a[i]; + r[i++] = c & DM; + c >>= DB; + } + c -= a.s; + } + r.s = (c < 0) ? -1 : 0; + if (c < -1) + r[i++] = DV + c; + else if (c > 0) + r[i++] = c; + r.t = i; + return clamp(r); +} + +function sub(x, y) { + return subTo(x, y, nbi(x.t)); +} + +function addTo(x, a, r) { + var i = 0, c = 0, m = Math.min(a.t, x.t); + while (i < m) { + c += x[i] + a[i]; + r[i++] = c & DM; + c >>= DB; + } + if (a.t < x.t) { + c += a.s; + while (i < x.t) { + c += x[i]; + r[i++] = c & DM; + c >>= DB; + } + c += x.s; + } + else { + c += x.s; + while (i < a.t) { + c += a[i]; + r[i++] = c & DM; + c = c >> DB; + } + c += a.s; + } + r.s = (c < 0) ? -1 : 0; + if (c > 0) + r[i++] = c; + else if (c < -1) + r[i++] = DV + c; + r.t = i; + return clamp(r); +} + +function add(x, y) { + return addTo(x, y, nbi(x.t)); +} + +function negTo(x, r) { + return subTo(ZERO, x, r); +} + +function neg(x) { + return negTo(x, nbi(x.t)); +} + +function absTo(x, r) { + return (x.s < 0) ? negTo(r) : copyTo(r); +} + +function abs(x) { + return (x.s < 0) ? neg(x) : x; +} + +function compare(x, a) { + var r = x.s - a.s; + if (r !== 0) + return r; + var i = x.t; + r = i - a.t; + if (r !== 0) + return (x.s < 0) ? -r : r; + while (--i >= 0) + if ((r = x[i] - a[i]) !== 0) + return r; + return 0; +} + +function equals(x, y) { + return(compare(x, y) === 0); +} + +function min(x, y) { + return(compare(x, y) < 0) ? x : y; +} + +function max(x, y) { + return(compare(x, y) > 0) ? x : y; +} + +function nbits(x) { + var r = 1, t; + if ((t = x >>> 16) !== 0) { + x = t; + r += 16; + } + if ((t = x >> 8) !== 0) { + x = t; + r += 8; + } + if ((t = x >> 4) !== 0) { + x = t; + r += 4; + } + if ((t = x >> 2) !== 0) { + x = t; + r += 2; + } + if ((t = x >> 1) !== 0) { + x = t; + r += 1; + } + return r; +} + +function dshlTo(x, n, r) { + var i; + for (i = x.t - 1; i >= 0; --i) + r[i + n] = x[i]; + for (i = n - 1; i >= 0; --i) + r[i] = 0; + r.t = x.t + n; + r.s = x.s; + return r; +} +function dshrTo(x, n, r) { + for (var i = n; i < x.t; ++i) + r[i - n] = x[i]; + r.t = Math.max(x.t - n, 0); + r.s = x.s; + return r; +} + +function shlTo(x, n, r) { + var bs = n % DB; + var cbs = DB - bs; + var bm = (1 << cbs) - 1; + var ds = Math.floor(n / DB), c = (x.s << bs) & DM, i; + for (i = x.t - 1; i >= 0; --i) { + r[i + ds + 1] = (x[i] >> cbs) | c; + c = (x[i] & bm) << bs; + } + for (i = ds - 1; i >= 0; --i) + r[i] = 0; + r[ds] = c; + r.t = x.t + ds + 1; + r.s = x.s; + return clamp(r); +} + +function shrTo(x, n, r) { + r.s = x.s; + var ds = Math.floor(n / DB); + if (ds >= x.t) { + r.t = 0; + return; + } + var bs = n % DB; + var cbs = DB - bs; + var bm = (1 << bs) - 1; + r[0] = x[ds] >> bs; + for (var i = ds + 1; i < x.t; ++i) { + r[i - ds - 1] |= (x[i] & bm) << cbs; + r[i - ds] = x[i] >> bs; + } + if (bs > 0) + r[x.t - ds - 1] |= (x.s & bm) << cbs; + r.t = x.t - ds; + return clamp(r); +} + +function shl(x, n) { + var r = nbi(x.t); + if (n < 0) + shrTo(x, -n, r); + else + shlTo(x, n, r); + return r; +} + +function shr(x, n) { + var r = nbi(x.t); + if (n < 0) + shlTo(x, -n, r); + else + shrTo(x, n, r); + return r; +} + +function bitLength(x) { + if (x.t <= 0) + return 0; + return DB * (x.t - 1) + nbits(x[x.t - 1] ^ (x.s & DM)); +} + +function mulTo(b, a, r) { + var x = abs(b), y = abs(a); + var i = x.t; + r.t = i + y.t; + while (--i >= 0) + r[i] = 0; + for (i = 0; i < y.t; ++i) + r[i + x.t] = am(x, 0, y[i], r, i, 0, x.t); + r.s = 0; + if (b.s !== a.s) + subTo(ZERO, r, r); + return clamp(r); +} + +function mul(x, y) { + return mulTo(x, y, nbi(x.t + y.t)); +} + +function sqrTo(a, r) { + var x = abs(a); + var i = r.t = 2 * x.t; + while (--i >= 0) + r[i] = 0; + for (i = 0; i < x.t - 1; ++i) { + var c = am(x, i, x[i], r, 2 * i, 0, 1); + if ((r[i + x.t] += am(x, i + 1, 2 * x[i], r, 2 * i + 1, c, x.t - i - 1)) >= x.DV) { + r[i + x.t] -= x.DV; + r[i + x.t + 1] = 1; + } + } + if (r.t > 0) + r[r.t - 1] += am(x, i, x[i], r, 2 * i, 0, 1); + r.s = 0; + return clamp(r); +} + +function sqr(a) { + return sqrTo(a, nbi(a.t * 2)); +} + +function divRemTo(n, m, q, r) { + var pm = abs(m); + if (pm.t <= 0) + throw new OperationError('Division by zero'); + var pt = abs(n); + if (pt.t < pm.t) { + if (q) + setInt(q, 0); + if (r) + copyTo(n, r); + return q; + } + if (!r) + r = nbi(m.t); + var y = nbi(m.t), ts = n.s, ms = m.s; + var nsh = DB - nbits(pm[pm.t - 1]); + if (nsh > 0) { + shlTo(pm, nsh, y); + shlTo(pt, nsh, r); + } + else { + copyTo(pm, y); + copyTo(pt, r); + } + var ys = y.t; + var y0 = y[ys - 1]; + if (y0 === 0) + return q; + var yt = y0 * (1 << F1) + ((ys > 1) ? y[ys - 2] >> F2 : 0); + var d1 = FV / yt, d2 = (1 << F1) / yt, e = 1 << F2; + var i = r.t, j = i - ys, t = !q ? nbi(Math.max(n.t - m.t, 1)) : q; + dshlTo(y, j, t); + if (compare(r, t) >= 0) { + r[r.t++] = 1; + subTo(r, t, r); + } + dshlTo(ONE, ys, t); + subTo(t, y, y); + while (y.t < ys) + y[y.t++] = 0; + while (--j >= 0) { + var qd = (r[--i] === y0) ? DM : Math.floor(r[i] * d1 + (r[i - 1] + e) * d2); + if ((r[i] += am(y, 0, qd, r, j, 0, ys)) < qd) { + dshlTo(y, j, t); + subTo(r, t, r); + while (r[i] < --qd) + subTo(r, t, r); + } + } + if (q) { + dshrTo(r, ys, q); + if (ts !== ms) + subTo(ZERO, q, q); + } + r.t = ys; + clamp(r); + if (nsh > 0) + shrTo(r, nsh, r); + if (ts < 0) + subTo(ZERO, r, r); + return q; +} + +function modTo(b, a, r) { + divRemTo(abs(b), a, null, r); + if (b.s < 0 && compare(r, ZERO) > 0) + subTo(a, r, r); + return r; +} + +function mod(b, a) { + return modTo(b, a, nbi(a.t)); +} + +function div(b, a) { + return divRemTo(b, a, nbi(Math.max(b.t - a.t, 1)), null); +} + +function isEven(x) { + + return ((x.t > 0) ? (x[0] & 1) : x.s) === 0; +} + +function isZero(x) { + return equals(x, ZERO); +} + +function sig(x) { + if (x.s < 0) + return -1; + else if (x.t <= 0 || (x.t === 1 && x[0] <= 0)) + return 0; + else + return 1; +} + +function invMod(x, m) { + var ac = isEven(m); + if ((isEven(x) && ac) || sig(m) === 0) + return ZERO; + var u = copy(m), v = copy(x); + var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); + while (sig(u) !== 0) { + while (isEven(u)) { + shrTo(u, 1, u); + if (ac) { + if (!isEven(a) || !isEven(b)) { + addTo(a, x, a); + subTo(b, m, b); + } + shrTo(a, 1, a); + } + else if (!isEven(b)) + subTo(b, m, b); + shrTo(b, 1, b); + } + while (isEven(v)) { + shrTo(v, 1, v); + if (ac) { + if (!isEven(c) || !isEven(d)) { + addTo(c, x, c); + subTo(d, m, d); + } + shrTo(c, 1, c); + } + else if (!isEven(d)) + subTo(d, m, d); + shrTo(d, 1, d); + } + if (compare(u, v) >= 0) { + subTo(u, v, u); + if (ac) + subTo(a, c, a); + subTo(b, d, b); + } + else { + subTo(v, u, v); + if (ac) + subTo(c, a, c); + subTo(d, b, d); + } + } + if (compare(v, ONE) !== 0) + return ZERO; + if (compare(d, m) >= 0) + return subtract(d, m); + if (sig(d) < 0) + addTo(d, m, d); + else + return d; + if (sig(d) < 0) + return add(d, m); + else + return d; +} + +function testBit(x, n) { + var j = Math.floor(n / DB); + if (j >= x.t) + return (x.s !== 0); + return ((x[j] & (1 << (n % DB))) !== 0); +} + +function nothing(x) { + return x; +} + +function extend(c, o) { + for (var i in o) + c.prototype[i] = o[i]; +} // + +/* + * Classic, Barret, Mongomery reductions, optimized ExpMod algorithms + * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn2.js + * + */ // + +// Classic reduction +var Classic = function (m) { + this.m = m; +}; + +extend(Classic, { + convert: function (x) { + if (x.s < 0 || compare(x, this.m) >= 0) + return mod(x, this.m); + else + return x; + }, + revert: nothing, + reduce: function (x) { + modTo(x, this.m, x); + }, + sqrTo: function (x, r) { + sqrTo(x, r); + this.reduce(r); + }, + mulTo: function (x, y, r) { + mulTo(x, y, r); + this.reduce(r); + } +}); + +function invDig(a) { + if (a.t < 1) + return 0; + var x = a[0]; + if ((x & 1) === 0) + return 0; + var y = x & 3; + y = (y * (2 - (x & 0xf) * y)) & 0xf; + y = (y * (2 - (x & 0xff) * y)) & 0xff; + y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; + y = (y * (2 - x * y % DV)) % DV; + return (y > 0) ? DV - y : -y; +} + +// Montgomery reduction +var Montgomery = function (m) { + this.m = m; + this.mp = invDig(m); + this.mpl = this.mp & 0x7fff; + this.mph = this.mp >> 15; + this.um = (1 << (DB - 15)) - 1; + this.mt2 = 2 * m.t; +}; + +extend(Montgomery, { + // xR mod m + convert: function (x) { + var r = nbi(x.t); + dshlTo(abs(x), this.m.t, r); + divRemTo(r, this.m, null, r); + if (x.s < 0 && compare(r, ZERO) > 0) + subTo(this.m, r, r); + return r; + }, + // x/R mod m + revert: function (x) { + var r = nbi(x.t); + copyTo(x, r); + this.reduce(r); + return r; + }, + // x = x/R mod m (HAC 14.32) + reduce: function (x) { + while (x.t <= this.mt2) + x[x.t++] = 0; + for (var i = 0; i < this.m.t; ++i) { + var j = x[i] & 0x7fff; + var u0 = (j * this.mpl + (((j * this.mph + (x[i] >> 15) * this.mpl) & this.um) << 15)) & DM; + j = i + this.m.t; + x[j] += am(this.m, 0, u0, x, i, 0, this.m.t); + while (x[j] >= DV) { + x[j] -= DV; + x[++j]++; + } + } + clamp(x); + dshrTo(x, this.m.t, x); + if (compare(x, this.m) >= 0) + subTo(x, this.m, x); + }, + // r = "x^2/R mod m"; x != r + sqrTo: function (x, r) { + sqrTo(x, r); + this.reduce(r); + }, + // r = "xy/R mod m"; x,y != r + mulTo: function (x, y, r) { + mulTo(x, y, r); + this.reduce(r); + } +}); + +function dAddOffset(x, n, w) { + if (n === 0) + return; + while (x.t <= w) + x[x.t++] = 0; + x[w] += n; + while (x[w] >= DV) { + x[w] -= DV; + if (++w >= x.t) + x[x.t++] = 0; + ++x[w]; + } +} + +function mulLowerTo(x, a, n, r) { + var i = Math.min(x.t + a.t, n); + r.s = 0; // assumes a,x >= 0 + r.t = i; + while (i > 0) + r[--i] = 0; + var j; + for (j = r.t - x.t; i < j; ++i) + r[i + x.t] = am(x, 0, a[i], r, i, 0, x.t); + for (j = Math.min(a.t, n); i < j; ++i) + am(x, 0, a[i], r, i, 0, n - i); + return clamp(r); +} + +function mulUpperTo(x, a, n, r) { + --n; + var i = r.t = x.t + a.t - n; + r.s = 0; // assumes a,x >= 0 + while (--i >= 0) + r[i] = 0; + for (i = Math.max(n - x.t, 0); i < a.t; ++i) + r[x.t + i - n] = am(x, n - i, a[i], r, 0, 0, x.t + i - n); + clamp(r); + return dshrTo(r, 1, r); +} + +// Barrett modular reduction +function Barrett(m) { + // setup Barrett + this.r2 = nbi(2 * m.t); + this.q3 = nbi(2 * m.t); + dshlTo(ONE, 2 * m.t, this.r2); + this.mu = div(this.r2, m); + this.m = m; +} + +extend(Barrett, { + convert: function (x) { + if (x.s < 0 || x.t > 2 * this.m.t) + return mod(x, this.m); + else if (compare(x, this.m) < 0) + return x; + else { + var r = nbi(x.t); + copyTo(x, r); + this.reduce(r); + return r; + } + }, + revert: function (x) { + return x; + }, + // x = x mod m (HAC 14.42) + reduce: function (x) { + dshrTo(x, this.m.t - 1, this.r2); + if (x.t > this.m.t + 1) { + x.t = this.m.t + 1; + clamp(x); + } + mulUpperTo(this.mu, this.r2, this.m.t + 1, this.q3); + mulLowerTo(this.m, this.q3, this.m.t + 1, this.r2); + while (compare(x, this.r2) < 0) + dAddOffset(x, 1, this.m.t + 1); + subTo(x, this.r2, x); + while (compare(x, this.m) >= 0) + subTo(x, this.m, x); + }, + // r = x^2 mod m; x != r + sqrTo: function (x, r) { + sqrTo(x, r); + this.reduce(r); + }, + // r = x*y mod m; x,y != r + mulTo: function (x, y, r) { + mulTo(x, y, r); + this.reduce(r); + } + +}); + +// x^e % m (HAC 14.85) +function expMod(x, e, m) { + var i = bitLength(e), k, r = nbv(1), z; + if (i <= 0) + return r; + else if (i < 18) + k = 1; + else if (i < 48) + k = 3; + else if (i < 144) + k = 4; + else if (i < 768) + k = 5; + else + k = 6; + if (i < 8) + z = new Classic(m); + else if (isEven(m)) + z = new Barrett(m); + else + z = new Montgomery(m); + + // precomputation + var g = new Array(), n = 3, k1 = k - 1, km = (1 << k) - 1; + g[1] = z.convert(x); + if (k > 1) { + var g2 = nbi(m.t * 2); + z.sqrTo(g[1], g2); + while (n <= km) { + g[n] = nbi(m.t * 2); + z.mulTo(g2, g[n - 2], g[n]); + n += 2; + } + } + + var j = e.t - 1, w, is1 = true, r2 = nbi(m.t * 2), t; + i = nbits(e[j]) - 1; + while (j >= 0) { + if (i >= k1) + w = (e[j] >> (i - k1)) & km; + else { + w = (e[j] & ((1 << (i + 1)) - 1)) << (k1 - i); + if (j > 0) + w |= e[j - 1] >> (DB + i - k1); + } + + n = k; + while ((w & 1) == 0) { + w >>= 1; + --n; + } + if ((i -= n) < 0) { + i += DB; + --j; + } + if (is1) { // ret == 1, don't bother squaring or multiplying it + copyTo(g[w], r); + is1 = false; + } + else { + while (n > 1) { + z.sqrTo(r, r2); + z.sqrTo(r2, r); + n -= 2; + } + if (n > 0) + z.sqrTo(r, r2); + else { + t = r; + r = r2; + r2 = t; + } + z.mulTo(r2, g[w], r); + } + while (j >= 0 && (e[j] & (1 << i)) == 0) { + z.sqrTo(r, r2); + t = r; + r = r2; + r2 = t; + if (--i < 0) { + i = DB - 1; + --j; + } + } + } + return z.revert(r); +} // + +/* + * EC Field Elements, Points, Curves + * optimized release of http://www-cs-students.stanford.edu/~tjw/jsbn/ec.js + * + */ // + +// EC Field Elemets +function newFE(a, x) { + a.r.reduce(x); + x.q = a.q; + x.r = a.r; + return x; +} + +function copyFE(a, x) { + x.q = a.q; + x.r = a.r; + return x; +} + +function negFE(a) { + return copyFE(a, sub(a.q, a)); +} + +function addFE(a, b) { + var r = add(a, b); + if (compare(r, a.q) > 0) + subTo(r, a.q, r); + return copyFE(a, r); +} + +function subFE(a, b) { + var r = sub(a, b); + if (r.s < 0) + addTo(a.q, r, r); + return copyFE(a, r); +} + +function mulFE(a, b) { + return newFE(a, mul(a, b)); +} + +function sqrFE(a) { + return newFE(a, sqr(a)); +} + +function shlFE(a, i) { + return newFE(a, shl(a, i)); +} + +function invFE(a) { + return copyFE(a, invMod(a, a.q)); +} + +// EC Points +function newEC(curve, x, y, z) { + return { + curve: curve, + x: x, + y: y, + z: z || newFE(curve, ONE) + }; +} + +function getX(point) { + if (!point.zinv) + point.zinv = invFE(point.z); + return mulFE(point.x, point.zinv); +} + +function getY(point) { + if (!point.zinv) + point.zinv = invFE(point.z); + return mulFE(point.y, point.zinv); +} + +function isInfinity(a) { + if ((!a.x) && (!a.y)) + return true; + return isZero(a.z) && !isZero(a.y); +} + +function getInfinity(a) { + return a.curve.infinity; +} + +function equalsEC(a, b) { + if (a === b) + return true; + if (isInfinity(a)) + return isInfinity(b); + if (isInfinity(b)) + return isInfinity(a); + var u, v; + // u = Y2 * Z1 - Y1 * Z2 + u = subFE(mulFE(b.y, a.z), mulFE(a.y, b.z)); + if (!isZero(u)) + return false; + // v = X2 * Z1 - X1 * Z2 + v = subFE(mulFE(b.x, a.z), mulFE(a.x, b.z)); + return isZero(v); +} + +function negEC(a) { + return newEC(a.curve, a.x, negFE(a.y), a.z); +} + +function addEC(a, b) { + if (isInfinity(a)) + return b; + if (isInfinity(b)) + return a; + + // u = Y2 * Z1 - Y1 * Z2 + var u = subFE(mulFE(b.y, a.z), mulFE(a.y, b.z)); + // v = X2 * Z1 - X1 * Z2 + var v = subFE(mulFE(b.x, a.z), mulFE(a.x, b.z)); + + if (isZero(v)) { + if (isZero(u)) { + return twiceEC(a); // a == b, so double + } + return getInfinity(a); // a = -b, so infinity + } + + var x1 = a.x; + var y1 = a.y; + + var v2 = sqrFE(v); + var v3 = mulFE(v2, v); + var x1v2 = mulFE(x1, v2); + var zu2 = mulFE(sqrFE(u), a.z); + + // x3 = v * (z2 * (z1 * u^2 - 2 * x1 * v^2) - v^3) + var x3 = mulFE(subFE(mulFE(subFE(zu2, shlFE(x1v2, 1)), b.z), v3), v); + // y3 = z2 * (3 * x1 * u * v^2 - y1 * v^3 - z1 * u^3) + u * v^3 + var y3 = addFE(mulFE(subFE(subFE(mulFE(mulFE(x1v2, THREE), u), mulFE(y1, v3)), mulFE(zu2, u)), b.z), mulFE(u, v3)); + // z3 = v^3 * z1 * z2 + var z3 = mulFE(mulFE(v3, a.z), b.z); + + return newEC(a.curve, x3, y3, z3); +} + +function twiceEC(b) { + if (isInfinity(b)) + return b; + if (sig(b.y) === 0) + return getInfinity(b); + + var x1 = b.x; + var y1 = b.y; + + var y1z1 = mulFE(y1, b.z); + var y1sqz1 = mulFE(y1z1, y1); + var a = b.curve.a; + + // w = 3 * x1^2 + a * z1^2 + var w = mulFE(sqrFE(x1), THREE); + if (!isZero(a)) { + w = addFE(w, mulFE(sqrFE(b.z), a)); + } + + // x3 = 2 * y1 * z1 * (w^2 - 8 * x1 * y1^2 * z1) + var x3 = mulFE(shlFE(subFE(sqrFE(w), mulFE(shlFE(x1, 3), y1sqz1)), 1), y1z1); + // y3 = 4 * y1^2 * z1 * (3 * w * x1 - 2 * y1^2 * z1) - w^3 + var y3 = subFE(mulFE(shlFE(subFE(mulFE(mulFE(w, THREE), x1), shlFE(y1sqz1, 1)), 2), y1sqz1), mulFE(sqrFE(w), w)); + // z3 = 8 * (y1 * z1)^3 + var z3 = shlFE(mulFE(sqrFE(y1z1), y1z1), 3); + + return newEC(b.curve, x3, y3, z3); +} + +// Simple NAF (Non-Adjacent Form) multiplication algorithm +function mulEC(a, k) { + if (isInfinity(a)) + return a; + if (sig(k) === 0) + return getInfinity(a); + + var e = k; + var h = mul(e, THREE); + + var neg = negEC(a); + var R = a; + + var i; + for (i = bitLength(h) - 2; i > 0; --i) { + R = twiceEC(R); + + var hBit = testBit(h, i); + var eBit = testBit(e, i); + + if (hBit !== eBit) { + R = addEC(R, hBit ? a : neg); + } + } + + return R; +} + +function mul2AndAddEC(a, k) { + var nbits = bitLength(k); + var R = a, + Q = getInfinity(a); + + for (var i = 0; i < nbits - 1; i++) { + if (testBit(k, i) === 1) + Q = addEC(Q, R); + + R = twiceEC(R); + } + + if (testBit(k, nbits - 1) === 1) + Q = addEC(Q, R); + + return Q; +} + +// Compute a*j + x*k (simultaneous multiplication) +function mulTwoEC(a, j, x, k) { + var i; + if (bitLength(j) > bitLength(k)) + i = bitLength(j) - 1; + else + i = bitLength(k) - 1; + + var R = getInfinity(a); + var both = addEC(a, x); + while (i >= 0) { + R = twiceEC(R); + if (testBit(j, i)) { + if (testBit(k, i)) { + R = addEC(R, both); + } + else { + R = addEC(R, a); + } + } + else { + if (testBit(k, i)) { + R = addEC(R, x); + } + } + --i; + } + + return R; +} + +// EC Curve +function newCurve(q, a, b) { + var curve = {}; + curve.q = q; + curve.r = new Barrett(q); + curve.a = newFE(curve, a); + curve.b = newFE(curve, b); + curve.infinity = newEC(curve); + return curve; +} // + +/* + * Converion tools (hex, binary) + * + */ // + +function atobi(d) { + var k = 8; + var a = new Uint8Array(d); + var r = nbi(a.length * 8 / DB); + r.t = 0; + r.s = 0; + var sh = 0; + for (var i = 0, n = a.length; i < n; i++) { + var x = a[i]; + if (sh === 0) + r[r.t++] = x; + else if (sh + k > DB) { + r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh; + r[r.t++] = (x >> (DB - sh)); + } + else + r[r.t - 1] |= x << sh; + sh += k; + if (sh >= DB) + sh -= DB; + } + return clamp(r); +} + +function bitoa(s, bitLength) { + var k = 8; + var km = (1 << k) - 1, d, m = false, r = [], i = s.t; + var p = DB - (i * DB) % k; + if (i-- > 0) { + if (p < DB && (d = s[i] >> p) > 0) { + m = true; + r.push(d); + } + while (i >= 0) { + if (p < k) { + d = (s[i] & ((1 << p) - 1)) << (k - p); + d |= s[--i] >> (p += DB - k); + } + else { + d = (s[i] >> (p -= k)) & km; + if (p <= 0) { + p += DB; + --i; + } + } + if (d > 0) + m = true; + if (m) + r.push(d); + } + } + var r8 = new Uint8Array(bitLength ? bitLength / 8 : r.length); + if (m) + r8.set(r.reverse()); + return r8.buffer; +} + + +function htobi(s) { + if (typeof s === 'number' || s instanceof Number) + return nbv(s); + s = s.replace(/[^\-A-Fa-f0-9]/g, ''); + if (!s) + s = '0'; + var k = 4; + var r = nbi(s.length / 7); + var i = s.length, mi = false, sh = 0; + while (--i >= 0) { + var c = s.charAt(i); + if (c === '-') { + mi = true; + continue; + } + var x = parseInt(s.charAt(i), 16); + mi = false; + if (sh === 0) + r[r.t++] = x; + else if (sh + k > DB) { + r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh; + r[r.t++] = (x >> (DB - sh)); + } + else + r[r.t - 1] |= x << sh; + sh += k; + if (sh >= DB) + sh -= DB; + } + if (mi) + subTo(ZERO, r, r); + return clamp(r); +} + +function bitoh(x) { + if (x.s < 0) + return "-" + bitoh(negTo(x, nbi(x.t))); + var k = 4; + var km = (1 << k) - 1, d, m = false, r = "", i = x.t; + var p = DB - (i * DB) % k; + if (i-- > 0) { + if (p < DB && (d = x[i] >> p) > 0) { + m = true; + r = d.toString(16); + } + while (i >= 0) { + if (p < k) { + d = (x[i] & ((1 << p) - 1)) << (k - p); + d |= x[--i] >> (p += DB - k); + } + else { + d = (x[i] >> (p -= k)) & km; + if (p <= 0) { + p += DB; + --i; + } + } + if (d > 0) + m = true; + if (m) + r += d.toString(16); + } + } + return "0x" + (m ? r : "0"); +} + +// biginteger to big-endian integer bytearray +function bitoi(s) { + var i = s.t, r = []; + r[0] = s.s; + var p = DB - (i * DB) % 8, d, k = 0; + if (i-- > 0) { + if (p < DB && (d = s[i] >> p) !== (s.s & DM) >> p) + r[k++] = d | (s.s << (DB - p)); + while (i >= 0) { + if (p < 8) { + d = (s[i] & ((1 << p) - 1)) << (8 - p); + d |= s[--i] >> (p += DB - 8); + } + else { + d = (s[i] >> (p -= 8)) & 0xff; + if (p <= 0) { + p += DB; + --i; + } + } + if ((d & 0x80) !== 0) + d |= -256; + if (k === 0 && (s.s & 0x80) !== (d & 0x80)) + ++k; + if (k > 0 || d !== s.s) + r[k++] = d; + } + } + return new Uint8Array(r).buffer; +} + +// big-endian integer bytearray to biginteger +function itobi(d) { + var k = 8, s = new Uint8Array(d), + r = nbi(s.length / 7); + r.t = 0; + r.s = 0; + var i = s.length, sh = 0; + while (--i >= 0) { + var x = s[i] & 0xff; + if (sh === 0) + r[r.t++] = x; + else if (sh + k > DB) { + r[r.t - 1] |= (x & ((1 << (DB - sh)) - 1)) << sh; + r[r.t++] = (x >> (DB - sh)); + } + else + r[r.t - 1] |= x << sh; + sh += k; + if (sh >= DB) + sh -= DB; + } + if ((s[0] & 0x80) !== 0) { + r.s = -1; + if (sh > 0) + r[r.t - 1] |= ((1 << (DB - sh)) - 1) << sh; + } + return clamp(r); +} + + +// Swap bytes in buffer +function swap(s) { + var src = new Uint8Array(s), + dst = new Uint8Array(src.length); + for (var i = 0, n = src.length; i < n; i++) + dst[n - i - 1] = src[i]; + return dst.buffer; +} + +// Calculate hash of data +function hash(d) { + if (this.hash) + d = this.hash.digest(d); + // Swap hash for SignalCom + if (this.procreator === 'SC' || + (this.procreator === 'VN' && this.hash.version === 2012)) + d = swap(d); + return d; +} + +// Check buffer +function buffer(d) { + if (d instanceof CryptoOperationData) + return d; + else if (d && d?.buffer instanceof CryptoOperationData) + return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ? + d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer; + else + throw new DataError('CryptoOperationData or CryptoOperationDataView required'); +} + +// Check double buffer +function to2(d) { + var b = buffer(d); + if (b.byteLength % 2 > 0) + throw new DataError('Buffer length must be even'); + var n = b.byteLength / 2; + return [atobi(new Uint8Array(b, 0, n)), atobi(new Uint8Array(b, n, n))]; +} + +function from2(x, y, bitLength) { + var a = bitoa(x, bitLength), + b = bitoa(y, bitLength), + d = new Uint8Array(a.byteLength + b.byteLength); + d.set(new Uint8Array(a)); + d.set(new Uint8Array(b), a.byteLength); + return d.buffer; +} + +function getSeed(length) { + GostRandom = GostRandom || root.GostRandom; + var randomSource = GostRandom ? new (GostRandom || root.GostRandom) : rootCrypto; + if (randomSource.getRandomValues) { + var d = new Uint8Array(Math.ceil(length / 8)); + randomSource.getRandomValues(d); + return d; + } else + throw new NotSupportedError('Random generator not found'); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * The sign method returns sign data generated with the supplied privateKey.
+ * + * @memberOf GostSign + * @method sign + * @instance + * @param {(CryptoOperationData|TypedArray)} privateKey Private key + * @param {(CryptoOperationData|TypedArray)} data Data + * @returns {CryptoOperationData} Signature + */ +function sign(privateKey, data) // +{ + + // Stage 1 + var b = buffer(data); + var alpha = atobi(hash.call(this, b)); + + var q = this.q; + var x = mod(atobi(buffer(privateKey)), q); + + // Stage 2 + var e = mod(alpha, q); + if (isZero(e)) + e = ONE; + + var s = ZERO; + while (isZero(s)) { + var r = ZERO; + while (isZero(r)) { + + // Stage 3 + var k = mod(atobi(this.ukm || + getSeed(this.bitLength)), q); // pseudo random 0 < k < q + // Stage 4 + if (this.curve) { + // Gost R 34.10-2001 || Gost R 34.10-2012 + var P = this.P; + var C = mulEC(P, k); + r = mod(getX(C), q); + } else { + // Gost R 34.10-94 + var p = this.p, a = this.a; + r = mod(expMod(a, k, p), q); + } + } + // Stage 5 + s = mod(add(mul(r, x), mul(k, e)), q); + } + // Stage 6 + // console.log('s', bitoh(s)); + // console.log('r', bitoh(r)); + var zetta; + // Integer structure for SignalCom algorithm + if (this.procreator === 'SC') { + zetta = { + r: bitoh(r), + s: bitoh(s) + }; + } else { + zetta = from2(r, s, this.bitLength); + // Swap bytes for CryptoPro algorithm + if (this.procreator === 'CP' || this.procreator === 'VN') + zetta = swap(zetta); + } + return zetta; +} // + +/** + * Algorithm name GOST R 34.10

+ * + * The verify method returns signature verification for the supplied publicKey.
+ * + * @memberOf GostSign + * @method sign + * @instance + * @param {(CryptoOperationData|TypedArray)} publicKey Public key + * @param {(CryptoOperationData|TypedArray)} signature Signature + * @param {(CryptoOperationData|TypedArray)} data Data + * @returns {boolean} Signature verified = true + */ +function verify(publicKey, signature, data) // +{ + + // Stage 1 + var q = this.q; + var r, s; + // Ready int for SignalCom algorithm + if (this.procreator === 'SC') { + r = htobi(signature.r); + s = htobi(signature.s); + } else { + if (this.procreator === 'CP' || this.procreator === 'VN') + signature = swap(signature); + var zetta = to2(signature); + // Swap bytes for CryptoPro algorithm + s = zetta[1]; // first 32 octets contain the big-endian representation of s + r = zetta[0]; // and second 32 octets contain the big-endian representation of r + } + if (compare(r, q) >= 0 || compare(s, q) >= 0) + return false; + // Stage 2 + var b = buffer(data); + var alpha = atobi(hash.call(this, b)); + // Stage 3 + var e = mod(alpha, q); + if (isZero(e) === 0) + e = ONE; + // Stage 4 + var v = invMod(e, q); + // Stage 5 + var z1 = mod(mul(s, v), q); + var z2 = sub(q, mod(mul(r, v), q)); + // Stage 6 + if (this.curve) { + // Gost R 34.10-2001 || Gost R 34.10-2012 + var k2 = to2(publicKey), + curve = this.curve, + P = this.P, + x = newFE(curve, k2[0]), // first 32 octets contain the little-endian representation of x + y = newFE(curve, k2[1]), // and second 32 octets contain the little-endian representation of y. + Q = new newEC(curve, x, y); // This corresponds to the binary representation of (256||256) + var C = mulTwoEC(P, z1, Q, z2); + var R = mod(getX(C), q); + } else { + // Gost R 34.10-94 + var p = this.p, a = this.a; + var y = atobi(publicKey); + var R = mod(mod(mul(expMod(a, z1, p), expMod(y, z2, p)), p), q); + } + // Stage 7 + return (compare(R, r) === 0); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * The generateKey method returns a new generated key pair using the specified + * AlgorithmIdentifier. + * + * @memberOf GostSign + * @method generateKey + * @instance + * @returns {Object} Object with two CryptoOperationData members: privateKey and publicKey + */ +function generateKey() // +{ + var curve = this.curve; + if (curve) { + + var Q = curve.infinity; + while (isInfinity(Q)) { + + // Generate random private key + var d = ZERO; + if (this.ukm) { + d = atobi(this.ukm); + } else { + while (isZero(d)) + d = mod(atobi(getSeed(this.bitLength)), this.q); // 0 < d < q + } + + // Calculate public key + Q = mulEC(this.P, d); + var x = getX(Q), y = getY(Q); + // console.log('d', bitoh(d)); + // console.log('x', bitoh(x)); + // console.log('y', bitoh(y)); + } + + // Return result + return { + privateKey: bitoa(d, this.bitLength), + publicKey: from2(x, y, this.bitLength) // This corresponds to the binary representation of (256||256) + }; + + } else + throw new NotSupportedError('Key generation for GOST R 34.10-94 not supported'); +} // + +/** + * Algorithm name GOST R 34.10 mode MASK

+ * + * The generateMaskKey method returns a new generated key mask using for wrapping. + * + * @memberOf GostSign + * @method generateMaskKey + * @instance + * @returns {Object} Object with two CryptoOperationData members: privateKey and publicKey + */ +function generateMaskKey() // +{ + var curve = this.curve; + if (curve) { + // Generate random private key + var d = ZERO; + while (isZero(d)) + d = mod(atobi(getSeed(this.bitLength)), this.q); // 0 < d < q + + // Return result + return bitoa(d, this.bitLength); + } else + throw new NotSupportedError('Key generation for GOST R 34.10-94 not supported'); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * Unwrap private key from private key and ukm (mask) + * + * @memberOf GostSign + * @method unwrap + * @instance + * @param {(CryptoOperationData|TypedArray)} baseKey Unwrapping key + * @param {(CryptoOperationData|TypedArray)} data Wrapped key + * @returns {Object} CryptoOperationData unwrapped privateKey + */ +function unwrapKey(baseKey, data) // +{ + var curve = this.curve; + if (curve) { + var q = this.q; + var x = mod(atobi(buffer(data)), q); + var y = mod(atobi(buffer(baseKey)), q); + var z = this.procreator === 'VN' ? mod(mul(x, y), q) : mod(mul(x, invMod(y, q)), q); + return bitoa(z); + } else + throw new NotSupportedError('Key wrapping GOST R 34.10-94 not supported'); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * Wrap private key with private key and ukm (mask) + * + * @memberOf GostSign + * @method unwrap + * @instance + * @param {(CryptoOperationData|TypedArray)} baseKey Wrapping key + * @param {(CryptoOperationData|TypedArray)} data Key + * @returns {Object} CryptoOperationData unwrapped privateKey + */ +function wrapKey(baseKey, data) // +{ + var curve = this.curve; + if (curve) { + var q = this.q; + var x = mod(atobi(buffer(data)), q); + var y = mod(atobi(buffer(baseKey)), q); + var z = this.procreator === 'VN' ? mod(mul(x, invMod(y, q)), q) : mod(mul(x, y), q); + return bitoa(z); + } else + throw new NotSupportedError('Key wrapping GOST R 34.10-94 not supported'); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * @memberOf GostSign + * @method derive + * @instance + * @private + * @param {CryptoOperationData} baseKey Key for deriviation + * @returns {CryptoOperationData} + */ +function derive(baseKey) // +{ + + var k, ukm = atobi(this.ukm); + var q = this.q; + var x = mod(atobi(buffer(baseKey)), q); + + if (this.curve) { + // 1) Let K(x,y,UKM) = ((UKM*x)(mod q)) . (y.P) (512 bit), where + // x - sender’s private key (256 bit) + // x.P - sender’s public key (512 bit) + // y - recipient’s private key (256 bit) + // y.P - recipient’s public key (512 bit) + // UKM - non-zero integer, produced as in step 2 p. 6.1 [GOSTR341001] + // P - base point on the elliptic curve (two 256-bit coordinates) + // UKM*x - x multiplied by UKM as integers + // x.P - a multiple point + var K = mulEC(this.peer_Q, mod(mul(ukm, x), q)); + k = from2(getX(K), getY(K), // This corresponds to the binary representation of (256||256) + this.bitLength); + } else { + // 1) Let K(x,y) = a^(x*y) (mod p), where + // x - sender’s private key, a^x - sender’s public key + // y - recipient’s private key, a^y - recipient’s public key + // a, p - parameters + var p = this.p, a = this.a; + k = bitoa(expMod(this.peer_y, x, p)); + } + // 2) Calculate a 256-bit hash of K(x,y,UKM): + // KEK(x,y,UKM) = gostSign (K(x,y,UKM) + return hash.call(this, k); +} // + +/** + * Algorithm name GOST R 34.10

+ * + * The deriveBits method returns length bits on baseKey. + * + * @memberOf GostSign + * @method deriveBits + * @instance + * @param {(CryptoOperationData|TypedArray)} baseKey Key for deriviation + * @param {number} length output bit-length + * @returns {CryptoOperationData} result + */ +function deriveBits(baseKey, length) // +{ + if (length < 8 || length > this.bitLength || length % 8 > 0) + throw new DataError('Length must be no more than ' + this.bitLength + ' bits and multiple of 8'); + var n = length / 8, + b = derive.call(this, baseKey), + r = new Uint8Array(n); + + r.set(new Uint8Array(b, 0, n)); + return r.buffer; +} // + +/** + * Algorithm name GOST R 34.10

+ * + * The deriveKey method returns 256 bit Key encryption key on baseKey. + * + * This algorithm creates a key encryption key (KEK) using 64 bit UKM, + * the sender’s private key, and the recipient’s public key (or the + * reverse of the latter pair + * + * @memberOf GostSign + * @method deriveKey + * @instance + * @param {(CryptoOperationData|TypedArray)} baseKey Key for deriviation + * @returns {CryptoOperationData} result + */ +function deriveKey(baseKey) // +{ + var b = derive.call(this, baseKey), + r = new Uint8Array(32); + + r.set(new Uint8Array(b, 0, 32)); + return r.buffer; +} // + + +/** + * Gost R 34.10 universal object

+ * + * References: {@link http://tools.ietf.org/html/rfc6986} and {@link http://tools.ietf.org/html/rfc5831}

+ * + * Normalized algorithm identifier common parameters: + * + *
    + *
  • name Algorithm name 'GOST R 34.10'
  • + *
  • version Algorithm version + *
      + *
    • 1994 - Old-style GOST R 34.10-94 ExpMod algorithm with GOST R 34.11-94 hash
    • + *
    • 2001 - GOST R 34.10-2001 Eliptic curve algorithm with old GOST R 34.11-94 hash
    • + *
    • 2012 - GOST R 34.10-2012 Eliptic curve algorithm with GOST R 34.11-12 hash, default mode
    • + *
    + *
  • + *
  • length Length of hash and signature. Key length == hash length for EC algorithms and 2 * hash length for ExpMod algorithm + *
      + *
    • GOST R 34.10-256 - 256 bits digest, default mode
    • + *
    • GOST R 34.10-512 - 512 bits digest only for GOST R 34.11-2012 hash
    • + *
    + *
  • + *
  • mode Algorithm mode + *
      + *
    • SIGN Digital signature mode (default)
    • + *
    • DH Diffie-Hellman key generation and key agreement mode
    • + *
    + *
  • + *
  • sBox Paramset sBox for GOST 34.11-94. Used only if version = 1994 or 2001
  • + *
+ * + * Supported algorithms, modes and parameters: + * + *
    + *
  • Sign/Verify mode (SIGN)
  • + *
  • DeriveKey/DeriveBits mode (DH) + *
      + *
    • {@link CryptoOperationData} ukm User key material. Default - random generated value
    • + *
    • {@link CryptoOperationData} public The peer's EC public key data
    • + *
    + *
  • + *
  • GenerateKey mode (SIGN and DH) version = 1994 + *
      + *
    • namedParam Paramset for key generation algorithm. If specified no additianal parameters required
    • + *
    + * Additional parameters, if namedParam not specified + *
      + *
    • modulusLength Bit length of p (512 or 1024 bits). Default = 1024
    • + *
    • p {@link CryptoOperationData} Modulus, prime number, 2^(t-1) + *
    • q {@link CryptoOperationData} Order of cyclic group, prime number, 2^254 + *
    • a {@link CryptoOperationData} Generator, integer, 1 + *
    + *
  • + *
  • GenerateKey mode (SIGN and DH) version = 2001 or 2012 + *
      + *
    • namedCurve Paramset for key generation algorithm. If specified no additianal parameters required
    • + *
    + * Additional EC parameters, if namedCurve not specified + *
      + *
    • p {@link CryptoOperationData} Prime number - elliptic curve modulus
    • + *
    • a {@link CryptoOperationData} Coefficients a of the elliptic curve E
    • + *
    • b {@link CryptoOperationData} Coefficients b of the elliptic curve E
    • + *
    • q {@link CryptoOperationData} Prime number - order of cyclic group
    • + *
    • x {@link CryptoOperationData} Base point p x-coordinate
    • + *
    • y {@link CryptoOperationData} Base point p y-coordinate
    • + *
    + *
  • + *
+ * + * @class GostSign + * @param {AlgorithmIndentifier} algorithm + */ +function GostSign(algorithm) // +{ + algorithm = algorithm || {}; + this.name = (algorithm.name || 'GOST R 34.10') + '-' + + ((algorithm.version || 2012) % 100) + '-' + (algorithm.length || 256) + + (((algorithm.mode || 'SIGN') !== 'SIGN') ? '-' + algorithm.mode : '') + + (typeof algorithm.namedParam === 'string' ? '/' + algorithm.namedParam : '') + + (typeof algorithm.namedCurve === 'string' ? '/' + algorithm.namedCurve : '') + + (typeof algorithm.sBox === 'string' ? '/' + algorithm.sBox : ''); + + var version = algorithm.version || 2012; + + // Functions + switch (algorithm.mode || 'SIGN') { + case 'SIGN': + this.sign = sign; + this.verify = verify; + this.generateKey = generateKey; + break; + case 'DH': + this.deriveBits = deriveBits; + this.deriveKey = deriveKey; + this.generateKey = generateKey; + break; + case 'MASK': + this.wrapKey = wrapKey; + this.unwrapKey = unwrapKey; + this.generateKey = generateMaskKey; + break; + } + + // Define parameters + if (version === 1994) { + // Named or parameters algorithm + var param = algorithm.param; + if (!param) + param = GostParams[this.namedParam = (algorithm.namedParam || 'S-A').toUpperCase()]; + this.modulusLength = algorithm.modulusLength || param.modulusLength || 1024; + this.p = htobi(param.p); + this.q = htobi(param.q); + this.a = htobi(param.a); + // Public key for derive + if (algorithm['public']) + this.peer_y = atobi(algorithm['public']); + } else { + // Named or parameters algorithm + var param = algorithm.curve; + if (!param) + param = ECGostParams[this.namedCurve = (algorithm.namedCurve || 'S-256-A').toUpperCase()]; + var curve = this.curve = newCurve(htobi(param.p), htobi(param.a), htobi(param.b)); + this.P = newEC(curve, + newFE(curve, htobi(param.x)), + newFE(curve, htobi(param.y))); + this.q = htobi(param.q); + // Public key for derive + if (algorithm['public']) { + var k2 = to2(algorithm['public']); + this.peer_Q = new newEC(this.curve, // This corresponds to the binary representation of (256||256) + newFE(this.curve, k2[0]), // first 32 octets contain the little-endian representation of x + newFE(this.curve, k2[1])); // and second 32 octets contain the little-endian representation of y. + } + } + + // Check bit length + var hashLen, keyLen; + if (this.curve) { + keyLen = algorithm.length || bitLength(this.q); + if (keyLen > 508 && keyLen <= 512) + keyLen = 512; + else if (keyLen > 254 && keyLen <= 256) + keyLen = 256; + else + throw new NotSupportedError('Support keys only 256 or 512 bits length'); + hashLen = keyLen; + } else { + keyLen = algorithm.modulusLength || bitLength(this.p); + if (keyLen > 1016 && keyLen <= 1024) + keyLen = 1024; + else if (keyLen > 508 && keyLen <= 512) + keyLen = 512; + else + throw new NotSupportedError('Support keys only 512 or 1024 bits length'); + hashLen = 256; + } + this.bitLength = hashLen; + this.keyLength = keyLen; + + // Algorithm proceator for result conversion + this.procreator = algorithm.procreator; + + // Hash function definition + var hash = algorithm.hash; + if (hash) { + if (typeof hash === 'string' || hash instanceof String) + hash = {name: hash}; + if (algorithm.version === 1994 || algorithm.version === 2001) { + hash.version = 1994; + hash.length = 256; + hash.sBox = algorithm.sBox || hash.sBox; + } else { + hash.version = 2012; + hash.length = hashLen; + } + hash.procreator = hash.procreator || algorithm.procreator; + + if (!GostDigest) + GostDigest = root.GostDigest; + if (!GostDigest) + throw new NotSupportedError('Object GostDigest not found'); + + this.hash = new GostDigest(hash); + } + + // Pregenerated seed for key exchange algorithms + if (algorithm.ukm) // Now don't check size + this.ukm = algorithm.ukm; + +} // + +export default GostSign; diff --git a/src/core/vendor/remove-exif.mjs b/src/core/vendor/remove-exif.mjs new file mode 100644 index 00000000..6c5c9e53 --- /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.mjs"; + +// 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/core/vendor/tesseract/lang-data/eng.traineddata.gz b/src/core/vendor/tesseract/lang-data/eng.traineddata.gz new file mode 100644 index 00000000..e83c1267 Binary files /dev/null and b/src/core/vendor/tesseract/lang-data/eng.traineddata.gz differ diff --git a/src/node/File.mjs b/src/node/File.mjs new file mode 100644 index 00000000..7a38ba16 --- /dev/null +++ b/src/node/File.mjs @@ -0,0 +1,76 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import { detectFileType } from "../core/lib/FileType.mjs"; + + +/** + * FileShim + * + * Create a class that behaves like the File object in the Browser so that + * operations that use the File object still work. + * + * File doesn't write to disk, but it would be easy to do so with e.gfs.writeFile. + */ +class File { + + /** + * Constructor + * + * https://w3c.github.io/FileAPI/#file-constructor + * + * @param {String|Array|ArrayBuffer|Buffer|[File]} bits - file content + * @param {String} name (optional) - file name + * @param {Object} stats (optional) - file stats e.g. lastModified + */ + constructor(data, name="", stats={}) { + + if (!Array.isArray(data)) { + data = [data]; + } + + const buffers = data.map((d) => { + if (d instanceof File) { + return Buffer.from(d.data); + } + + if (d instanceof ArrayBuffer) { + return Buffer.from(d); + } + + return Buffer.from(d); + }); + const totalLength = buffers.reduce((p, c) => p + c.length, 0); + this.data = Buffer.concat(buffers, totalLength); + + this.name = name; + this.lastModified = stats.lastModified || Date.now(); + + const types = detectFileType(this.data); + if (types.length) { + this.type = types[0].mime; + } else { + this.type = "application/unknown"; + } + } + + /** + * size property + */ + get size() { + return this.data.length; + } + + /** + * Return lastModified as Date + */ + get lastModifiedDate() { + return new Date(this.lastModified); + } + +} + +export default File; diff --git a/src/node/NodeDish.mjs b/src/node/NodeDish.mjs new file mode 100644 index 00000000..ef9fd11a --- /dev/null +++ b/src/node/NodeDish.mjs @@ -0,0 +1,84 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import util from "util"; +import Dish from "../core/Dish.mjs"; + +/** + * Subclass of Dish for use in the Node.js environment. + * + * Adds some helper functions and improves coercion for Node.js logging. + */ +class NodeDish extends Dish { + + /** + * Create a Dish + * @param {any} inputOrDish - The dish input + * @param {String|Number} - The dish type, as enum or string + */ + constructor(inputOrDish=null, type=null) { + + // Allow `fs` file input: + // Any node fs Buffers transformed to array buffer + // Use Array.from as Uint8Array doesnt pass instanceof Array test + if (Buffer.isBuffer(inputOrDish)) { + inputOrDish = Array.from(inputOrDish); + type = Dish.BYTE_ARRAY; + } + super(inputOrDish, type); + } + + /** + * Apply the inputted operation to the dish. + * + * @param {WrappedOperation} operation the operation to perform + * @param {*} args - any arguments for the operation + * @returns {Dish} a new dish with the result of the operation. + */ + apply(operation, args=null) { + return operation(this, args); + } + + /** + * alias for get + * @param args see get args + */ + to(...args) { + return this.get(...args); + } + + /** + * Avoid coercion to a String primitive. + */ + toString() { + return this.presentAs(Dish.typeEnum("string")); + } + + /** + * What we want to log to the console. + */ + [util.inspect.custom](depth, options) { + return this.presentAs(Dish.typeEnum("string")); + } + + /** + * Backwards compatibility for node v6 + * Log only the value to the console in node. + */ + inspect() { + return this.presentAs(Dish.typeEnum("string")); + } + + /** + * Avoid coercion to a Number primitive. + */ + valueOf() { + return this.presentAs(Dish.typeEnum("number")); + } + +} + +export default NodeDish; diff --git a/src/node/NodeRecipe.mjs b/src/node/NodeRecipe.mjs new file mode 100644 index 00000000..bad8fc27 --- /dev/null +++ b/src/node/NodeRecipe.mjs @@ -0,0 +1,104 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import {operations} from "./index.mjs"; +import { sanitise } from "./apiUtils.mjs"; + +/** + * Similar to core/Recipe, Recipe controls a list of operations and + * the NodeDish the operate on. However, this Recipe is for the node + * environment. + */ +class NodeRecipe { + + /** + * Recipe constructor + * @param recipeConfig + */ + constructor(recipeConfig) { + this._parseConfig(recipeConfig); + } + + + /** + * Validate an ingredient & coerce to operation if necessary. + * @param {String | Function | Object} ing + * @returns {Function || Object} The operation, or an object with the + * operation and its arguments + * @throws {TypeError} If it cannot find the operation in chef's list of operations. + */ + _validateIngredient(ing) { + // CASE operation name given. Find operation and validate + if (typeof ing === "string") { + const op = operations.find((op) => { + return sanitise(op.opName) === sanitise(ing); + }); + if (op) { + // Need to validate against case 2 + return this._validateIngredient(op); + } else { + throw new TypeError(`Couldn't find an operation with name '${ing}'.`); + } + // CASE operation given. Check its a chef operation and check its not flowcontrol + } else if (typeof ing === "function") { + if (ing.flowControl) { + throw new TypeError(`flowControl operations like ${ing.opName} are not currently allowed in recipes for chef.bake in the Node API`); + } + + if (operations.includes(ing)) { + return ing; + } else { + throw new TypeError("Inputted function not a Chef operation."); + } + // CASE: op, maybe with configuration + } else if (ing.op) { + const sanitisedOp = this._validateIngredient(ing.op); + if (ing.args) { + return {op: sanitisedOp, args: ing.args}; + } + return sanitisedOp; + } else { + throw new TypeError("Recipe can only contain function names or functions"); + } + } + + + /** + * Parse an opList from a recipeConfig and assign it to the recipe's opList. + * @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig + */ + _parseConfig(recipeConfig) { + if (!recipeConfig) { + this.opList = []; + return; + } + + if (!Array.isArray(recipeConfig)) { + recipeConfig = [recipeConfig]; + } + + this.opList = recipeConfig.map((ing) => this._validateIngredient(ing)); + } + + /** + * Run the dish through each operation, one at a time. + * @param {NodeDish} dish + * @returns {NodeDish} + */ + execute(dish) { + return this.opList.reduce((prev, curr) => { + // CASE where opList item is op and args + if (Object.prototype.hasOwnProperty.call(curr, "op") && + Object.prototype.hasOwnProperty.call(curr, "args")) { + return curr.op(prev, curr.args); + } + // CASE opList item is just op. + return curr(prev); + }, dish); + } +} + +export default NodeRecipe; diff --git a/src/node/api.mjs b/src/node/api.mjs new file mode 100644 index 00000000..0c9dd8a7 --- /dev/null +++ b/src/node/api.mjs @@ -0,0 +1,350 @@ +/** + * Wrap operations for consumption in Node. + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +/* eslint no-console: ["off"] */ + +import NodeDish from "./NodeDish.mjs"; +import NodeRecipe from "./NodeRecipe.mjs"; +import OperationConfig from "../core/config/OperationConfig.json" assert {type: "json"}; +import { sanitise, removeSubheadingsFromArray, sentenceToCamelCase } from "./apiUtils.mjs"; +import ExcludedOperationError from "../core/errors/ExcludedOperationError.mjs"; + + +/** + * transformArgs + * + * Take the default args array and update with any user-defined + * operation arguments. Allows user to define arguments in object style, + * with accommodating name matching. Using named args in the API is more + * clear to the user. + * + * Argument name matching is case and space insensitive + * @private + * @param {Object[]} originalArgs - the operation-s args list + * @param {Object} newArgs - any inputted args + */ +function transformArgs(opArgsList, newArgs) { + + if (newArgs && Array.isArray(newArgs)) { + return newArgs; + } + + // Filter out arg values that are list subheadings - they are surrounded in []. + // See Strings op for example. + const opArgs = Object.assign([], opArgsList).map((a) => { + if (Array.isArray(a.value)) { + a.value = removeSubheadingsFromArray(a.value); + } + return a; + }); + + // Reconcile object style arg info to fit operation args shape. + if (newArgs) { + Object.keys(newArgs).map((key) => { + const index = opArgs.findIndex((arg) => { + return arg.name.toLowerCase().replace(/ /g, "") === + key.toLowerCase().replace(/ /g, ""); + }); + + if (index > -1) { + const argument = opArgs[index]; + if (argument.type === "toggleString") { + if (typeof newArgs[key] === "string") { + argument.string = newArgs[key]; + } else { + argument.string = newArgs[key].string; + argument.option = newArgs[key].option; + } + } else if (argument.type === "editableOption") { + // takes key: "option", key: {name, val: "string"}, key: {name, val: [...]} + argument.value = typeof newArgs[key] === "string" ? newArgs[key]: newArgs[key].value; + } else { + argument.value = newArgs[key]; + } + } + }); + } + + // Sanitise args + return opArgs.map((arg) => { + if (arg.type === "option") { + // pick default option if not already chosen + return typeof arg.value === "string" ? arg.value : arg.value[0]; + } + + if (arg.type === "editableOption") { + return typeof arg.value === "string" ? arg.value : arg.value[0].value; + } + + if (arg.type === "toggleString") { + // ensure string and option exist when user hasn't defined + arg.string = arg.string || ""; + arg.option = arg.option || arg.toggleValues[0]; + return arg; + } + + return arg.value; + }); +} + + +/** + * Ensure an input is a SyncDish object. + * @param input + */ +function ensureIsDish(input) { + if (!input) { + return new NodeDish(); + } + + if (input instanceof NodeDish) { + return input; + } else { + return new NodeDish(input); + } +} + + +/** + * prepareOp: transform args, make input the right type. + * Also convert any Buffers to ArrayBuffers. + * @param opInstance - instance of the operation + * @param input - operation input + * @param args - operation args + */ +function prepareOp(opInstance, input, args) { + const dish = ensureIsDish(input); + // Transform object-style args to original args array + const transformedArgs = transformArgs(opInstance.args, args); + const transformedInput = dish.get(opInstance.inputType); + return {transformedInput, transformedArgs}; +} + + +/** + * createArgInfo + * + * Create an object of options for each argument in the given operation + * + * Argument names are converted to camel case for consistency. + * + * @param {Operation} op - the operation to extract args from + * @returns {{}} - arrays of options for args. +*/ +function createArgInfo(op) { + const result = {}; + op.args.forEach((a) => { + if (a.type === "option" || a.type === "editableOption") { + result[sentenceToCamelCase(a.name)] = { + type: a.type, + options: removeSubheadingsFromArray(a.value) + }; + } else if (a.type === "toggleString") { + result[sentenceToCamelCase(a.name)] = { + type: a.type, + value: a.value, + toggleValues: removeSubheadingsFromArray(a.toggleValues), + }; + } else { + result[sentenceToCamelCase(a.name)] = { + type: a.type, + value: a.value, + }; + } + }); + + return result; +} + + +/** + * Wrap an operation to be consumed by node API. + * Checks to see if run function is async or not. + * new Operation().run() becomes operation() + * Perform type conversion on input + * @private + * @param {Operation} Operation + * @returns {Function} The operation's run function, wrapped in + * some type conversion logic + */ +export function _wrap(OpClass) { + + // Check to see if class's run function is async. + const opInstance = new OpClass(); + const isAsync = opInstance.run.constructor.name === "AsyncFunction"; + const isFlowControl = opInstance.flowControl; + + let wrapped; + + // If async, wrap must be async. + if (isAsync) { + /** + * Async wrapped operation run function + * @param {*} input + * @param {Object | String[]} args - either in Object or normal args array + * @returns {Promise} operation's output, on a Dish. + * @throws {OperationError} if the operation throws one. + */ + wrapped = async (input, args=null) => { + const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args); + + // SPECIAL CASE for Magic. Other flowControl operations will + // not work because the opList is not passed in. + if (isFlowControl) { + opInstance.ingValues = transformedArgs; + + const state = { + progress: 0, + dish: ensureIsDish(transformedInput), + opList: [opInstance], + }; + + const updatedState = await opInstance.run(state); + + return new NodeDish({ + value: updatedState.dish.value, + type: opInstance.outputType, + }); + } + + const result = await opInstance.run(transformedInput, transformedArgs); + + return new NodeDish({ + value: result, + type: opInstance.outputType, + }); + }; + } else { + /** + * wrapped operation run function + * @param {*} input + * @param {Object | String[]} args - either in Object or normal args array + * @returns {SyncDish} operation's output, on a Dish. + * @throws {OperationError} if the operation throws one. + */ + wrapped = (input, args=null) => { + const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args); + const result = opInstance.run(transformedInput, transformedArgs); + return new NodeDish({ + value: result, + type: opInstance.outputType, + }); + }; + } + + // used in chef.help + wrapped.opName = OpClass.name; + wrapped.args = createArgInfo(opInstance); + // Used in NodeRecipe to check for flowControl ops + wrapped.flowControl = isFlowControl; + + return wrapped; +} + + +/** + * help: Give information about operations matching the given search term, + * or inputted operation. + * + * @param {String || wrapped operation} input - the name of the operation to get help for. + * Case and whitespace are ignored in search. + * @returns {Object[]} Config of matching operations. + */ +export function help(input) { + let searchTerm = false; + if (typeof input === "string") { + searchTerm = input; + } else if (typeof input === "function") { + searchTerm = input.opName; + } + + if (!searchTerm) { + return null; + } + + let exactMatchExists = false; + + // Look for matches in operation name and description, listing name + // matches first. + const matches = Object.keys(OperationConfig) + // hydrate operation: swap op name for op config object (with name) + .map((m) => { + const hydrated = OperationConfig[m]; + hydrated.name = m; + + // flag up an exact name match. Only first exact match counts. + if (!exactMatchExists) { + exactMatchExists = sanitise(hydrated.name) === sanitise(searchTerm); + } + // Return hydrated along with what type of match it was + return { + hydrated, + nameExactMatch: sanitise(hydrated.name) === sanitise(searchTerm), + nameMatch: sanitise(hydrated.name).includes(sanitise(searchTerm)), + descMatch: sanitise(hydrated.description).includes(sanitise(searchTerm)) + }; + }) + // Filter out non-matches. If exact match exists, filter out all others. + .filter((result) => { + if (exactMatchExists) { + return !!result.nameExactMatch; + } + return result.nameMatch || result.descMatch; + }) + // sort results with name match first + .sort((a, b) => { + const aInt = a.nameMatch ? 1 : 0; + const bInt = b.nameMatch ? 1 : 0; + return bInt - aInt; + }) + // extract just the hydrated config + .map(result => result.hydrated); + + if (matches && matches.length) { + // console.log(`${matches.length} result${matches.length > 1 ? "s" : ""} found.`); + return matches; + } + + // console.log("No results found."); + return null; +} + + +/** + * bake + * + * @param {*} input - some input for a recipe. + * @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig - + * An operation, operation name, or an array of either. + * @returns {NodeDish} of the result + * @throws {TypeError} if invalid recipe given. + */ +export function bake(input, recipeConfig) { + const recipe = new NodeRecipe(recipeConfig); + const dish = ensureIsDish(input); + return recipe.execute(dish); +} + + +/** + * explainExcludedFunction + * + * Explain that the given operation is not included in the Node.js version. + * @param {String} name - name of operation + */ +export function _explainExcludedFunction(name) { + /** + * Throw new error type with useful message. + */ + const func = () => { + throw new ExcludedOperationError(`Sorry, the ${name} operation is not available in the Node.js version of CyberChef.`); + }; + // Add opName prop so NodeRecipe can handle it, just like wrap does. + func.opName = name; + return func; +} diff --git a/src/node/apiUtils.mjs b/src/node/apiUtils.mjs new file mode 100644 index 00000000..64688073 --- /dev/null +++ b/src/node/apiUtils.mjs @@ -0,0 +1,86 @@ +/** + * Utility functions for the node environment + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + + +/** + * someName => Somename + * + * @param {String} str = string to be altered + * @returns {String} + */ +const capitalise = function capitalise(str) { + // Don't edit names that start with 2+ caps + if (/^[A-Z0-9]{2,}/g.test(str)) { + return str; + } + // reserved. Don't change for now. + if (str === "Return") { + return str; + } + + return `${str.charAt(0).toUpperCase()}${str.substr(1).toLowerCase()}`; +}; + + +/** + * SomeName => someName + * @param {String} name - string to be altered + * @returns {String} decapitalised + */ +export function decapitalise(str) { + // Don't decapitalise str that start with 2+ caps + if (/^[A-Z0-9]{2,}/g.test(str)) { + return str; + } + // reserved. Don't change for now. + if (str === "Return") { + return str; + } + + return `${str.charAt(0).toLowerCase()}${str.substr(1)}`; +} + + +/** + * Remove strings surrounded with [] from the given array. +*/ +export function removeSubheadingsFromArray(array) { + if (Array.isArray(array)) { + return array.filter((i) => { + if (typeof i === "string") { + return !i.match(/^\[[\s\S]*\]$/); + } + return true; + }); + } +} + + +/** + * Remove spaces, make lower case. + * @param str + */ +export function sanitise(str) { + return str.replace(/ /g, "").toLowerCase(); +} + + +/** + * something like this => somethingLikeThis + * ABC a sentence => ABCASentence +*/ +export function sentenceToCamelCase(str) { + return str.split(" ") + .map((s, index) => { + if (index === 0) { + return decapitalise(s); + } + return capitalise(s); + }) + .reduce((prev, curr) => `${prev}${curr}`, ""); +} diff --git a/src/node/config/excludedOperations.mjs b/src/node/config/excludedOperations.mjs new file mode 100644 index 00000000..9359475d --- /dev/null +++ b/src/node/config/excludedOperations.mjs @@ -0,0 +1,24 @@ +/** + * Operations to exclude from the Node API + * + * @author d98762656 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +export default [ + // This functionality can be done more easily using JavaScript + "Fork", + "Merge", + "Jump", + "ConditionalJump", + "Label", + "Comment", + + // esprima doesn't work in .mjs + "JavaScriptBeautify", + "JavaScriptMinify", + "JavaScriptParser", + + // Irrelevant in Node console + "SyntaxHighlighter", +]; diff --git a/src/node/config/scripts/generateNodeIndex.mjs b/src/node/config/scripts/generateNodeIndex.mjs new file mode 100644 index 00000000..981c9b79 --- /dev/null +++ b/src/node/config/scripts/generateNodeIndex.mjs @@ -0,0 +1,127 @@ +/** + * This script generates the exports functionality for the node API. + * + * it exports chef as default, but all the wrapped operations as + * other top level exports. + * + * @author d98762656 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +/* eslint no-console: 0 */ + +import fs from "fs"; +import path from "path"; +import * as operations from "../../../core/operations/index.mjs"; +import { decapitalise } from "../../apiUtils.mjs"; +import excludedOperations from "../excludedOperations.mjs"; + +const includedOperations = Object.keys(operations).filter((op => excludedOperations.indexOf(op) === -1)); + +const dir = path.join(`${process.cwd()}/src/node`); +if (!fs.existsSync(dir)) { + console.log("\nCWD: " + process.cwd()); + console.log("Error: generateNodeIndex.mjs should be run from the project root"); + console.log("Example> node --experimental-modules src/node/config/scripts/generateNodeIndex.mjs"); + process.exit(1); +} + +let code = `/** +* THIS FILE IS AUTOMATICALLY GENERATED BY src/node/config/scripts/generateNodeIndex.mjs +* +* @author d98762625 [d98762625@gmail.com] +* @copyright Crown Copyright 2019 +* @license Apache-2.0 +*/ + +/* eslint camelcase: 0 */ + + +import NodeDish from "./NodeDish.mjs"; +import { _wrap, help, bake, _explainExcludedFunction } from "./api.mjs"; +import File from "./File.mjs"; +import { OperationError, DishError, ExcludedOperationError } from "../core/errors/index.mjs"; +import { + // import as core_ to avoid name clashes after wrap. +`; + +includedOperations.forEach((op) => { + // prepend with core_ to avoid name collision later. + code += ` ${op} as core_${op},\n`; +}); + +code +=` +} from "../core/operations/index.mjs"; + +global.File = File; + +/** + * generateChef + * + * Creates decapitalised, wrapped ops in chef object for default export. + */ +function generateChef() { + return { +`; + +includedOperations.forEach((op) => { + code += ` "${decapitalise(op)}": _wrap(core_${op}),\n`; +}); + +excludedOperations.forEach((op) => { + code += ` "${decapitalise(op)}": _explainExcludedFunction("${op}"),\n`; +}); + +code += ` }; +} + +const chef = generateChef(); +// Add some additional features to chef object. +chef.help = help; +chef.Dish = NodeDish; + +// Define consts here so we can add to top-level export - wont allow +// export of chef property. +`; + +Object.keys(operations).forEach((op) => { + code += `const ${decapitalise(op)} = chef.${decapitalise(op)};\n`; +}); + +code +=` + +// Define array of all operations to create register for bake. +const operations = [\n`; + +Object.keys(operations).forEach((op) => { + code += ` ${decapitalise(op)},\n`; +}); + +code += `]; + +chef.bake = bake; +export default chef; + +// Operations as top level exports. +export { + operations, +`; + +Object.keys(operations).forEach((op) => { + code += ` ${decapitalise(op)},\n`; +}); + +code += " NodeDish as Dish,\n"; +code += " bake,\n"; +code += " help,\n"; +code += " OperationError,\n"; +code += " ExcludedOperationError,\n"; +code += " DishError,\n"; +code += "};\n"; + + +fs.writeFileSync( + path.join(dir, "./index.mjs"), + code +); diff --git a/src/node/index.js b/src/node/index.js deleted file mode 100644 index 06db248c..00000000 --- a/src/node/index.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Node view for CyberChef. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - */ -require("babel-polyfill"); - -var 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/repl.mjs b/src/node/repl.mjs new file mode 100644 index 00000000..9846ff31 --- /dev/null +++ b/src/node/repl.mjs @@ -0,0 +1,36 @@ +/** + * Create a REPL server for chef + * + * + * @author d98762656 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import chef from "./index.mjs"; +import repl from "repl"; + + +/* eslint no-console: ["off"] */ + +console.log(` + ______ __ ________ ____ + / ____/_ __/ /_ ___ _____/ ____/ /_ ___ / __/ + / / / / / / __ \\/ _ \\/ ___/ / / __ \\/ _ \\/ /_ +/ /___/ /_/ / /_/ / __/ / / /___/ / / / __/ __/ +\\____/\\__, /_.___/\\___/_/ \\____/_/ /_/\\___/_/ + /____/ + +`); +const replServer = repl.start({ + prompt: "chef > ", +}); + +global.File = chef.File; + +Object.keys(chef).forEach((key) => { + if (key !== "operations") { + replServer.context[key] = chef[key]; + } +}); + diff --git a/src/node/wrapper.js b/src/node/wrapper.js new file mode 100644 index 00000000..80b941fc --- /dev/null +++ b/src/node/wrapper.js @@ -0,0 +1,11 @@ +/** + * Export the main ESM module as CommonJS + * + * + * @author d98762656 [d98762625@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +module.exports = (async () => await import("./index.mjs"))(); +module.exports.File = (async () => await import("./File.mjs"))(); diff --git a/src/web/App.js b/src/web/App.js deleted file mode 100755 index 59121bd9..00000000 --- a/src/web/App.js +++ /dev/null @@ -1,678 +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. - */ -var 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.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(); -}; - - -/** - * An error handler for displaying the error to the user. - * - * @param {Error} err - */ -App.prototype.handleError = function(err) { - console.error(err); - var msg = err.displayStr || err.toString(); - this.alert(msg, "danger", this.options.errorTimeout, !this.options.showErrors); -}; - - -/** - * 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 = function(step) { - var response; - - try { - response = 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); - } - - 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() { - var 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() { - var 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")); - - var html = ""; - - for (var i = 0; i < this.categories.length; i++) { - var catConf = this.categories[i], - selected = i === 0, - cat = new HTMLCategory(catConf.name, selected); - - for (var j = 0; j < catConf.ops.length; j++) { - var 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; - - var 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 - var 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() { - var favourites = localStorage.favourites && - localStorage.favourites.length > 2 ? - JSON.parse(localStorage.favourites) : - this.dfavourites; - - favourites = this.validFavourites(favourites); - this.saveFavourites(favourites); - - var 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) { - var validFavs = []; - for (var 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) { - var 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 {}; - var b = {}; - for (var i = 0; i < a.length; i++) { - var 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 - var autoBakeVal = this.autoBake_; - this.autoBake_ = false; - - // Read in recipe from query string - if (this.queryString.recipe) { - try { - var 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 - var matchedOps = this.manager.ops.filterOperations(this.queryString.op, false); - if (matchedOps.length) { - this.manager.recipe.addOperation(matchedOps[0].name); - } - - // Populate search with the string - var 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 { - var 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() { - var 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 (var i = 0; i < recipeConfig.length; i++) { - var item = this.manager.recipe.addOperation(recipeConfig[i].op); - - // Populate arguments - var args = item.querySelectorAll(".arg"); - for (var 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 - var 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) { - var time = new Date(); - - console.log("[" + time.toLocaleString() + "] " + str); - if (silent) return; - - style = style || "danger"; - timeout = timeout || 0; - - var 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"; - - var 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 100644 index 00000000..7071854a --- /dev/null +++ b/src/web/App.mjs @@ -0,0 +1,824 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils, { debounce } from "../core/Utils.mjs"; +import {fromBase64} from "../core/lib/Base64.mjs"; +import Manager from "./Manager.mjs"; +import HTMLCategory from "./HTMLCategory.mjs"; +import HTMLOperation from "./HTMLOperation.mjs"; +import Split from "split.js"; +import moment from "moment-timezone"; +import cptable from "codepage"; + + +/** + * 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.progress = 0; + this.ingId = 0; + + this.appLoaded = false; + this.workerLoaded = false; + this.waitersLoaded = false; + + this.snackbars = []; + } + + + /** + * 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.manager.options.applyPreferredColorScheme(); + this.populateOperationsList(); + this.manager.setup(); + this.manager.output.saveBombe(); + this.adjustComponentSizes(); + this.setCompileMessage(); + this.uriParams = this.getURIParams(); + + log.debug("App loaded"); + this.appLoaded = true; + 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 || !this.waitersLoaded || + !document.getElementById("loader-wrapper")) return; + + // Load state from URI + this.loadURIParams(this.uriParams); + + // 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"); + + // Bake initial input + this.manager.input.bakeAll(); + }.bind(this), 1000); + + // Clear the loading message interval + clearInterval(window.loadingMsgsInt); + + // Remove the loading error handler + window.removeEventListener("error", window.loadingErrorHandler); + + document.dispatchEvent(this.manager.apploaded); + + this.manager.input.calcMaxTabs(); + this.manager.output.calcMaxTabs(); + } + + + /** + * 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(Utils.escapeHtml(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; + + // Remove all current indicators + this.manager.recipe.updateBreakpointIndicator(false); + + this.manager.worker.bake( + 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 (this.baking) { + this.manager.worker.cancelBakeForAutoBake(); + this.baking = false; + } + + if (this.autoBake_) { + log.debug("Auto-baking"); + this.manager.worker.bakeInputs({ + nums: [this.manager.tabs.getActiveTab("input")], + step: false + }); + } else { + this.manager.controls.showStaleIndicator(); + } + } + + + /** + * Executes the next step of the recipe. + */ + step() { + if (this.baking) return; + + // Reset status using cancelBake + this.manager.worker.cancelBake(true, false); + + const activeTab = this.manager.tabs.getActiveTab("input"); + if (activeTab === -1) return; + + let progress = 0; + if (this.manager.output.outputs[activeTab].progress !== false) { + log.error(this.manager.output.outputs[activeTab]); + progress = this.manager.output.outputs[activeTab].progress; + } + + this.manager.input.inputWorker.postMessage({ + action: "step", + data: { + activeTab: activeTab, + progress: progress + 1 + } + }); + } + + + /** + * 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); + } + + + /** + * Sets the user's input data. + * + * @param {string} input - The string to set the input to + */ + setInput(input) { + // Get the currently active tab. + // If there isn't one, assume there are no inputs so use inputNum of 1 + let inputNum = this.manager.tabs.getActiveTab("input"); + if (inputNum === -1) inputNum = 1; + this.manager.input.updateInputValue(inputNum, input); + + this.manager.input.inputWorker.postMessage({ + action: "setInput", + data: { + inputNum: inputNum, + silent: true + } + }); + } + + + /** + * 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 (!(opName in this.operations)) { + 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) + const favCat = document.querySelector("#categories a"); + favCat.appendChild(document.getElementById("edit-favourites")); + favCat.setAttribute("data-help-title", "Favourite operations"); + favCat.setAttribute("data-help", `

This category displays your favourite operations.

+
    +
  • To add: drag an operation over the Favourites category
  • +
  • To reorder: Click on the 'Edit favourites' button and drag operations up and down in the list provided
  • +
  • To remove: Click on the 'Edit favourites' button and hit the delete button next to the operation you want to remove
  • +
`); + } + + + /** + * 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 minimise 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, 310, 450], + gutterSize: 4, + expandToMin: true, + onDrag: debounce(function() { + this.adjustComponentSizes(); + }, 50, "dragSplitter", this, []) + }); + + this.ioSplitter = Split(["#input", "#output"], { + direction: "vertical", + gutterSize: 4, + minSize: minimise ? [0, 0] : [100, 100] + }); + + this.adjustComponentSizes(); + } + + + /** + * 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?.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 (favourites[i] in this.operations) { + 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(); + } + + /** + * Gets the URI params from the window and parses them to extract the actual values. + * + * @returns {object} + */ + getURIParams() { + // 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; + const parsedParams = Utils.parseURIParams(params); + return parsedParams; + } + + /** + * Searches the URI parameters for recipe and input parameters. + * If recipe is present, replaces the current recipe with the recipe provided in the URI. + * If input is present, decodes and sets the input to the one provided in the URI. + * If character encodings are present, sets them appropriately. + * If theme is present, uses the theme. + * + * @param {Object} params + * @fires Manager#statechange + */ + loadURIParams(params=this.getURIParams()) { + this.uriParams = params; + + // 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")); + } + + // Input Character Encoding + // Must be set before the input is loaded + if (this.uriParams.ienc) { + this.manager.input.chrEncChange(parseInt(this.uriParams.ienc, 10), true, true); + } + + // Output Character Encoding + if (this.uriParams.oenc) { + this.manager.output.chrEncChange(parseInt(this.uriParams.oenc, 10), true); + } + + // Input EOL sequence + if (this.uriParams.ieol) { + this.manager.input.eolChange(this.uriParams.ieol, true); + } + + // Output EOL sequence + if (this.uriParams.oeol) { + this.manager.output.eolChange(this.uriParams.oeol, true); + } + + // Read in input data from URI params + if (this.uriParams.input) { + try { + let inputVal; + const inputChrEnc = this.manager.input.getChrEnc(); + const inputData = fromBase64(this.uriParams.input, null, "byteArray"); + if (inputChrEnc > 0) { + inputVal = cptable.utils.decode(inputChrEnc, inputData); + } else { + inputVal = Utils.byteArrayToChars(inputData); + } + this.setInput(inputVal); + } catch (err) {} + } + + // Read in theme from URI params + if (this.uriParams.theme) { + this.manager.options.changeTheme(Utils.escapeHtml(this.uriParams.theme)); + } else { + this.manager.options.applyPreferredColorScheme(); + } + + 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; + + for (let i = 0; i < recipeConfig.length; i++) { + const item = this.manager.recipe.addOperation(recipeConfig[i].op); + + // Populate arguments + log.debug(`Populating arguments for ${recipeConfig[i].op}`); + 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.manager.recipe.triggerArgEvents(item); + + this.progress = 0; + } + } + + + /** + * Resets the splitter positions to default. + */ + resetLayout() { + this.columnSplitter.setSizes([20, 30, 50]); + this.ioSplitter.setSizes([50, 50]); + this.adjustComponentSizes(); + } + + /** + * Adjust components to fit their containers. + */ + adjustComponentSizes() { + this.manager.recipe.adjustWidth(); + this.manager.input.calcMaxTabs(); + this.manager.output.calcMaxTabs(); + this.manager.controls.calcControlsHeight(); + } + + + /** + * 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)); + notice.setAttribute("data-help-title", "Last build"); + notice.setAttribute("data-help", "This live version of CyberChef is updated whenever new commits are added to the master branch of the CyberChef repository. It represents the latest, most up-to-date build of CyberChef."); + } + + + /** + * 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=0] - The number of milliseconds before the alert closes automatically + * 0 for never (until the user closes it) + * @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the + * console + * + * @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=0, silent=false) { + const time = new Date(); + + log.info("[" + time.toLocaleString() + "] " + str); + if (silent) return; + + this.snackbars.push($.snackbar({ + content: str, + timeout: timeout, + htmlAllowed: true, + onClose: () => { + this.snackbars.shift().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 {string} accept - The text of the accept button + * @param {string} reject - The text of the reject button + * @param {function} callback - A function accepting one boolean argument which handles the + * response e.g. function(answer) {...} + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Pops up a box asking if the user would like a cookie. Prints the answer to the console. + * this.confirm("Question", "Would you like a cookie?", "Yes", "No", function(answer) {console.log(answer);}); + */ + confirm(title, body, accept, reject, callback, scope) { + scope = scope || this; + document.getElementById("confirm-title").innerHTML = title; + document.getElementById("confirm-body").innerHTML = body; + document.getElementById("confirm-yes").innerText = accept; + document.getElementById("confirm-no").innerText = reject; + document.getElementById("confirm-modal").style.display = "block"; + + this.confirmClosed = false; + $("#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("click", "#confirm-no", function() { + this.confirmClosed = true; + callback.bind(scope)(false); + }.bind(this)) + .one("hide.bs.modal", function(e) { + if (!this.confirmClosed) { + callback.bind(scope)(undefined); + } + 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) { + debounce(function() { + this.progress = 0; + this.autoBake(); + this.updateURL(true, null, true); + }, 20, "stateChange", this, [])(); + } + + + /** + * Update the page title and URL to contain the new recipe + * + * @param {boolean} includeInput + * @param {string} [input=null] + * @param {boolean} [changeUrl=true] + */ + updateURL(includeInput, input=null, changeUrl=true) { + // 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 && changeUrl) { + this.lastStateUrl = this.manager.controls.generateStateUrl(true, includeInput, input, 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/ControlsWaiter.js b/src/web/ControlsWaiter.js deleted file mode 100755 index 9896ed9e..00000000 --- a/src/web/ControlsWaiter.js +++ /dev/null @@ -1,362 +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. - */ -var 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() { - var controls = document.getElementById("controls"), - step = document.getElementById("step"), - clrBreaks = document.getElementById("clr-breaks"), - saveImg = document.querySelector("#save img"), - loadImg = document.querySelector("#load img"), - stepImg = document.querySelector("#step img"), - clrRecipImg = document.querySelector("#clr-recipe img"), - 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) { - var autoBakeCheckbox = document.getElementById("auto-bake"); - - if (autoBakeCheckbox.checked !== value) { - autoBakeCheckbox.click(); - } -}; - - -/** - * Handler to trigger baking. - */ -ControlsWaiter.prototype.bakeClick = function() { - this.app.bake(); - var 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); - var outputText = document.getElementById("output-text"); - outputText.focus(); - outputText.setSelectionRange(0, 0); -}; - - -/** - * Handler for changes made to the Auto Bake checkbox. - */ -ControlsWaiter.prototype.autoBakeChange = function() { - var autoBakeLabel = document.getElementById("auto-bake-label"), - autoBakeCheckbox = document.getElementById("auto-bake"); - - this.app.autoBake_ = autoBakeCheckbox.checked; - - if (autoBakeCheckbox.checked) { - autoBakeLabel.classList.remove("btn-default"); - autoBakeLabel.classList.add("btn-success"); - } else { - autoBakeLabel.classList.remove("btn-success"); - autoBakeLabel.classList.add("btn-default"); - } -}; - - -/** - * 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() { - var bps = document.querySelectorAll("#rec-list li.operation .breakpoint"); - - for (var 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(); - - var includeRecipe = document.getElementById("save-link-recipe-checkbox").checked, - includeInput = document.getElementById("save-link-input-checkbox").checked, - saveLinkEl = document.getElementById("save-link"), - 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(); - - var link = baseURL || window.location.protocol + "//" + - window.location.host + - window.location.pathname, - recipeStr = JSON.stringify(recipeConfig), - 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); - - if (includeRecipe) { - link += "?recipe=" + encodeURIComponent(recipeStr); - } - - if (includeRecipe && includeInput) { - link += "&input=" + encodeURIComponent(inputStr); - } else if (includeInput) { - link += "?input=" + encodeURIComponent(inputStr); - } - - return link; -}; - - -/** - * Handler for changes made to the save dialog text area. Re-initialises the save link. - */ -ControlsWaiter.prototype.saveTextChange = function() { - try { - var 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() { - var recipeConfig = this.app.getRecipeConfig(), - 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() { - var recipeName = document.getElementById("save-name").value, - recipeStr = document.getElementById("save-text").value; - - if (!recipeName) { - this.app.alert("Please enter a recipe name", "danger", 2000); - return; - } - - var 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() { - var loadNameEl = document.getElementById("load-name"); - - // Remove current recipes from select - var i = loadNameEl.options.length; - while (i--) { - loadNameEl.remove(i); - } - - // Add recipes to select - var savedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : []; - - for (i = 0; i < savedRecipes.length; i++) { - var opt = document.createElement("option"); - opt.value = savedRecipes[i].id; - opt.innerHTML = 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() { - var id = parseInt(document.getElementById("load-name").value, 10), - savedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : []; - - savedRecipes = savedRecipes.filter(function(r) { - return r.id !== id; - }); - - localStorage.savedRecipes = JSON.stringify(savedRecipes); - this.populateLoadRecipesList(); -}; - - -/** - * Displays the selected recipe in the load text box. - */ -ControlsWaiter.prototype.loadNameChange = function(e) { - var el = e.target, - savedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : [], - id = parseInt(el.value, 10); - - var recipe = savedRecipes.filter(function(r) { - return r.id === id; - })[0]; - - document.getElementById("load-text").value = recipe.recipe; -}; - - -/** - * Loads the selected recipe and populates the Recipe with its operations. - */ -ControlsWaiter.prototype.loadButtonClick = function() { - try { - var 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() { - var reportBugInfo = document.getElementById("report-bug-info"), - 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/HTMLCategory.js b/src/web/HTMLCategory.js deleted file mode 100755 index d1120c20..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. - */ -var 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() { - var catName = "cat" + this.name.replace(/[\s/-:_]/g, ""); - var html = "
\ - \ - " + this.name + "\ - \ -
    "; - - for (var 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..b61a6740 --- /dev/null +++ b/src/web/HTMLCategory.mjs @@ -0,0 +1,62 @@ +/** + * @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 05e98b9c..00000000 --- a/src/web/HTMLIngredient.js +++ /dev/null @@ -1,214 +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. - */ -var 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() { - var 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) { - var el = e.target, - op = el.parentNode.parentNode, - args = op.querySelectorAll(".arg-group"), - els; - - for (var i = 0; i < this.disableArgs.length; i++) { - els = args[this.disableArgs[i]].querySelectorAll("input, select, button"); - - for (var 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) { - var el = e.target, - op = el.parentNode.parentNode, - 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) { - var 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..7eddb32c --- /dev/null +++ b/src/web/HTMLIngredient.mjs @@ -0,0 +1,422 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "../core/Utils.mjs"; + +/** + * 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.maxLength = config.maxLength || null; + this.toggleValues = config.toggleValues; + this.ingId = this.app.nextIngId(); + this.id = "ing-" + this.ingId; + this.tabIndex = this.ingId + 2; // Input = 1, Search = 2 + this.min = (typeof config.min === "number") ? config.min : ""; + this.max = (typeof config.max === "number") ? config.max : ""; + this.step = config.step || 1; + } + + + /** + * Renders the ingredient in HTML. + * + * @returns {string} + */ + toHtml() { + let html = "", + i, m, eventFn; + + 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 += `
+
+ +
+
`; + break; + case "option": + html += `
+ + +
`; + break; + case "populateOption": + case "populateMultiOption": + html += `
+ + +
`; + + eventFn = this.type === "populateMultiOption" ? + this.populateMultiOptionChange : + this.populateOptionChange; + this.manager.addDynamicListener("#" + this.id, "change", eventFn, this); + break; + case "editableOption": + html += `
+ + +
+ + +
+
`; + + this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this); + break; + case "editableOptionShort": + html += `
+ + +
+ + +
+
`; + + this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this); + break; + case "text": + html += `
+ + +
`; + break; + case "argSelector": + html += `
+ + +
`; + + this.manager.addDynamicListener(".arg-selector", "change", this.argSelectorChange, this); + break; + case "label": + html += `
+ + +
`; + break; + default: + break; + } + + return html; + } + + + /** + * Handler for populate option changes. + * Populates the relevant argument with the specified value. + * + * @param {event} e + */ + populateOptionChange(e) { + e.preventDefault(); + e.stopPropagation(); + + const el = e.target; + const op = el.parentNode.parentNode; + const target = op.querySelectorAll(".arg")[this.target]; + + const popVal = el.childNodes[el.selectedIndex].getAttribute("populate-value"); + if (popVal !== "") target.value = popVal; + + const evt = new Event("change"); + target.dispatchEvent(evt); + + this.manager.recipe.ingChange(); + } + + + /** + * Handler for populate multi option changes. + * Populates the relevant arguments with the specified values. + * + * @param {event} e + */ + populateMultiOptionChange(e) { + e.preventDefault(); + e.stopPropagation(); + + const el = e.target; + const op = el.parentNode.parentNode; + const args = op.querySelectorAll(".arg"); + const targets = this.target.map(i => args[i]); + const vals = JSON.parse(el.childNodes[el.selectedIndex].getAttribute("populate-value")); + const evt = new Event("change"); + + for (let i = 0; i < targets.length; i++) { + targets[i].value = vals[i]; + } + + // Fire change event after all targets have been assigned + this.manager.recipe.ingChange(); + + // Send change event for each target once all have been assigned, to update the label placement. + for (const target of targets) { + target.dispatchEvent(evt); + } + } + + + /** + * 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(); + } + + + /** + * Handler for argument selector changes. + * Shows or hides the relevant arguments for this operation. + * + * @param {event} e + */ + argSelectorChange(e) { + e.preventDefault(); + e.stopPropagation(); + + const option = e.target.options[e.target.selectedIndex]; + const op = e.target.closest(".operation"); + const args = op.querySelectorAll(".ingredients .form-group"); + const turnon = JSON.parse(option.getAttribute("turnon")); + const turnoff = JSON.parse(option.getAttribute("turnoff")); + + args.forEach((arg, i) => { + if (turnon.includes(i)) { + arg.classList.remove("d-none"); + } + if (turnoff.includes(i)) { + arg.classList.add("d-none"); + } + }); + } + +} + +export default HTMLIngredient; diff --git a/src/web/HTMLOperation.js b/src/web/HTMLOperation.js deleted file mode 100755 index dd9a8ee1..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. - */ -var 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 (var i = 0; i < config.args.length; i++) { - var 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) { - var 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() { - var html = "
    " + this.name + "
    "; - - for (var 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..30cfd1d9 --- /dev/null +++ b/src/web/HTMLOperation.mjs @@ -0,0 +1,177 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import HTMLIngredient from "./HTMLIngredient.mjs"; +import Utils from "../core/Utils.mjs"; +import url from "url"; + + +/** + * 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 = `
    ${Utils.escapeHtml(this.name)}
    +
    `; + + for (let i = 0; i < this.ingList.length; i++) { + html += this.ingList[i].toHtml(); + } + + html += `
    +
    + pause + not_interested + keyboard_arrow_up +
    +
     
    `; + + return html; + } + + + /** + * Highlights searched strings in the name and description of the operation. + * + * @param {[[number]]} nameIdxs - Indexes of the search strings in the operation name [[start, length]] + * @param {[[number]]} descIdxs - Indexes of the search strings in the operation description [[start, length]] + */ + highlightSearchStrings(nameIdxs, descIdxs) { + if (nameIdxs.length && typeof nameIdxs[0][0] === "number") { + let opName = "", + pos = 0; + + nameIdxs.forEach(idxs => { + const [start, length] = idxs; + if (typeof start !== "number") return; + opName += this.name.slice(pos, start) + "" + + this.name.slice(start, start + length) + ""; + pos = start + length; + }); + opName += this.name.slice(pos, this.name.length); + this.name = opName; + } + + if (this.description && descIdxs.length && descIdxs[0][0] >= 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. + const inHTMLTag = descIdxs.reduce((acc, idxs) => { + const start = idxs[0]; + return start >= match.index && start <= (match.index + match[0].length); + }, false); + + if (inHTMLTag) return; + } + + let desc = "", + pos = 0; + + descIdxs.forEach(idxs => { + const [start, length] = idxs; + desc += this.description.slice(pos, start) + "" + + this.description.slice(start, start + length) + ""; + pos = start + length; + }); + desc += this.description.slice(pos, this.description.length); + this.description = desc; + } + } + +} + + +/** + * Given a URL for a Wikipedia (or other wiki) page, this function returns a link to that page. + * + * @param {string} urlStr + * @returns {string} + */ +function titleFromWikiLink(urlStr) { + const urlObj = url.parse(urlStr); + let wikiName = "", + pageTitle = ""; + + switch (urlObj.host) { + case "forensics.wiki": + wikiName = "Forensics Wiki"; + pageTitle = Utils.toTitleCase(urlObj.path.replace(/\//g, "").replace(/_/g, " ")); + break; + case "wikipedia.org": + wikiName = "Wikipedia"; + pageTitle = urlObj.pathname.substr(6).replace(/_/g, " "); // Chop off '/wiki/' + break; + default: + // Not a wiki link, return full URL + return `More Informationopen_in_new`; + } + + 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 56e4ae81..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. - */ -var 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() { - var backwards = false, - sel = window.getSelection(); - - if (!sel.isCollapsed) { - var 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) { - var sel = window.getSelection(), - 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() { - var sel = window.getSelection(), - 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) { - var 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) { - var 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(); - - var el = e.target, - start = el.selectionStart, - 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(); - - var el = e.target, - start = el.selectionStart, - 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; - - var 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; - - var el = e.target, - start = el.selectionStart, - 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; - - var el = e.target, - start = el.selectionStart, - 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; - - var 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) { - var width = end.toString().length; - width = width < 2 ? 2 : width; - var startStr = Utils.pad(start.toString(), width, " ").replace(/ /g, " "), - endStr = Utils.pad(end.toString(), width, " ").replace(/ /g, " "), - 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() { - var recipeConfig = this.app.getRecipeConfig(), - highlights = []; - - for (var 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; - - var 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) { - var highlights = this.generateHighlightList(); - - if (!highlights || !this.app.autoBake_) { - return false; - } - - for (var 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) { - var highlights = this.generateHighlightList(); - - if (!highlights || !this.app.autoBake_) { - return false; - } - - for (var 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; - - var startPlaceholder = "[startHighlight]", - startPlaceholderRegex = /\[startHighlight\]/g, - endPlaceholder = "[endHighlight]", - endPlaceholderRegex = /\[endHighlight\]/g, - 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? - var result = "", - endPlaced = true; - - for (var i = 0; i < text.length; i++) { - for (var 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; - } - - var 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 158d2b70..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. - */ -var 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) { - var width = length.toString().length; - width = width < 2 ? 2 : width; - - var lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " "); - var 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 - var inputText = this.get(), - 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(); - - var el = e.target, - file = e.dataTransfer.files[0], - text = e.dataTransfer.getData("Text"), - reader = new FileReader(), - inputCharcode = "", - offset = 0, - CHUNK_SIZE = 20480; // 20KB - - var 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); - var 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); - - var seek = function() { - if (offset >= file.size) { - setInput(); - return; - } - el.value = "Processing... " + Math.round(offset / file.size * 100) + "%"; - var slice = file.slice(offset, offset + CHUNK_SIZE); - reader.readAsArrayBuffer(slice); - }; - - reader.onload = function(e) { - var 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/Manager.js b/src/web/Manager.js deleted file mode 100755 index 28b9f93a..00000000 --- a/src/web/Manager.js +++ /dev/null @@ -1,278 +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. - */ -var 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); - - // 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) { - var evs = eventTypes.split(" "); - for (var 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) { - var evs = eventTypes.split(" "); - for (var 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) { - var 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) { - var handlers = this.dynamicHandlers[e.type], - matches = e.target.matches || - e.target.webkitMatchesSelector || - e.target.mozMatchesSelector || - e.target.msMatchesSelector || - e.target.oMatchesSelector; - - for (var i = 0; i < handlers.length; i++) { - if (matches && e.target[matches.name](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..2e87210c --- /dev/null +++ b/src/web/Manager.mjs @@ -0,0 +1,361 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import WorkerWaiter from "./waiters/WorkerWaiter.mjs"; +import WindowWaiter from "./waiters/WindowWaiter.mjs"; +import ControlsWaiter from "./waiters/ControlsWaiter.mjs"; +import RecipeWaiter from "./waiters/RecipeWaiter.mjs"; +import OperationsWaiter from "./waiters/OperationsWaiter.mjs"; +import InputWaiter from "./waiters/InputWaiter.mjs"; +import OutputWaiter from "./waiters/OutputWaiter.mjs"; +import OptionsWaiter from "./waiters/OptionsWaiter.mjs"; +import HighlighterWaiter from "./waiters/HighlighterWaiter.mjs"; +import SeasonalWaiter from "./waiters/SeasonalWaiter.mjs"; +import BindingsWaiter from "./waiters/BindingsWaiter.mjs"; +import BackgroundWorkerWaiter from "./waiters/BackgroundWorkerWaiter.mjs"; +import TabWaiter from "./waiters/TabWaiter.mjs"; +import TimingWaiter from "./waiters/TimingWaiter.mjs"; + + +/** + * 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.timing = new TimingWaiter(this.app, this); + 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.tabs = new TabWaiter(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.input.setupInputWorker(); + this.input.addInput(true); + this.worker.setupChefWorker(); + this.recipe.initialiseOperationDragNDrop(); + this.controls.initComponents(); + this.controls.autoBakeChange(); + this.bindings.updateKeybList(); + this.background.registerChefWorker(); + this.seasonal.load(); + + this.confirmWaitersLoaded(); + } + + /** + * Confirms that all Waiters have loaded correctly. + */ + confirmWaitersLoaded() { + if (this.tabs.getActiveTab("input") >= 0 && + this.tabs.getActiveTab("output") >= 0) { + log.debug("Waiters loaded"); + this.app.waitersLoaded = true; + this.app.loaded(); + } else { + // Not loaded yet, try again soon + setTimeout(this.confirmWaitersLoaded.bind(this), 10); + } + } + + + /** + * 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("hide-icon").addEventListener("click", this.controls.hideRecipeArgsClick.bind(this.recipe)); + 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(".hide-args-icon", "click", this.recipe.hideArgsClick, 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 + document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); + this.addListeners("#clr-io,#btn-close-all-tabs", "click", this.input.clearAllIoClick, this.input); + this.addListeners("#open-file,#open-folder", "change", this.input.inputOpen, this.input); + document.getElementById("btn-open-file").addEventListener("click", this.input.inputOpenClick.bind(this.input)); + document.getElementById("btn-open-folder").addEventListener("click", this.input.folderOpenClick.bind(this.input)); + this.addListeners("#input-wrapper", "dragover", this.input.inputDragover, this.input); + this.addListeners("#input-wrapper", "dragleave", this.input.inputDragleave, this.input); + this.addListeners("#input-wrapper", "drop", this.input.inputDrop, this.input); + document.getElementById("btn-new-tab").addEventListener("click", this.input.addInputClick.bind(this.input)); + document.getElementById("btn-previous-input-tab").addEventListener("mousedown", this.input.previousTabClick.bind(this.input)); + document.getElementById("btn-next-input-tab").addEventListener("mousedown", this.input.nextTabClick.bind(this.input)); + this.addListeners("#btn-next-input-tab,#btn-previous-input-tab", "mouseup", this.input.tabMouseUp, this.input); + this.addListeners("#btn-next-input-tab,#btn-previous-input-tab", "mouseout", this.input.tabMouseUp, this.input); + document.getElementById("btn-go-to-input-tab").addEventListener("click", this.input.goToTab.bind(this.input)); + document.getElementById("btn-find-input-tab").addEventListener("click", this.input.findTab.bind(this.input)); + this.addDynamicListener("#input-tabs li .input-tab-content", "click", this.input.changeTabClick, this.input); + document.getElementById("input-show-pending").addEventListener("change", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-show-loading").addEventListener("change", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-show-loaded").addEventListener("change", this.input.filterTabSearch.bind(this.input)); + this.addListeners("#input-filter-content,#input-filter-filename", "click", this.input.filterOptionClick, this.input); + document.getElementById("input-filter").addEventListener("change", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-filter").addEventListener("keyup", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-num-results").addEventListener("change", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-num-results").addEventListener("keyup", this.input.filterTabSearch.bind(this.input)); + document.getElementById("input-filter-refresh").addEventListener("click", this.input.filterTabSearch.bind(this.input)); + this.addDynamicListener(".input-filter-result", "click", this.input.filterItemClick, this.input); + + + // Output + document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output)); + document.getElementById("save-all-to-file").addEventListener("click", this.output.saveAllClick.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("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); + document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output)); + this.addDynamicListener(".extract-file,.extract-file i", "click", this.output.extractFileClick, this.output); + this.addDynamicListener("#output-tabs-wrapper #output-tabs li .output-tab-content", "click", this.output.changeTabClick, this.output); + document.getElementById("btn-previous-output-tab").addEventListener("mousedown", this.output.previousTabClick.bind(this.output)); + document.getElementById("btn-next-output-tab").addEventListener("mousedown", this.output.nextTabClick.bind(this.output)); + this.addListeners("#btn-next-output-tab,#btn-previous-output-tab", "mouseup", this.output.tabMouseUp, this.output); + this.addListeners("#btn-next-output-tab,#btn-previous-output-tab", "mouseout", this.output.tabMouseUp, this.output); + document.getElementById("btn-go-to-output-tab").addEventListener("click", this.output.goToTab.bind(this.output)); + document.getElementById("btn-find-output-tab").addEventListener("click", this.output.findTab.bind(this.output)); + document.getElementById("output-show-pending").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-show-baking").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-show-baked").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-show-stale").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-show-errored").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-content-filter").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-content-filter").addEventListener("keyup", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-num-results").addEventListener("change", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-num-results").addEventListener("keyup", this.output.filterTabSearch.bind(this.output)); + document.getElementById("output-filter-refresh").addEventListener("click", this.output.filterTabSearch.bind(this.output)); + this.addDynamicListener(".output-filter-result", "click", this.output.filterItemClick, 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]#wordWrap", "change", this.options.setWordWrap, this.options); + this.addDynamicListener(".option-item input[type=checkbox]#useMetaKey", "change", this.bindings.updateKeybList, this.bindings); + this.addDynamicListener(".option-item input[type=checkbox]#showCatCount", "change", this.ops.setCatCount, this.ops); + 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 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 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 (Object.prototype.hasOwnProperty.call(this.dynamicHandlers, 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 997c4ff8..00000000 --- a/src/web/OperationsWaiter.js +++ /dev/null @@ -1,287 +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. - */ -var 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) { - var 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 { - var searchResultsEl = document.getElementById("search-results"), - el = e.target, - str = el.value; - - while (searchResultsEl.firstChild) { - $(searchResultsEl.firstChild).popover("destroy"); - searchResultsEl.removeChild(searchResultsEl.firstChild); - } - - $("#categories .in").collapse("hide"); - if (str) { - var matchedOps = this.filterOperations(str, true), - matchedOpsHtml = ""; - - for (var i = 0; i < matchedOps.length; i++) { - matchedOpsHtml += matchedOps[i].toStubHtml(); - } - - 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(searchStr, highlight) { - var matchedOps = [], - matchedDescs = []; - - searchStr = searchStr.toLowerCase(); - - for (var opName in this.app.operations) { - var op = this.app.operations[opName], - namePos = opName.toLowerCase().indexOf(searchStr), - descPos = op.description.toLowerCase().indexOf(searchStr); - - if (namePos >= 0 || descPos >= 0) { - var 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 (var 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) { - var 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 - var favCat = this.app.categories.filter(function(c) { - return c.name === "Favourites"; - })[0]; - - var html = ""; - for (var i = 0; i < favCat.ops.length; i++) { - var opName = favCat.ops[i]; - var operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); - html += operation.toStubHtml(true); - } - - var editFavouritesList = document.getElementById("edit-favourites-list"); - editFavouritesList.innerHTML = html; - this.removeIntent = false; - - var editableList = Sortable.create(editFavouritesList, { - filter: ".remove-icon", - onFilter: function (evt) { - var el = editableList.closest(evt.item); - if (el) { - $(el).popover("destroy"); - el.parentNode.removeChild(el); - } - }, - onEnd: function(evt) { - if (this.removeIntent) 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() { - var favouritesList = [], - favs = document.querySelectorAll("#edit-favourites-list li"); - - for (var i = 0; i < favs.length; i++) { - favouritesList.push(favs[i].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) { - var 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) { - var opEl = e.target.parentNode, - 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/OptionsWaiter.js b/src/web/OptionsWaiter.js deleted file mode 100755 index d7c89eb6..00000000 --- a/src/web/OptionsWaiter.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Waiter to handle events related to the CyberChef options. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - */ -var OptionsWaiter = function(app) { - this.app = app; -}; - - -/** - * Loads options and sets values of switches and inputs to match them. - * - * @param {Object} options - */ -OptionsWaiter.prototype.load = function(options) { - $(".option-item input:checkbox").bootstrapSwitch({ - size: "small", - animate: false, - }); - - for (var option in options) { - this.app.options[option] = options[option]; - } - - // Set options to match object - var cboxes = document.querySelectorAll("#options-body input[type=checkbox]"); - for (var i = 0; i < cboxes.length; i++) { - $(cboxes[i]).bootstrapSwitch("state", this.app.options[cboxes[i].getAttribute("option")]); - } - - var nboxes = document.querySelectorAll("#options-body input[type=number]"); - for (i = 0; i < nboxes.length; i++) { - nboxes[i].value = this.app.options[nboxes[i].getAttribute("option")]; - nboxes[i].dispatchEvent(new CustomEvent("change", {bubbles: true})); - } - - var selects = document.querySelectorAll("#options-body select"); - for (i = 0; i < selects.length; i++) { - selects[i].value = this.app.options[selects[i].getAttribute("option")]; - selects[i].dispatchEvent(new CustomEvent("change", {bubbles: true})); - } -}; - - -/** - * Handler for options click events. - * Dispays the options pane. - */ -OptionsWaiter.prototype.optionsClick = function() { - $("#options-modal").modal(); -}; - - -/** - * Handler for reset options click events. - * Resets options back to their default values. - */ -OptionsWaiter.prototype.resetOptionsClick = function() { - this.load(this.app.doptions); -}; - - -/** - * Handler for switch change events. - * Modifies the option state and saves it to local storage. - * - * @param {event} e - * @param {boolean} state - */ -OptionsWaiter.prototype.switchChange = function(e, state) { - var el = e.target, - option = el.getAttribute("option"); - - this.app.options[option] = state; - localStorage.setItem("options", JSON.stringify(this.app.options)); -}; - - -/** - * Handler for number change events. - * Modifies the option value and saves it to local storage. - * - * @param {event} e - */ -OptionsWaiter.prototype.numberChange = function(e) { - var el = e.target, - option = el.getAttribute("option"); - - this.app.options[option] = parseInt(el.value, 10); - localStorage.setItem("options", JSON.stringify(this.app.options)); -}; - - -/** - * Handler for select change events. - * Modifies the option value and saves it to local storage. - * - * @param {event} e - */ -OptionsWaiter.prototype.selectChange = function(e) { - var el = e.target, - option = el.getAttribute("option"); - - this.app.options[option] = el.value; - localStorage.setItem("options", JSON.stringify(this.app.options)); -}; - - -/** - * Sets or unsets word wrap on the input and output depending on the wordWrap option value. - */ -OptionsWaiter.prototype.setWordWrap = function() { - document.getElementById("input-text").classList.remove("word-wrap"); - document.getElementById("output-text").classList.remove("word-wrap"); - document.getElementById("output-html").classList.remove("word-wrap"); - document.getElementById("input-highlighter").classList.remove("word-wrap"); - document.getElementById("output-highlighter").classList.remove("word-wrap"); - - if (!this.app.options.wordWrap) { - document.getElementById("input-text").classList.add("word-wrap"); - document.getElementById("output-text").classList.add("word-wrap"); - document.getElementById("output-html").classList.add("word-wrap"); - document.getElementById("input-highlighter").classList.add("word-wrap"); - document.getElementById("output-highlighter").classList.add("word-wrap"); - } -}; - -export default OptionsWaiter; diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js deleted file mode 100755 index 90f297d3..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. - */ -var 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) { - var outputText = document.getElementById("output-text"), - outputHtml = document.getElementById("output-html"), - outputHighlighter = document.getElementById("output-highlighter"), - 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 - var scriptElements = outputHtml.querySelectorAll("script"); - for (var 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(); - var 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) { - var width = length.toString().length; - width = width < 4 ? 4 : width; - - var lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " "); - var linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " "); - var 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() { - var output = document.getElementById("output"), - saveToFile = document.getElementById("save-to-file"), - switchIO = document.getElementById("switch"), - undoSwitch = document.getElementById("undo-switch"), - 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() { - var data = Utils.toBase64(this.app.dishStr), - filename = window.prompt("Please enter a filename:", "download.dat"); - - if (filename) { - var 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) { - var 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/RecipeWaiter.js b/src/web/RecipeWaiter.js deleted file mode 100755 index 2829ccdc..00000000 --- a/src/web/RecipeWaiter.js +++ /dev/null @@ -1,428 +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. - */ -var 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() { - var 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) - }); - - 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) { - var loc = e.changedTouches[0], - 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 the 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) { - $(evt.item).popover("destroy"); - 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"); - - var 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) { - var 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) { - var 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() { - var config = [], ingredients, ingList, disabled, bp, item, - operations = document.querySelectorAll("#rec-list li.operation"); - - for (var i = 0; i < operations.length; i++) { - ingredients = []; - disabled = operations[i].querySelector(".disable-icon"); - bp = operations[i].querySelector(".breakpoint"); - ingList = operations[i].querySelectorAll(".arg"); - - for (var 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) { - var operations = document.querySelectorAll("#rec-list li.operation"); - for (var 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) { - var opName = el.textContent; - var 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) { - var 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() { - var 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) { - var el = e.target, - 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/SeasonalWaiter.js b/src/web/SeasonalWaiter.js deleted file mode 100755 index ad897b63..00000000 --- a/src/web/SeasonalWaiter.js +++ /dev/null @@ -1,154 +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. - */ -var SeasonalWaiter = function(app, manager) { - this.app = app; - this.manager = manager; -}; - - -/** - * Loads all relevant items depending on the current date. - */ -SeasonalWaiter.prototype.load = function() { - //var now = new Date(); - - // SpiderChef - // if (now.getMonth() === 3 && now.getDate() === 1) { // Apr 1 - // this.insertSpiderIcons(); - // this.insertSpiderText(); - // } - - // Konami code - this.kkeys = []; - window.addEventListener("keydown", this.konamiCodeListener.bind(this)); -}; - - -/** - * Replaces chef icons with spider icons. - * #spiderchef - */ -SeasonalWaiter.prototype.insertSpiderIcons = function() { - var spider16 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB3UlEQVQ4y2NgGJaAmYGBgVnf0oKJgYGBobWtXamqqoYTn2I4CI+LTzM2NTulpKbu+vPHz2dV5RWlluZmi3j5+KqFJSSEzpw8uQPdAEYYIzo5Kfjrl28rWFlZzjAzMYuEBQao3Lh+g+HGvbsMzExMDN++fWf4/PXLBzY2tqYNK1f2+4eHM2xcuRLigsT09Igf3384MTExbf767etBI319jU8fPsi+//jx/72HDxh5uLkZ7ty7y/Dz1687Avz8n2UUFR3Z2NjOySoqfmdhYGBg+PbtuwI7O8e5H79+8X379t357PnzYo+ePP7y6cuXc9++f69nYGRsvf/w4XdtLS2R799/bBUWFHr57sP7Jbs3b/ZkzswvUP3165fZ7z9//r988WIVAyPDr8tXr576+u3bpb9//7YwMjKeV1dV41NWVGoVEhDgPH761DJREeHaz1+/lqlpafUx6+jrRfz4+fPy+w8fTu/fsf3uw7t3L39+//4cv7DwGQYGhpdPbt9m4BcRFlNWVJC4fuvWASszs4C379792Ldt2xZBUdEdDP5hYSqQGIjDGa965uYKCalpZQwMDAxhMTG9DAwMDLaurhIkJY7A8IgGBgYGBgd3Dz2yUpeFo6O4rasrA9T24ZRxAAMTwMpgEJwLAAAAAElFTkSuQmCC", - spider32 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAACYVBMVEUAAAAcJSU2Pz85QkM9RUWEhIWMjI2MkJEcJSU2Pz85QkM9RUWWlpc9RUVXXl4cJSU2Pz85QkM8REU9RUVRWFh6ens9RUVCSkpNVFRdY2McJSU5QkM7REQ9RUVGTk5KUlJQVldcY2Rla2uTk5WampscJSVUWltZX2BrcHF1e3scJSUjLCw9RUVASEhFTU1HTk9bYWJeZGRma2xudHV1eHiZmZocJSUyOjpJUFFQVldSWlpTWVpXXl5YXl5rb3B9fX6RkZIcJSUmLy8tNTU9RUVFTU1IT1BOVldRV1hTWlp0enocJSUfKChJUFBWXV1hZ2hnbGwcJSVETExLUlJLU1NNVVVPVlZYXl9cY2RiaGlobW5rcXFyd3h0eHgcJSUpMTFDS0tQV1dRV1hSWFlWXF1bYWJma2tobW5uc3SsrK0cJSVJUFBMVFROVlZVW1xZX2BdYmNhZ2hjaGhla2tqcHBscHE4Pz9KUlJRWVlSWVlXXF1aYGFbYWFfZWZlampqbW4cJSUgKSkiKysuNjY0PD01PT07QkNES0tHTk5JUFBMUlNMU1NOU1ROVVVPVVZRVlZRV1dSWVlWXFxXXV5aX2BbYWFbYWJcYmJcYmNcY2RdYmNgZmZhZmdkaWpkampkamtlamtla2tma2tma2xnbG1obW5pbG1pb3Bqb3Brb3BtcXJudHVvcHFvcXJvc3NwcXNwdXVxc3RzeXl1eXp2eXl3ent6e3x+gYKAhISBg4SKi4yLi4yWlpeampudnZ6fn6CkpaanqKiur6+vr7C4uLm6urq6u7u8vLy9vb3Av8DR0dL2b74UAAAAgHRSTlMAEBAQEBAQECAgICAgMDBAQEBAQEBAUFBQUGBgYGBgYGBgYGBgcHBwcHCAgICAgICAgICAgICPj4+Pj4+Pj4+Pj5+fn5+fn5+fn5+vr6+vr6+/v7+/v7+/v7+/v7+/z8/Pz8/Pz8/Pz8/P39/f39/f39/f39/f7+/v7+/v7+/v78x6RlYAAAGBSURBVDjLY2AYWUCSgUGAk4GBTdlUhQebvP7yjIgCPQbWzBMnjx5wwJSX37Rwfm1isqj9/iPHTuxYlyeMJi+yunfptBkZOw/uWj9h3vatcycu8eRGlldb3Vsts3ph/cFTh7fN3bCoe2Vf8+TZoQhTvBa6REozVC7cuPvQnmULJm1e2z+308eyJieEBSLPXbKQIUqQIczk+N6eNaumtnZMaWhaHM89m8XVCqJA02Y5w0xmga6yfVsamtrN4xoXNzS0JTHkK3CXy4EVFMumcxUy2LbENTVkZfEzMDAudtJyTmNwS2XQreAFyvOlK9louDNVaXurmjkGgnTMkWDgXswtNouFISEX6Awv+RihQi5OcYY4DtVARpCCFCMGhiJ1hjwFBpagEAaWEpFoC0WQOCOjFMRRwXYMDB4BDLJ+QLYsg7GBGjtasLnEMjCIrWBgyAZ7058FI9x1SoFEnTCDsCyIhynPILYYSFgbYpUDA5bpQBluXzxpI1yYAbd2sCMYRhwAAHB9ZPztbuMUAAAAAElFTkSuQmCC", - spider64 = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAJZUlEQVR42u1ZaXMU1xXlJ+gHpFITOy5sAcnIYCi2aIL2bTSSZrSP1NpHK41kISQBHgFaQIJBCMwi4TFUGYcPzggwEMcxHVGxQaag5QR/np/QP+Hmnsdr0hpmtEACwulb9aq7p7d3zz333Pt61q2zzTbbbLPNNttss80222yzzTbbVmu7MzKcJRWVkXjntqam6jyURPeGQqeTpqbOqp+evxC5dGlam5m5rE3PzGi8Hzx/4aLzbXDe09HdYxwZHaPc4mLFXVoW9pRXGNv3pDngeHlNLfE2Ljjj4xPOUGjSYKfpq6/+TLdv36bbX39Nt27epGvXvqSLl6bp3LlPtdOnz7jWrPNZ7kLCKCovp5bOTmP/4EHq6vmYMtzuSKbbbQCAHE8Rxd47MjrmuHjxkjF3/z4tLCzQkyc6PX78mB49ekQPHjygub/P0d27f6FrX/6JpqbO0YkT48E1R/sCr9cYHZ+gqrp64mPq+riXcoqKKC0vP9q6VyV/fQOiH+LrsPVY7z82PBKZnb1Bd+7cpfn5eQbgCT1hAADC/MN5uj83R99881eanZ2lL5gN/nrxjihAXwvOJ7l9vuiBQ4dF9LEtLC0V+2rv/ijTX6luaCS3rxT57wADAMTBQ4c9PIIDg4PBwYOHaHhklM5MnSWkwLff/o0+v3qVHv34Iz344QEDc4d8VVXUEAhQXXMzVdQqzKweKq6oABARzOGNOZ+Wl6fD6T25ubQrPT0E5xF93o82tbdjkkZ+iZfAAgbD6fZ6o339A8S0p7HjJ2h4eIQOHf6EujlV9nX3UOj0JDXzfXje+KlTdOPGDeF0T1+fGHg+2JSen08tHZ0CiPySEoPn8vq1IaOgIAzneQK0UzjcQd6qaqrlCVfV1+tpubnRnv5+2p2ZqYMF/oZGPTh0xLhy5Sr9wLn9j++/p5nLn9FxBoLZQJ1dKrkys6iYNeTExEnx3PqWFuF4W9deKq2upkEGCyzyMBC709MFC7r391Fjayv9MSdHZyCU1xJ5FjrNdN6VnU1KS4CjU4Yoh/m8CsezCguFJgAMV05ueP+BfhF5OL+gL9A/f/qJ7t3TaPLMFB09eoy6mTkMGg2PjTELOsS20OcTACgMKqJugqA0NtE7ycn0202b6A+ZmYIVAAKApGZlgRHB/0lqQPAqFEVE9hntM0R0ZblTzeswWdCeU8HAtYW+Uu0AUx+0f/jwoXD+56c/073v7tHU2XMiFbrUfVTNAtfL10FIAQL2QftsBrOEnavld5kg7E7PoF+99x79ev162rJrV9RMi6a2dvKUlQsR5uAgII7/ivMsbEE4g2hggjzC7LQL1OftovoO0WJKUn0gYEAn2hmMXo4QHIXQIfLfsfOXPwuLvB86cpQqamooyEzg1BLMwv04RkoE+B3B4BBBMHEcCwIP0N+ByJdUVhpgBJ7j4WvdANDjeTUglOaWEChfJF7uJzPX2HEPaj1vg7EAbHO5QnAeIPgqKvUB7gtAdbBgcvKMqOnc/NAIVwCcq21qElFnCgvaI9cBBFKhlSPbPzBIbbzduGULpWzfLkDAdZs++sgEwSlZqoIJMg2CzFSNGzODwdBfOi26+w4YTCm9LhDQwQDzdzguFf4FALjciTws8/u1yyx2N2/dovPnL9DRY8PkZ204xtuhoSM0wI7V8DEiirQCCHD+99u2CUdx3Lmvmz7kfemoGDgPEDr4HNKAf1MlAC4wgMGLWFJXQUrklZSEX6rLE2rOyDIQGlhgBUAyYFEZkm2vAGVi4qQ+x83M0389pevXr6OToy07d4qcR+krr/KzqpeJ/IfjGO+npDx3FCKHVPjd1q2LAMBI3ryZ9vL7U56BEzLfD80ACFba876OlGCQV9dAcT0Pyw7PgWij6zPP5Xt9EYgg+n3LosdVzdfz5CI8KY1LH31+5Yro9KanZwjHmPzmHTsoOeVDemfDBuE8dGVnWpqx3unUrE4CDLCAG64XAHB88IFgQV5xMY7DFmc16A6CZvnNBYYVcW+yKj0A/VHTsQ8dwMPNc6X+Gg0VIGbVpzYGWundjRujmGQWi9Eol7+TJ0/R2Nhx2sNlM9YJRPDdDRsM5DGPJB4KHOIhngHhAwixAGAAuDZ2lsuiYnFWBQOYrdEYNochilyiV6YHoH+rRNJkAG+fUw31PzU7Z1EFKPD69CIuQ1Bm6URoh8tFmVym3nc6rZOPyi0cD8HxeHPg3x2InNrbS79JTsYzNXmPuBclsO3ZvKwAOJEGsmI5rT0M+gSf3y9K5LIA1LUEIlL1k0AhCYBH5r9TCqBqib4D+c/1PyInGOThkvuaHCYALhlpbQWBMGR/4IpzTqlpbKQyf0045vdoe0zATHagSYMeWFMkbscnHRYPZjoFJaIiUkz9EJy15j/X3qCsAIqMcFjSWrNE1Iygg0fEmrtLzEUTdT/OhBFht9fHDVCbEUt3LJxi08B8Xj6vTDESriq9lVWqBECgHujqiqAUmufb1X3cfRXoluhjZWiwkOnSUcUS6ZD8LUmmhks6b5j1ezkAkAKZBe5QvPPcNBnoCawMwT66Qxk0R2xwwRAui2iSDGuaPDcubzo3EJq8wcx/9Vmk3QryH42QBQCFF0UagIiJtjX6DskIXTLEucJSHIIIMuO0BOcjn3A3ybU/lu5RCUBc5qA0Ih0Q2EWiCPRk7VfMNhjLW1zETic1tLYZDMKyuSsdfh5l6bwho5+0il4kyA0VohlNcF5FP8DlWo/VB16HYB2hJ0pzgIe2mcXxP2IOumPRY17U0tll8KIkZNb+sppafOxYkQPSaYfchyYoL9GMqWYpTLRIq1QUcT4O3aPQgqVqPwIOIMwDhzX6mQUFIQAgo+9MzcrWrML3mj6+YIKiFCZyhL87RqVQKrEskF+P1BUvfLCAkfRwoPUtq6l5o5+lZb5SolJo6oT8avTCl+c9OTmat6pKW8mLkvBpGzlvsiGuQr4ZEEwA1EQgoR/gNtxIxKBluz+OtMJiF31jHxqXBiAqAUj4WRxpADFM0DCFlv1khvX7Wol4vF4AIldVVxdZqlrIfiCYQPHDy6bAGv7nKYRVY6JewExZVAP+ey5Rv+Ba97aaUHMW5NauLmMZFkegBb/EP14d6NoS9QLWFSzWBmuZza8CQmSpXsAqmGtVy14VALWuuYWWy+W3OteXa4jwceQX6+BKG6J1/8+2VCNkm2222WabbbbZZpttttlmm22rt38DCdA0vq3bcAkAAAAASUVORK5CYII="; - - // Favicon - document.querySelector("link[rel=icon]").setAttribute("href", "data:image/png;base64," + spider16); - - // Bake button - document.querySelector("#bake img").setAttribute("src", "data:image/png;base64," + spider32); - - // About box - document.querySelector(".about-img-left").setAttribute("src", "data:image/png;base64," + spider64); -}; - - -/** - * Replaces all instances of the word "cyber" with "spider". - * #spiderchef - */ -SeasonalWaiter.prototype.insertSpiderText = function() { - // Title - document.title = document.title.replace(/Cyber/g, "Spider"); - - // Body - SeasonalWaiter.treeWalk(document.body, function(node) { - // process only text nodes - if (node.nodeType === 3) { - node.nodeValue = node.nodeValue.replace(/Cyber/g, "Spider"); - } - }, true); - - // Bake button - SeasonalWaiter.treeWalk(document.getElementById("bake-group"), function(node) { - // process only text nodes - if (node.nodeType === 3) { - node.nodeValue = node.nodeValue.replace(/Bake/g, "Spin"); - } - }, true); - - // Recipe title - document.querySelector("#recipe .title").innerHTML = "Web"; -}; - - -/** - * 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); - var konami = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; - for (var 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 = []; - } - } -}; - - -/** - * Walks through the entire DOM starting at the specified element and operates on each node. - * - * @static - * @param {element} parent - The DOM node to start from - * @param {Function} fn - The callback function to operate on each node - * @param {booleam} allNodes - Whether to operate on every node or not - */ -SeasonalWaiter.treeWalk = (function() { - // Create closure for constants - var skipTags = { - "SCRIPT": true, "IFRAME": true, "OBJECT": true, - "EMBED": true, "STYLE": true, "LINK": true, "META": true - }; - - return function(parent, fn, allNodes) { - var node = parent.firstChild; - - while (node && node !== parent) { - if (allNodes || node.nodeType === 1) { - if (fn(node) === false) { - return false; - } - } - // If it's an element && - // has children && - // has a tagname && is not in the skipTags list - // then, we can enumerate children - if (node.nodeType === 1 && - node.firstChild && - !(node.tagName && skipTags[node.tagName])) { - node = node.firstChild; - } else if (node.nextSibling) { - node = node.nextSibling; - } else { - // No child and no nextsibling - // Find parent that has a nextSibling - while ((node = node.parentNode) !== parent) { - if (node.nextSibling) { - node = node.nextSibling; - break; - } - } - } - } - }; -})(); - -export default SeasonalWaiter; diff --git a/src/web/WindowWaiter.js b/src/web/WindowWaiter.js deleted file mode 100755 index d85ee6b6..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. - */ -var 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() { - var unfocusedTime = new Date().getTime() - this.windowBlurTime; - if (unfocusedTime > 60000) { - this.app.silentBake(); - } -}; - -export default WindowWaiter; diff --git a/src/web/css/index.js b/src/web/css/index.js deleted file mode 100644 index 6c76009a..00000000 --- a/src/web/css/index.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * CSS index - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - */ - -import "google-code-prettify/src/prettify.css"; - -import "./lib/bootstrap.less"; -import "bootstrap-switch/src/less/bootstrap3/build.less"; -import "bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css"; - -import "./structure/overrides.css"; -import "./structure/layout.css"; -import "./structure/utils.css"; -import "./themes/classic.css"; diff --git a/src/web/css/lib/bootstrap.less b/src/web/css/lib/bootstrap.less deleted file mode 100644 index 12637095..00000000 --- a/src/web/css/lib/bootstrap.less +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Bootstrap imports - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - */ - -// Core variables and mixins -@import "~bootstrap/less/variables.less"; -@import "~bootstrap/less/mixins.less"; - -// Reset and dependencies -@import "~bootstrap/less/normalize.less"; -@import "~bootstrap/less/print.less"; -// @import "~bootstrap/less/glyphicons.less"; - -// Core CSS -@import "~bootstrap/less/scaffolding.less"; -@import "~bootstrap/less/type.less"; -@import "~bootstrap/less/code.less"; -// @import "~bootstrap/less/grid.less"; -@import "~bootstrap/less/tables.less"; -@import "~bootstrap/less/forms.less"; -@import "~bootstrap/less/buttons.less"; - -// Components -@import "~bootstrap/less/component-animations.less"; -@import "~bootstrap/less/dropdowns.less"; -@import "~bootstrap/less/button-groups.less"; -@import "~bootstrap/less/input-groups.less"; -@import "~bootstrap/less/navs.less"; -// @import "~bootstrap/less/navbar.less"; -// @import "~bootstrap/less/breadcrumbs.less"; -// @import "~bootstrap/less/pagination.less"; -// @import "~bootstrap/less/pager.less"; -@import "~bootstrap/less/labels.less"; -// @import "~bootstrap/less/badges.less"; -// @import "~bootstrap/less/jumbotron.less"; -// @import "~bootstrap/less/thumbnails.less"; -@import "~bootstrap/less/alerts.less"; -@import "~bootstrap/less/progress-bars.less"; -// @import "~bootstrap/less/media.less"; -@import "~bootstrap/less/list-group.less"; -@import "~bootstrap/less/panels.less"; -// @import "~bootstrap/less/responsive-embed.less"; -// @import "~bootstrap/less/wells.less"; -@import "~bootstrap/less/close.less"; - -// Components w/ JavaScript -@import "~bootstrap/less/modals.less"; -@import "~bootstrap/less/tooltip.less"; -@import "~bootstrap/less/popovers.less"; -// @import "~bootstrap/less/carousel.less"; - -// Utility classes -@import "~bootstrap/less/utilities.less"; -// @import "~bootstrap/less/responsive-utilities.less"; \ No newline at end of file diff --git a/src/web/css/structure/layout.css b/src/web/css/structure/layout.css deleted file mode 100755 index 8286eb82..00000000 --- a/src/web/css/structure/layout.css +++ /dev/null @@ -1,432 +0,0 @@ -#content-wrapper { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -#banner { - position: absolute; - height: 30px; - width: 100%; - text-align: center; - line-height: 30px; -} - -#wrapper { - position: absolute; - top: 30px; - bottom: 0; - width: 100%; -} - -div#operations, -div#recipe { - width: 50%; - height: 100%; -} - -div#input, -div#output { - width: 100%; - height: 50%; -} - -.title { - padding: 10px; - height: 43px; -} - -.textarea-wrapper { - position: absolute; - top: 43px; - bottom: 0; - width: 100%; - overflow: hidden; -} - -textarea, -#output-html { - width: 100%; - height: 100%; - border: none; - padding: 3px; - -moz-padding-start: 3px; - -moz-padding-end: 3px; -} - -#input-text, -#output-text, -#output-html { - position: relative; - border-width: 0px; - margin: 0; - resize: none; - background-color: transparent; - white-space: pre-wrap; - word-wrap: break-word; -} - -#output-html { - display: none; - overflow-y: auto; - -moz-padding-start: 1px; /* Fixes bug in Firefox */ -} - -.split { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - overflow: auto; - position: relative; -} - -.split.split-horizontal, .gutter.gutter-horizontal { - height: 100%; - float: left; -} - -#input-highlighter, -#output-highlighter { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - padding: 3px; - margin: 0; - overflow: hidden; - letter-spacing: normal; - white-space: pre-wrap; - word-wrap: break-word; - color: #fff; - background-color: transparent; - border: none; -} - -#op-list, -#rec-list { - position: absolute; - top: 43px; - bottom: 0; - width: 100%; - list-style-type: none; - margin: 0; - padding: 0; -} - -.op-list { - list-style-type: none; - margin: 0; - padding: 0; -} - -#rec-list { - bottom: 120px; /* Linked to #controls height */ - overflow: auto; -} - -.operation { - cursor: pointer; - padding: 10px; - list-style-type: none; - position: relative; -} - -#controls { - position: absolute; - width: 100%; - height: 120px; /* Linked to #rec-list bottom */ - bottom: 0; - padding: 10px; -} - -.io-btn-group { - float: right; - margin-top: -4px; -} - -.io-info { - margin-right: 20px; - margin-top: -4px; - float: right; - height: 30px; - text-align: right; - line-height: 10px; -} - -#input-info { - line-height: 15px; -} - -.arg-group { - display: table; - width: 100%; - margin-top: 10px; -} - -.arg-group-text { - display: block; -} - -.inline-args { - float: left; - width: auto; - margin-right: 30px; - height: 34px; -} - -.inline-args input[type="checkbox"] { - margin-top: 10px; -} - -.inline-args input[type="number"] { - width: 100px; -} - -.arg-input { - display: table-cell; - width: 100%; - padding: 6px 12px; - vertical-align: middle; -} - -.short-string { - width: 150px; -} - -select { - display: block; - vertical-align: middle; -} - -.arg[disabled] { - cursor: not-allowed; - opacity: 1; -} - -textarea.arg { - width: 100%; - min-height: 50px; - height: 70px; - margin-top: 5px; - border: 1px solid #ddd; - resize: vertical; -} - -.arg-label { - display: table-cell; - width: 1px; - padding-right: 10px; - font-weight: normal; - vertical-align: middle; - white-space: pre; -} - -.editable-option { - position: relative; - display: inline-block; -} - -.editable-option-select { - min-width: 250px; -} - -.editable-option-input { - position: absolute; - top: 1px; - left: 1px; - width: calc(100% - 20px); - height: calc(100% - 2px) !important; - border: none !important; -} - -#operational-controls { - width: 65%; /* Linked to #extra-controls width */ - float: left; - text-align: center; -} - -#bake-group { - display: table; - width: 100%; -} - -#bake { - display: table-cell; - width: 100%; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -#auto-bake-label { - display: table-cell; - padding: 1px; - line-height: 1.35; - width: 60px; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: 1px solid #5cb85c; -} - -#auto-bake-label:hover { - border-left-color: #398439; -} - -#auto-bake-label div { - font-size: 10px; - padding: 2px; -} - -#extra-controls { - float: right; - width: 35%; /* Linked to #operational-controls width */ - padding-left: 10px; -} - -.op-icon { - float: right; - margin-left: 10px; - margin-top: 3px; -} - -.recip-icons { - position: absolute; - top: 13px; - right: 10px; - height: 16px; -} - -.recip-icon { - margin-right: 10px; - vertical-align: baseline; - float: right; -} - -.disable-icon { - width: 16px; - height: 16px; - margin-top: -1px; - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAACfElEQVQ4y6WTPWgTYRjHf8nlfVvuoDVIP4Yuki4WHKoUqggFRUTsUEGkVG2hmCq6OnTwIxYHB+eijZOKdLNDW1pKKyGigh8dBHUJElxyBgx3vEnukvdyDrUhRXDxGR+e/+/583xEwjDkfyIGwNVTzURm4tYAMA6MAoN/0tvAMrA48uL+l2bx4w0iYRjSuHKC6OnTZLqHk8CcaZq9bW1tSCkBqNVq+L5PpVIpAHdGfr5LN9bXiT7Z2nGgteb1/qFkLBJZ6OjowHEc8vk8pVIJgHg8TldXF52dnb2u6y5s7R/iuF5JSyAKkLl4eyAMwznLsrBtm1wu99Z13amk+BFJih8R13WXANrb27EsizAM5zIXbw+wC9Baj0spe5VSFAqFt4ZhXJ6ufXuK55E5cDKVSCTGenp6yGazKKWQUvZqrcebgCAIRqWUOI6DEOLR1K8POapVMgfPpoC7u2LLspYcx0FKSRAEo60OBg3DwPd9Jr5vPqWvj8zh83vEwL2J75vnfN/HMAy01oPNNQZBQBAEO1OvVsl0D/8lTuZfpYDd7gRBQKuD7XK5jGmarB679PIv8deVFJUKq8cuTZqmSblcRmu93QpYVkohhMCyrLE94n2/UlSrbJy5kRBCXBNCoJRCa73cClh0XbfgeR6WZZHNZunv719KvnmeYnWVVxdmJ2Ox2DMhxFHP83Bdt6C1XgR2LvHzQDvvb84npZQL8Xgc0zSJRqN7br7RaFCpVCiVStRqtZmhh9fTh754TQdMr82nPc+bsW27UCwWUUpRr9ep1+sopSgWi9i2XfA8b2Z6bT6ttabp4GMi0uz0aXbhn890+MFM85mO5MIdwP/Eb1pMUCdctYRzAAAAAElFTkSuQmCC') no-repeat; -} - -.disable-icon-selected { - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACFUlEQVR4XqWTP0tbURjGn9zY3mjBwsUhBQtS6XKxiNypIGZJ6SKYUYdaKWg7OrrE3pYO+Qit3dpFuuQO6lI7Z4nESQdjlJbkJh0MksSb3Jvk9H0gjZFu9YWH83LO7zn/3nNCSincJobAeP1sEDBFi6J50UyPy4l2RNuioz756Ts0tt1OB4jH2a52Ne2HGh9PwrJm2EcxZx/HyPRYMDgB2u02/N3d1c7w8BZMM1ptNJBPp3GwsUExB/s4RoYsPf0JOkFgdoH34YkJ/D48xC/HyTTOzl5ayWSIktwxqlVo0SjIkKWnP0Hg+4swjGitVMJFNpu5o+svptfXv6DZBDIZezoWS3Db3A0ZsvRcH8H354dGR9EoFHA3EvlorqycwvOAXM4G8Pav+f7YmEOGLD1gsIzl54+V+vBK/Yw9ZAv1LQW1FrdFSnKVfQTK5liPUfRI9I8ArqiPjLAF9vcHVybyzlpasgcZeq7voNXKNSsV3DMMXB4fp/8xLyzYuLri2DIZsvQM3sFOzXURiUR4zsQNcyrFleFVKpNyP2/IkKVnsArbF65bbkqplJSJZrl5x5qbs7G3h3artSyV+arr+lMyZOnpP2Wp6ZFos3R+vvUgCGDNzgKalkA4rECIr07662J2i0X4nrfJJ33jJT6Zmvpcr9XWCicn5WI+j7rrAmKgmLOPY2TI0sPgb8TBZOi/PpN1qnDr7/wH3jxgB/FKIXkAAAAASUVORK5CYII=') no-repeat; -} - -.breakpoint { - float: right; - width: 14px; - height: 14px; - background-color: #eee; - border: 1px solid #aaa; -} - -.breakpoint-selected { - background: #eee url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAXVBMVEXIUkvzUVHzTEzzn5785eXrbW24BgbzWVnze3vzVVXzY2Pyion509PzbW3zXV1UMxj0l5f1srKbRTRgOxzJDg796ur74ODfIyP5zs6LLx3pNTXYGxuxdkVZNhn////sCC1eAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAABWSURBVBjTnc+7EoAgDERRTOQVxMEZFAf//z8FjAUFDbfb060QU2FwxngimxnCea3bjegSgz+0tguAfBgIy64QGfZQdg91dgAtqUZgnfz6IacYVWvu2AvR4wNAv0nxrAAAAABJRU5ErkJggg==') no-repeat -2px -2px; -} - -.banner-right { - float: right; - margin-right: 10px; -} - -#banner img { - margin-bottom: 2px; - margin-left: 8px; -} - -.category-title { - display: block; - padding: 10px; -} - -.category { - margin: 0 !important; - border-radius: 0 !important; - border: none; -} - -#search { - border-radius: 0; - border: none; -} - -.loading_file { - background: #f5f5f5 url('data:image/gif;base64,R0lGODlhPAA8APcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQhCAD/ACwAAAAAPAA8AAAI/gD/CRxIsKDBgwgTKlzIsKHDhxAZ9puy5VjEixj/hZsAAECGfhlDFrSl5hPBdCA6dgxSkF26dyIfItox48aXgfk+qASQYiC/dOXKmXMXkyGxJDOS9pA1cMyBjhLUDJQXNOg5fkUV+hqStGaoqY4+dBBEMF7Vcuj2ZVVIpasRfwXrwS14rmq7tQTLzR0oRokWePoa7kt3jh1Igf7mxcMXEp+dx4wJ7sMK8fBAd+aEWoZ4To6Zz3nY4f2HL7NVjMPWfDazpthos1XPqY2oLs5qOeVG/6sbFF3Gcp7l/NL9b945c+j2XuR2Kxxxgf3ubX5OXSG9dsqrG5xXbyGvUqRO/mk/qA+d0HeUDUoDlak9qvEFgVaNh5BW+/ak5sGHzjvo3YPGbHIfKfsNpM5Z+h00Ty6eaHLKOQUaaI45+MyG0DXPRCiZPYFp6OGHBvWTHYj8TPdPP/w0www0IF6GDjqRDdQPMzQy40yLBwZljnLZ1EhjOh/2Y15VRA3kjY/MAOmhP0MGBQ9B/vgoTYvx8OZbQf5I0ww2LQokzzrvWNjlmGSS2U887tjz4TzqrNNdQu3omN5+9Jh2zogC4XPWnQUKeZZoB8FmlZja+VmVOgk1eWWBglKYUD3nnJOch+2gkw6KCvmTD55ldopRPPJQJ89LIe3DmzqchhRnbxnJF1SCh2vd0185RULkz6yAxjprqBflKBSsa7nKJ0bsRLpOQfl06JA/+ExXKaqpLhRdPgWtIyk90cp43FXw+WoOsP/Ig55kppUjm3ZM/plXVZbVc1Y59BS6q4HvDmRqVeYQStytQSkpULlBpWeqOefoYyJx9rwTz2bs1CtZPfp62F+2LfYDD0yeZkxmQAAh+QQhCAD/ACwCAAIAKQAfAAAI/gD/CRxIsKDBgv3q8JF2sKHDhwLNCZkx40g/iBgJInt0i2C7JhQpninIpIWbjAVLrTGT5tBAfUtCzqAysFwMAAAcdEEpcNodM0DbEBsoKAdFIYgGGjKAE0CHdTydyQHKMtdAeqSYKNlEsI+Aph648fz3hyodfwXvoS3ooekViO7WDkx0Z9C8fRnDufDAxJ1DfaMC6yvI7+LYeg/fhcrEuJS8sZD/XePEOFMnbJHHxgNVOVS7zGPdLQZFDTRkdM+gml7N+mA+e3JbP+wmLdo02RBRM9t9G3fDbLt3R8vn+6C44MyiFXe9zRmzafOWN1xnTrr167j9xcber1+5ctWxHwv0/h28+H/ryn+Pjr2d+nL0xPtTf+78P3/oyqkWHxAAIfkEIQgA/wAsBAACAC8AFgAACP4A/wkcSLCgwYH9LnHqdrChw4cN2ckxY6ZOP4gYH2qj9YxgvDwUKTIqCOeKoowQh3HKpOnVwH14Qpr5M1CdlhkzfPRB2TAcqUxAO2EbGEoNxTilBnq6gXOGknc8DXoLBZQltIH3duW5Q4vgpRpNl4yLajBVVVH+CuZLW3BJUzxk7bEdCIvUqnv8MprDsgROPISU9PhqyC+a4bwE+12Meq8gGAgAHsAzeO8Zs8vS8JGF2KsBgM8bDK5rdplZs3WbH+r5/JkDt4L4LF9+Zi/1Qw+sQRy0Z/lZOtsPH12wIAKxwXnm6gGHCE8XveXQDfLTNzd61HbozqGzHlWfPHPlwiRv554xHbvw4veRh9jvHDz05c6tx6huXzvw6DTPz0hP3v6MAQEAIfkEIQgA/wAsCQACAC8AFgAACP4A/wkcSLAgQX+6eqEzyLChw4cC5YHKlGmUP4gYH7LLdo6gvVIUKc4qSCnQqYwQwzVjxszaQH4gQ6Ya+G6QGTNuPKFsCC8aS2bO1g0EtokiqGEDbaW5aebOvJ0G3T37yayjwHzRSpFqRjDWGaZ41EE1SO0ntIsE96EliIepprH61gq8Fo2avn4Z2QXCM4newH6pKC1r2O+cYbwH5WbMVxAQkBk/nhbUZ66cZXT7xkJU1mOG5yQG6VkeXU/zQ0qeP48ruK+yZXP6TD9kkpoJQ8rlzkmW3bBUkSJO+DXMJ48x74fzkNk7zry584GSRj0f262EAQhmzC2cjvEFgO8JACFh5v6wXofv36FYJe+wBnoClfCxh4gDwoRg2eanRFVNYEAAIfkEIQgA/wAsEQACACkAOAAACP4A/wkcSPCfP27e5hVcyLChw3/4njFjFs3fw4sL67GTR1CftIkTsRW8tYoYxoXvyqlUN7DfR5DUBtJjlSmTJ18nB947p7KcOXoDvzWb+CzcQGebamYidS/nP3s8e3IUyA+dtGjmCC5TmqlUPKf/0vU8Z5Fgv7IESyndBVbgOnTo+KF9KG9VqVv4Wvp6JfKiv7kn9xX8BMfMG3ttE19zY6axncRtXzVufIclZKd4Jue5DHbXnDl6+nEGa69a3tGoUzs9VUs1xnFRcAAptM61QywzcuvIZJvhPSW5c8vpzbBLcBuqiDMEA0RIM3DKF57L1S269eu2z03FLhDMhxDFuN//mwGgfAXR1995KF++C3Z968sHgMO9z4byKMT/S/TDzDb9AAYoIEHzqLNOPeLRY45KZGHXDzo9lcOOgxD2ZNl18fRkzmnYtYNOOv3wM+CIysmTzjv6tdMTOtztFKE72LkoFXdiMQhYQfnoA5Y/+KA3kIfq/OXQOuegQ8+NDPVzjjnniAiWOhoqRFA+9zgp0D4LMihYTv5UqNKEA6lzzjnpEFRPhOUAlZOSEW4HT4RlXhmVT1tyGVWcAkE5lo/7LHmOPj7mZM878QQqD5wF7VNPnaq1syQ6SBLnzz2IuRYQACH5BCEIAP8ALBoAAgAgADgAAAj+AP/989fOXT6BCBMqXMhwn7ly5c75Y0ix4j9+6CBCXKdwG7VwFhn6y6gxHcJ81Zgxc+Yt5MJ3Gs29Q2hOpcpo+1wm7DcP3Tl5CcvZZBYNn06F/SYqlGaz21Gd+KpF25ZToD9qyco9ZdhP4a9PmT4d3FqRnKdMaEmRrZgMbdp4aymWclsqLkVpokKZ6mqXYb5x+voKHvyvFzLCCdXxSfNmFDzE/wSZmbxmFuJ8dyZPrgS5kGY0vyD/OwQnTjZ0otst0yq6teuQ6+i5BsSkCTTRXGboJsJ3sLwlunX3QbwPuG4ajSBbQqJbSmtQZgqJe029umtJM3acEv2JAgAAGHqGC6YH4vt3J4jflTePA/KdBN8ZEBONRcSKeuqs648rL93M1Bqhhtg952hUjjsDFqgRUIilo5FEcfmDj3j/tIOOOv4otVU/55hzDj/8vQMiQg49WNVTBvZWj4HlyPaUOiySqGA55pyo00MGjvjPPh2eow+FIbETY0L71GPjUzNqSFg/8PyHWEAAIfkEIQgA/wAsJAAEABYALgAACP4A//1jl+6dwIMIEx7kl65cOXPuFEo8KM+hw3P8JkqMZ7Ecun0aJZ6z2C6kxH3pzrHrd9BfOnLyTP5jifDbM2bPMso8GM8Zs5/Rdh4k9xMoPqECoxWVhlQgOmjQpPlrKpBfPJ1Us4acpi1rPFSbPgWr13RVprOcmCHdR+rsWVxNXbnVRI3qq0+gzBlsOo9bRK2AAyeEd0/rJzx5tlElZKbxHJo76+Fp3LgTUn6TKadqCstOYz9ZcTEalU6w6dM7T3ERA7eprCEzZhiBLNMek9ix4yCV1wT3jC9NJemI3QMaVT1OrNj7i7r5wUMx0mSNUgBAgBFNcWUAwN2AGaSxMBdwB2AAUVMY4zfQTugP33ooIWbwm6owIAAh+QQhCAD/ACwkAAkAFgAvAAAI/gD/7Ut3jl2/fwj9zYuHD6HDhw4PPnRnrpw5iRAzOsRXsVy5cxpD/ovn0eO5fSI1niuJLqXGeefMofPnUmO/exhr6twJER07ng7vTWvmDJw+oNWYKW1Wjme/aEqVbgNqLSqzdED/XXv2TN69rPna2ctKtmzNevnK/iplCiTQVpnihqK5E1+puHF9Ob2LtxhQZaPioiL7TFYweGYTKy7bq1CiZFmLyTFjhk5Ol/jyUKZciWc9zZsPvV1Duc1UoJv0AMInb7FrkZ+0iM4658YMGk+AGjsyo/cNQjyBGek94wYooFmIJ7n80N6v1g/nNNnCj25GdhkqaFgHVJsEAOA3H3jj6UkBeAAIOPH8duG8A2xAw2lYgMFau6zbQoHLGhAAIfkEIQgA/wAsGwARAB8AKQAACP4A/wkcSLCgQYLz6h1cyHCgPnTlzL3j17AiwXTlMpaLZ9Fiv3May7XraFFdyHkkS5ozh29fyor77Ol7SbOmzZsOKeI0+E2aNHk7CVpjRhSav6D/9kkjStQbUn9LmYpD+o9cNKLTqAo8hw3cPa1gw4pdOK0VrG1asYXKlEnU0aD6SrFliwspPlNzM72iiowTW0/ntPIypUqfvbGId94aVAqspTRmzOyhSq1OZDNpRiGFRudymrpIB12206/mPWb0ClrKQ6jf25TvjhBB8q5mMFfrCIYLMqN3EnIvaVyoUKK0wFg7es/IASvlmwMAoqsYWK6Ich/fUsaIHl3DyK1HdhwY8QYv5aEB3E0UFEfLXE0pFyB8MI60HytSYAMCACH5BCEIAP8ALBEAGgApACAAAAj+AP8JHEiwoMGDCP/x65ewoUOE7tChw/ewokN15TKa82exY8F+6DJmdOex5D9/IUXCM1ky3rmM6FialLfu3T6ZOHPq3MlTJzpr19r1bLjuGTNm0DgONchP2tGj25Ya3Of0qTWpBsc1O+psHlaD3aRR46fvq9mzJZ2xEoZ2YC5NmTKdaitOVNxMmoKh/WY37qZnbVndJaUUITLAHfNhu1cwl6lW/Qob9MFBRCaKD+fVmWPHq8ccAEJLiFSQGa93BNHBMcPazrqO9zyEDs2EIJciQ6AwFFhsDWszaoZ1pDdi9oBGAxfhmMGcysB1dH67OecxHwcABFoQzMKc+ZGVAtsk1WFDxxy9krDSAKpHsFON7lEKpjPGbumcIkCW7Ebbb1etoQEBACH5BCEIAP8ALAkAJAAvABYAAAj+AP8JHEiwoMGDCA3OU7eu3j9+CSNKjEjPXLly5/xlnMiRYz90Fy+yO7evo8mEH0OWU4fupMuD8UKaw+fvpU2C7dCl6wfxps+fQCP6QRQUoblq4BB6+yAgQY57RQlyY0Z12sEXALIWWBRVIDxoVKkmJYivQ9asTLr+ewc27DmDO846mKT2X7Ww0WoaPIKBQ5CC07Cd3FcuX0Fu0qz501vQXS5mBckkcdLK8MR7o0KNgvoTzIzPQk4VvLZsHkF4oDKpJhXPJ74lnz/DIThoTpw9/QZi66Q6E6drPu09iV2D1EBTacwo9zNQnqjent791Jdkho0rBAMpV07HtMB5ozokiXLH2ScwQ5nK/6N1ZvuegvCyyatbkJKcN3dy05fYT5mxogEBACH5BCEIAP8ALAQAJAAvABYAAAj+AP/RA0TG1b+DCBMqXMiwIUMtCwAsUOewosWK9IBFBABAg76LIEH2QweII0cO2kKqdDjynwiTIVbKZBjv3ygMGkD0m8lzoT5lO3sKHUq0IiZQRRvKS/euITkmNXSEwZc0YbtyWNExzDKj641QVQ/eO4cVqzuF+ZR07fom7L+xZcvJWyhmrQ9Ubv+lK3vOH0M2RpKcUehN3Mp+8vgpbIdOnT+/C+Mds6Zw0R09wj5e3BcNWrR9RBGZGR2nl0Jx2u4lvPeMmetoVHvqwzN6NKWEq0CBKhX037pmrpk1WycUn57aZ3QhDLYpk/NTCPFBC+5MtdB9dsygCZRQlXPnoawp/8sXrRk0e6CHPiMlK19CZd8zmVJ4j13svAhtgfI0CjL+iv1oQxlPAQEAIfkEIQgA/wAsAgAaACkAIAAACP4A//3L50+gwYMIEypcKHDfvRIbhDCcSDEhPwwAAAiQWLEjQ0MHMgLgoMyjSYSZEIjcYOyky3/+NmQsgOXlS35LQLSxybOnz59Agy60l2mQL6EU9fCYwcMd0oXMls6YgWTf04SZpk5NEu5qQidam3hNWMsIEib9xibcRy2t2rc2Y92Ca3AdnjNrEOmjK8iM3zS54Oq749fvJLqJCrs5ShcSnTuNEKZbd9IfPrcM6VUDhzAWKVPW+HXsd87cOdEnX2VaDUoaQnborBrcZ66c7XOyO+4rtXr1XIPSnDmDhrme7eP0TOoz1VtTNIPdmEln9rzhuePmcnfkRyqTplUHpSVNZ/Zsr3XT+jB7/CaMmXZw46Eh3FdPO9Brzpo9K0i3X7pzFQUEACH5BCEIAP8ALAIAEQAgACkAAAj+AP8J/EeOmr+BCBMqXDgQy4cOIxhKnOhoAoCLKCZqTFjk4sUO4TaKnFPAoweRIsNBaTBgRDGUKDclgkmzps2bC/UdxLlwHz4oSMzwVMivyIwZNIQOHcgJx9EZSKYtFbgqx1Mk0Kb+84fk6A08WgXyc8NkZtizaNPCxCdLlDO0m9iYYSMvbDa5ZszU4adVVt68d9CF1fNXz9ljdOrk6YeW3zfGaiMnXPYMbbxSmTi92heWVabPmrJO5Ufq8+dbYWGZ9iQ1bC1RpGQlpFePJ75x6hJiiyZNHeSp15gJfyYYobx3fGn2kyZc+DaE5aKX+y2SH/Pm5waqkx69Zr9owqsZITTHvVxymO/ATUfIrvzZc9J3au0H793AgAAh+QQhCAD/ACwCAAkAFgAvAAAI/gD/CRxIsJo/gv/WoUPH7yBCgfQ2SKSH0J/Dh/+uUQDA8QM4jCA5JeAIQMEnkBi7TSBZgRpKjNUqAKBQ6SVIY4ia2dzJMx23izwF5mGi5EnQgaWEzFg65eg/NUuXKjl31NGNqEucnpvTo8YTaE4FvgIVtqxZkPuABuWXb0+dRWH5zTFDF+7RWWnomqnD7agvNXrraDvqrw5dNJjC9ouEp9TZx5Aj62MWzFtZXp0ydbLntFzmTJlG9TvKDDRoUvCcmjJtKqw2UaNKqeXZL93syGXLUQ2LTxqzZtdGH63GrDiz3bSjGWe2zek1487SuYYWDRvCfPps7otHkeC6c+joId1Gqa6ceXPzCKMzb57d0X7n2JeT59Rf/HLSw9p7F290QAAh+QQhCAD/ACwCAAQAFgAvAAAI/gD/CRxIsKBAUkUOGVxYUE0CAAVwMGRoiwOAiww+TTToisJFiIo2GlTxEYO/jd1OEtzRAUa7fAztIUGSxF7BffwmfhsyoycTcyIJwtLRc8YOWUEHjhNSlAi3pAO7EZkxRBVUgtE+XbvKtaC7ciq5bspzZ0/XXXHMqO3D9ZFatXfaXVWF5u0dru0stUGz52nXYbi6Ch7MkF/Yq/z2lRIFq2u/UJkiN76aTFPkTKKAQo226bKoclf9iYqsKTDXfrRIBSPMurVrfuXAuRPczRkzZ/q4yrPNjFm0wyLL9e4d7R5XacOldWUHLZo04En90YPuWnA8eYL3nStXTh31iem4IXOfF3q7eHZc1Yk3R54ru3Pn1hXMl3tjv3swCa47h256QAA7') no-repeat center center; -} - -#alert { - position: fixed; - width: 30%; - margin: 30px auto; - top: 10px; - left: 0; - right: 0; - z-index: 2000; - display: none; -} - -#alert a { - text-decoration: underline; -} - -.option-item .bootstrap-switch { - margin: 15px 10px; -} - -.option-item button { - margin: 10px; -} - -.option-item input[type=number] { - margin: 15px 10px; - width: 80px; - height: 28px; - padding: 3px 10px; - vertical-align: middle; -} - -.option-item select { - margin: 10px; - display: inline-block; -} - -button img, -span.btn img { - margin-right: 3px; - margin-bottom: 1px; -} - -#edit-favourites { - float: right; - margin-top: -5px; -} - -#edit-favourites-list { - margin: 10px; -} - -.about-img-left { - float: left; - margin: 10px 20px 20px 0; -} - -.about-img-right { - float: right; - margin: 10px 0 20px 20px; -} - -.save-link-options { - float: right; -} - -.save-link-options input{ - margin-left: 10px; -} - -#save-footer { - border-top: none; - margin-top: 0; -} diff --git a/src/web/css/structure/overrides.css b/src/web/css/structure/overrides.css deleted file mode 100755 index 51c27627..00000000 --- a/src/web/css/structure/overrides.css +++ /dev/null @@ -1,113 +0,0 @@ -/* Bootstrap */ - -button, -a:focus { - outline: none; - -moz-outline-style: none; -} - -.btn-default { - border-color: #ddd; -} - -.btn-default:focus { - background-color: #fff; - border-color: #adadad; -} - -.btn-default:hover, -.btn-default:active { - background-color: #ebebeb; - border-color: #adadad; -} - -.btn, -.btn-lg, -.nav-tabs>li>a, -.form-control, -.popover, -.alert, -.modal-content, -.tooltip-inner, -.dropdown-menu { - border-radius: 0 !important; -} - -input[type="search"] { - -webkit-appearance: searchfield; - box-shadow: none; -} - -input[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: searchfield-cancel-button; -} - -.modal { - overflow-y: auto; -} - -.form-control { - background-color: transparent; -} - -code { - border: 0; - white-space: pre-wrap; - font-family: Consolas, monospace; -} - -pre { - border-radius: 0 !important; -} - -blockquote { - font-size: inherit; -} - -blockquote a { - cursor: pointer; -} - -optgroup { - font-weight: bold; -} - -.panel-body:before, -.panel-body:after { - content: ""; -} - -.table-nonfluid { - width: auto !important; -} - - -/* Bootstrap-switch */ - -.bootstrap-switch, -.bootstrap-switch-container, -.bootstrap-switch-handle-on, -.bootstrap-switch-handle-off, -.bootstrap-switch-label { - border-radius: 0 !important; -} - - -/* Sortable */ - -.sortable-ghost { - opacity: 0.6; -} - - -/* Bootstrap Colorpicker */ - -.colorpicker-element { - float: left; - margin-right: 15px; -} - -.colorpicker-color, -.colorpicker-color div { - height: 100px; -} \ No newline at end of file diff --git a/src/web/css/structure/utils.css b/src/web/css/structure/utils.css deleted file mode 100755 index e2a804a7..00000000 --- a/src/web/css/structure/utils.css +++ /dev/null @@ -1,37 +0,0 @@ -.word-wrap { - white-space: pre !important; - word-wrap: normal !important; - overflow-x: scroll !important; -} - -.clearfix { - clear: both; - height: 0; -} - -.blur { - color: transparent !important; - text-shadow: rgba(0, 0, 0, 0.95) 0 0 10px !important; -} - -.no-select { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.konami { - -ms-transform: rotate(180deg); - -webkit-transform: rotate(180deg); - transform: rotate(180deg); - -moz-transform: rotate(180deg); -} - -.hl1, .hlyellow { background-color: #fff000; } -.hl2, .hlblue { background-color: #95dfff; } -.hl3, .hlred { background-color: #ffb6b6; } /* Half-Life 3 confirmed :O */ -.hl4, .hlorange { background-color: #fcf8e3; } -.hl5, .hlgreen { background-color: #8de768; } diff --git a/src/web/css/themes/classic.css b/src/web/css/themes/classic.css deleted file mode 100755 index 1507593a..00000000 --- a/src/web/css/themes/classic.css +++ /dev/null @@ -1,258 +0,0 @@ -#banner { - border-bottom: 1px solid #ddd; -} - -.title { - border-bottom: 1px solid #ddd; - font-weight: bold; - color: #424242; - background-color: #fafafa; -} - -.gutter { - background-color: #eee; - background-repeat: no-repeat; - background-position: 50%; -} - -.gutter.gutter-horizontal { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAYAAAAGos/EAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAlSURBVChTYzxz5sx/BiBgAhEgwPju3TtUEZZ79+6BGcNcDQMDACWJMFs4hNOSAAAAAElFTkSuQmCC'); - cursor: ew-resize; -} - -.gutter.gutter-vertical { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAACCAYAAABPJGxCAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAACHDwAAjA8AAP1SAACBQAAAfXkAAOmLAAA85QAAGcxzPIV3AAAKL2lDQ1BJQ0MgUHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTDSGXqTLjCA9C4gHQRRGGYGGMoAwwxNbIioQEQREQFFkKCAAaOhSKyIYiEoqGAPSBBQYjCKqKhkRtZKfHl57+Xl98e939pn73P32XuftS4AJE8fLi8FlgIgmSfgB3o401eFR9Cx/QAGeIABpgAwWempvkHuwUAkLzcXerrICfyL3gwBSPy+ZejpT6eD/0/SrFS+AADIX8TmbE46S8T5Ik7KFKSK7TMipsYkihlGiZkvSlDEcmKOW+Sln30W2VHM7GQeW8TinFPZyWwx94h4e4aQI2LER8QFGVxOpohvi1gzSZjMFfFbcWwyh5kOAIoktgs4rHgRm4iYxA8OdBHxcgBwpLgvOOYLFnCyBOJDuaSkZvO5cfECui5Lj25qbc2ge3IykzgCgaE/k5XI5LPpLinJqUxeNgCLZ/4sGXFt6aIiW5paW1oamhmZflGo/7r4NyXu7SK9CvjcM4jW94ftr/xS6gBgzIpqs+sPW8x+ADq2AiB3/w+b5iEAJEV9a7/xxXlo4nmJFwhSbYyNMzMzjbgclpG4oL/rfzr8DX3xPSPxdr+Xh+7KiWUKkwR0cd1YKUkpQj49PZXJ4tAN/zzE/zjwr/NYGsiJ5fA5PFFEqGjKuLw4Ubt5bK6Am8Kjc3n/qYn/MOxPWpxrkSj1nwA1yghI3aAC5Oc+gKIQARJ5UNz13/vmgw8F4psXpjqxOPefBf37rnCJ+JHOjfsc5xIYTGcJ+RmLa+JrCdCAACQBFcgDFaABdIEhMANWwBY4AjewAviBYBAO1gIWiAfJgA8yQS7YDApAEdgF9oJKUAPqQSNoASdABzgNLoDL4Dq4Ce6AB2AEjIPnYAa8AfMQBGEhMkSB5CFVSAsygMwgBmQPuUE+UCAUDkVDcRAPEkK50BaoCCqFKqFaqBH6FjoFXYCuQgPQPWgUmoJ+hd7DCEyCqbAyrA0bwwzYCfaGg+E1cBycBufA+fBOuAKug4/B7fAF+Dp8Bx6Bn8OzCECICA1RQwwRBuKC+CERSCzCRzYghUg5Uoe0IF1IL3ILGUGmkXcoDIqCoqMMUbYoT1QIioVKQ21AFaMqUUdR7age1C3UKGoG9QlNRiuhDdA2aC/0KnQcOhNdgC5HN6Db0JfQd9Dj6DcYDIaG0cFYYTwx4ZgEzDpMMeYAphVzHjOAGcPMYrFYeawB1g7rh2ViBdgC7H7sMew57CB2HPsWR8Sp4sxw7rgIHA+XhyvHNeHO4gZxE7h5vBReC2+D98Oz8dn4Enw9vgt/Az+OnydIE3QIdoRgQgJhM6GC0EK4RHhIeEUkEtWJ1sQAIpe4iVhBPE68QhwlviPJkPRJLqRIkpC0k3SEdJ50j/SKTCZrkx3JEWQBeSe5kXyR/Jj8VoIiYSThJcGW2ChRJdEuMSjxQhIvqSXpJLlWMkeyXPKk5A3JaSm8lLaUixRTaoNUldQpqWGpWWmKtKm0n3SydLF0k/RV6UkZrIy2jJsMWyZf5rDMRZkxCkLRoLhQWJQtlHrKJco4FUPVoXpRE6hF1G+o/dQZWRnZZbKhslmyVbJnZEdoCE2b5kVLopXQTtCGaO+XKC9xWsJZsmNJy5LBJXNyinKOchy5QrlWuTty7+Xp8m7yifK75TvkHymgFPQVAhQyFQ4qXFKYVqQq2iqyFAsVTyjeV4KV9JUCldYpHVbqU5pVVlH2UE5V3q98UXlahabiqJKgUqZyVmVKlaJqr8pVLVM9p/qMLkt3oifRK+g99Bk1JTVPNaFarVq/2ry6jnqIep56q/ojDYIGQyNWo0yjW2NGU1XTVzNXs1nzvhZei6EVr7VPq1drTltHO0x7m3aH9qSOnI6XTo5Os85DXbKug26abp3ubT2MHkMvUe+A3k19WN9CP16/Sv+GAWxgacA1OGAwsBS91Hopb2nd0mFDkqGTYYZhs+GoEc3IxyjPqMPohbGmcYTxbuNe408mFiZJJvUmD0xlTFeY5pl2mf5qpm/GMqsyu21ONnc332jeaf5ymcEyzrKDy+5aUCx8LbZZdFt8tLSy5Fu2WE5ZaVpFW1VbDTOoDH9GMeOKNdra2Xqj9WnrdzaWNgKbEza/2BraJto22U4u11nOWV6/fMxO3Y5pV2s3Yk+3j7Y/ZD/ioObAdKhzeOKo4ch2bHCccNJzSnA65vTC2cSZ79zmPOdi47Le5bwr4urhWuja7ybjFuJW6fbYXd09zr3ZfcbDwmOdx3lPtKe3527PYS9lL5ZXo9fMCqsV61f0eJO8g7wrvZ/46Pvwfbp8Yd8Vvnt8H67UWslb2eEH/Lz89vg98tfxT/P/PgAT4B9QFfA00DQwN7A3iBIUFdQU9CbYObgk+EGIbogwpDtUMjQytDF0Lsw1rDRsZJXxqvWrrocrhHPDOyOwEaERDRGzq91W7109HmkRWRA5tEZnTdaaq2sV1iatPRMlGcWMOhmNjg6Lbor+wPRj1jFnY7xiqmNmWC6sfaznbEd2GXuKY8cp5UzE2sWWxk7G2cXtiZuKd4gvj5/munAruS8TPBNqEuYS/RKPJC4khSW1JuOSo5NP8WR4ibyeFJWUrJSBVIPUgtSRNJu0vWkzfG9+QzqUvia9U0AV/Uz1CXWFW4WjGfYZVRlvM0MzT2ZJZ/Gy+rL1s3dkT+S453y9DrWOta47Vy13c+7oeqf1tRugDTEbujdqbMzfOL7JY9PRzYTNiZt/yDPJK817vSVsS1e+cv6m/LGtHlubCyQK+AXD22y31WxHbedu799hvmP/jk+F7MJrRSZF5UUfilnF174y/ariq4WdsTv7SyxLDu7C7OLtGtrtsPtoqXRpTunYHt897WX0ssKy13uj9l4tX1Zes4+wT7hvpMKnonO/5v5d+z9UxlfeqXKuaq1Wqt5RPXeAfWDwoOPBlhrlmqKa94e4h+7WetS212nXlR/GHM44/LQ+tL73a8bXjQ0KDUUNH4/wjowcDTza02jV2Nik1FTSDDcLm6eORR67+Y3rN50thi21rbTWouPguPD4s2+jvx064X2i+yTjZMt3Wt9Vt1HaCtuh9uz2mY74jpHO8M6BUytOdXfZdrV9b/T9kdNqp6vOyJ4pOUs4m3924VzOudnzqeenL8RdGOuO6n5wcdXF2z0BPf2XvC9duex++WKvU++5K3ZXTl+1uXrqGuNax3XL6+19Fn1tP1j80NZv2d9+w+pG503rm10DywfODjoMXrjleuvyba/b1++svDMwFDJ0dzhyeOQu++7kvaR7L+9n3J9/sOkh+mHhI6lH5Y+VHtf9qPdj64jlyJlR19G+J0FPHoyxxp7/lP7Th/H8p+Sn5ROqE42TZpOnp9ynbj5b/Wz8eerz+emCn6V/rn6h++K7Xxx/6ZtZNTP+kv9y4dfiV/Kvjrxe9rp71n/28ZvkN/NzhW/l3x59x3jX+z7s/cR85gfsh4qPeh+7Pnl/eriQvLDwG/eE8/s3BCkeAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAI0lEQVQYV2M8c+bMfwYgUFJSAlEM9+7dA9O05jOBSboDBgYAtPcYZ1oUA30AAAAASUVORK5CYII='); - cursor: ns-resize; -} - -.operation { - border: 1px solid #999; - border-top-width: 0; -} - -.op-list .operation { /*blue*/ - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} - -#rec-list .operation { /*green*/ - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -#controls { - border-top: 1px solid #ddd; - background-color: #fafafa; -} - -.textarea-wrapper textarea, -.textarea-wrapper div { - font-family: Consolas, monospace; - font-size: inherit; -} - -.io-info { - font-family: Consolas, monospace; - font-weight: normal; - font-size: 8pt; -} - -.arg-title { - font-weight: bold; -} - -.arg-input { - height: 34px; - font-size: 15px; - line-height: 1.428571429; - color: #424242; - background-color: #fff; - border: 1px solid #ddd; - font-family: Consolas, monospace; -} - -select { - padding: 6px 8px; - height: 34px; - border: 1px solid #ddd; - background-color: #fff; - color: #424242; -} - -.arg[disabled] { - background-color: #eee; -} - -textarea.arg { - color: #424242; - font-family: Consolas, monospace; -} - -.break { - color: #b94a48 !important; - background-color: #f2dede !important; - border-color: #eed3d7 !important; -} - -.category-title { - background-color: #fafafa; - border-bottom: 1px solid #eee; - font-weight: bold; -} - -.category-title[href='#catFavourites'] { - border-bottom-color: #ddd; -} - -.category-title[aria-expanded=true] { - border-bottom-color: #ddd; -} - -.category-title.collapsed { - border-bottom-color: #eee; -} - -.category-title:hover { - color: #3a87ad; -} - -#search { - border-bottom: 1px solid #e3e3e3; -} - -.dropping-file { - border: 5px dashed #3a87ad !important; -} - -.selected-op { - color: #c09853 !important; - background-color: #fcf8e3 !important; - border-color: #fbeed5 !important; -} - -.option-item input[type=number] { - font-size: 14px; - line-height: 1.428571429; - color: #555; - background-color: #fff; - border: 1px solid #ccc; -} - -.favourites-hover { - color: #468847; - background-color: #dff0d8; - border: 2px dashed #468847 !important; - padding: 8px 8px 9px 8px; -} - -#edit-favourites-list { - border: 1px solid #bce8f1; -} - -#edit-favourites-list .operation { - border-left: none; - border-right: none; -} - -#edit-favourites-list .operation:last-child { - border-bottom: none; -} - -.subtext { - font-style: italic; - font-size: 13px; - color: #999; -} - -#save-footer { - border-bottom: 1px solid #e5e5e5; -} - -.flow-control-op { - color: #396f3a !important; - background-color: #c7e4ba !important; - border-color: #b3dba2 !important; -} - -.flow-control-op.break { - color: #94312f !important; - background-color: #eabfbf !important; - border-color: #e2aeb5 !important; -} - -#support-modal textarea { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -} - -#save-text, -#load-text { - font-family: Consolas, monospace; -} - -button.dropdown-toggle { - background-color: #f4f4f4; -} - - - - -::-webkit-scrollbar { - width: 10px; - height: 10px; -} -::-webkit-scrollbar-track { - background-color: #fafafa; -} -::-webkit-scrollbar-thumb { - background-color: #ccc; -} -::-webkit-scrollbar-thumb:hover { - background-color: #bbb; -} -::-webkit-scrollbar-corner { - background-color: #fafafa; -} - -.disabled { - color: #999 !important; - background-color: #dfdfdf !important; - border-color: #cdcdcd !important; -} - -.grey { - color: #333; - background-color: #f5f5f5; - border-color: #ddd; -} - -.dark-blue { - color: #fff; - background-color: #428bca; - border-color: #428bca; -} - -.red { - color: #b94a48; - background-color: #f2dede; - border-color: #eed3d7; -} - -.amber { - color: #c09853; - background-color: #fcf8e3; - border-color: #fbeed5; -} - -.green { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -.blue { - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} diff --git a/src/web/html/index.html b/src/web/html/index.html index edf2a2d5..38bf7ccc 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -1,17 +1,17 @@ - + CyberChef - - - + + + + + - Edit -
    - - + +
    +
    +
    +
    + +
    -