Merge remote-tracking branch 'upstream/master'
# Conflicts: # README.md
This commit is contained in:
commit
edacc3f810
25
CHANGELOG.md
25
CHANGELOG.md
@ -13,6 +13,17 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
|
|
||||||
## Details
|
## Details
|
||||||
|
|
||||||
|
### [11.2.0] - 2026-06-17
|
||||||
|
This release includes a security fix ([#2569])
|
||||||
|
- Security: Chart operation prototype protection [@C85297] | [#2569]
|
||||||
|
- Update website references [@C85297] | [#2566]
|
||||||
|
- Fix: Add input validation for XOR Checksum blocksize (#2537) [@dweep-js] | [#2542]
|
||||||
|
- Fix: Reverse highlights unwind incorrectly [@kendallgoto] [@C85297] | [#2022]
|
||||||
|
- Fix Uint8Array concat crash in Parse IPv4 header [@Zish19] | [#2409]
|
||||||
|
- Fix typos and documentation errors (bytes→bits, wrong release link, spelling) [@qa2me] [@GCHQDeveloper581] | [#2404]
|
||||||
|
- Add integer check for alphabet size [@heapframe] [@GCHQDeveloper581] | [#2458]
|
||||||
|
- fix: validate hexdump width upper bound [@skyswordw] | [#2514]
|
||||||
|
|
||||||
### [11.1.0] - 2026-06-13
|
### [11.1.0] - 2026-06-13
|
||||||
This release includes a security fix ([#2557])
|
This release includes a security fix ([#2557])
|
||||||
- Security: Add fix, and tests, for Lorem Ipsum DoS issue [@GCHQDeveloper581] | [#2557]
|
- Security: Add fix, and tests, for Lorem Ipsum DoS issue [@GCHQDeveloper581] | [#2557]
|
||||||
@ -707,6 +718,7 @@ Breaking changes:
|
|||||||
## [4.0.0] - 2016-11-28
|
## [4.0.0] - 2016-11-28
|
||||||
- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)
|
- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)
|
||||||
|
|
||||||
|
[11.2.0]: https://github.com/gchq/CyberChef/releases/tag/v11.2.0
|
||||||
[11.1.0]: https://github.com/gchq/CyberChef/releases/tag/v11.1.0
|
[11.1.0]: https://github.com/gchq/CyberChef/releases/tag/v11.1.0
|
||||||
[11.0.0]: https://github.com/gchq/CyberChef/releases/tag/v11.0.0
|
[11.0.0]: https://github.com/gchq/CyberChef/releases/tag/v11.0.0
|
||||||
[10.24.0]: https://github.com/gchq/CyberChef/releases/tag/v10.24.0
|
[10.24.0]: https://github.com/gchq/CyberChef/releases/tag/v10.24.0
|
||||||
@ -1001,6 +1013,11 @@ Breaking changes:
|
|||||||
[@Louis-Ladd]: https://github.com/Louis-Ladd
|
[@Louis-Ladd]: https://github.com/Louis-Ladd
|
||||||
[@Blank0120]: https://github.com/Blank0120
|
[@Blank0120]: https://github.com/Blank0120
|
||||||
[@zachbowden]: https://github.com/zachbowden
|
[@zachbowden]: https://github.com/zachbowden
|
||||||
|
[@dweep-js]: https://github.com/dweep-js
|
||||||
|
[@Zish19]: https://github.com/Zish19
|
||||||
|
[@qa2me]: https://github.com/qa2me
|
||||||
|
[@heapframe]: https://github.com/heapframe
|
||||||
|
[@skyswordw]: https://github.com/skyswordw
|
||||||
|
|
||||||
|
|
||||||
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
||||||
@ -1364,4 +1381,12 @@ Breaking changes:
|
|||||||
[#2332]: https://github.com/gchq/CyberChef/pull/2332
|
[#2332]: https://github.com/gchq/CyberChef/pull/2332
|
||||||
[#2353]: https://github.com/gchq/CyberChef/pull/2353
|
[#2353]: https://github.com/gchq/CyberChef/pull/2353
|
||||||
[#2351]: https://github.com/gchq/CyberChef/pull/2351
|
[#2351]: https://github.com/gchq/CyberChef/pull/2351
|
||||||
|
[#2569]: https://github.com/gchq/CyberChef/pull/2569
|
||||||
|
[#2566]: https://github.com/gchq/CyberChef/pull/2566
|
||||||
|
[#2542]: https://github.com/gchq/CyberChef/pull/2542
|
||||||
|
[#2022]: https://github.com/gchq/CyberChef/pull/2022
|
||||||
|
[#2409]: https://github.com/gchq/CyberChef/pull/2409
|
||||||
|
[#2404]: https://github.com/gchq/CyberChef/pull/2404
|
||||||
|
[#2458]: https://github.com/gchq/CyberChef/pull/2458
|
||||||
|
[#2514]: https://github.com/gchq/CyberChef/pull/2514
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
# Modifier --platform=$BUILDPLATFORM limits the platform to "BUILDPLATFORM" during buildx multi-platform builds
|
# 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
|
# 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
|
# For more info see: https://docs.docker.com/build/building/multi-platform/#cross-compilation
|
||||||
FROM --platform=$BUILDPLATFORM node:24-alpine@sha256:fb71d01345f11b708a3553c66e7c74074f2d506400ea81973343d915cb64eef0 AS builder
|
FROM --platform=$BUILDPLATFORM node:24-alpine@sha256:156b55f92e98ccd5ef49578a8cea0df4679826564bad1c9d4ef04462b9f0ded6 AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ RUN npm run build
|
|||||||
#########################################
|
#########################################
|
||||||
# Package static build files into nginx #
|
# Package static build files into nginx #
|
||||||
#########################################
|
#########################################
|
||||||
FROM nginxinc/nginx-unprivileged:stable-alpine@sha256:37f356a5eba5d187365b4f59cd6cc29f1f922ad18146d554b576a80983377e6a AS cyberchef
|
FROM nginxinc/nginx-unprivileged:stable-alpine@sha256:fafa1102c789119971b3d83f9293f1ef5526bc73583a12e13ff5cd1299ed8b6c AS cyberchef
|
||||||
|
|
||||||
LABEL maintainer="GCHQ <oss@gchq.gov.uk>"
|
LABEL maintainer="GCHQ <oss@gchq.gov.uk>"
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
## Supported Versions
|
## Support
|
||||||
|
|
||||||
CyberChef is supported on a best endeavours basis.
|
CyberChef is supported on a best endeavours basis.
|
||||||
Patches will be applied to the latest version rather than retroactively to older versions.
|
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 website](https://gchq.github.io/CyberChef/) is always up to date.
|
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 website](https://gchq.github.io/CyberChef/) is always up to date.
|
||||||
|
|
||||||
|
No guarantee is offered for the correctness or security of CyberChef. In paticular, the security of cryptographic operations should not be relied upon.
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
If you discover a vulnerability in CyberChef, please do not publicly disclose it, and do not create a GitHub issue.
|
If you discover a vulnerability in CyberChef, please do not publicly disclose it, and do not create a GitHub issue.
|
||||||
|
|||||||
68
package-lock.json
generated
68
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "cyberchef",
|
"name": "cyberchef",
|
||||||
"version": "11.1.0",
|
"version": "11.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cyberchef",
|
"name": "cyberchef",
|
||||||
"version": "11.1.0",
|
"version": "11.2.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -21,7 +21,7 @@
|
|||||||
"assert": "^2.1.0",
|
"assert": "^2.1.0",
|
||||||
"avsc": "^5.7.9",
|
"avsc": "^5.7.9",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"bignumber.js": "^11.1.3",
|
"bignumber.js": "^11.1.4",
|
||||||
"blakejs": "^1.2.1",
|
"blakejs": "^1.2.1",
|
||||||
"bootstrap": "4.6.2",
|
"bootstrap": "4.6.2",
|
||||||
"bootstrap-colorpicker": "^3.4.0",
|
"bootstrap-colorpicker": "^3.4.0",
|
||||||
@ -39,7 +39,7 @@
|
|||||||
"d3": "7.9.0",
|
"d3": "7.9.0",
|
||||||
"d3-hexbin": "^0.2.2",
|
"d3-hexbin": "^0.2.2",
|
||||||
"diff": "^9.0.0",
|
"diff": "^9.0.0",
|
||||||
"dompurify": "^3.4.8",
|
"dompurify": "^3.4.11",
|
||||||
"es6-promisify": "^7.0.0",
|
"es6-promisify": "^7.0.0",
|
||||||
"escodegen": "^2.1.0",
|
"escodegen": "^2.1.0",
|
||||||
"esprima": "^4.0.1",
|
"esprima": "^4.0.1",
|
||||||
@ -85,7 +85,7 @@
|
|||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"protobufjs": "^8.6.2",
|
"protobufjs": "^8.6.4",
|
||||||
"punycode.js": "^2.3.1",
|
"punycode.js": "^2.3.1",
|
||||||
"qr-image": "^3.2.0",
|
"qr-image": "^3.2.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
@ -116,7 +116,7 @@
|
|||||||
"@babel/runtime": "^7.29.7",
|
"@babel/runtime": "^7.29.7",
|
||||||
"@codemirror/commands": "^6.10.3",
|
"@codemirror/commands": "^6.10.3",
|
||||||
"@codemirror/language": "^6.12.3",
|
"@codemirror/language": "^6.12.3",
|
||||||
"@codemirror/search": "^6.7.0",
|
"@codemirror/search": "^6.7.1",
|
||||||
"@codemirror/state": "^6.5.4",
|
"@codemirror/state": "^6.5.4",
|
||||||
"@codemirror/view": "^6.43.1",
|
"@codemirror/view": "^6.43.1",
|
||||||
"@puppeteer/browsers": "3.0.4",
|
"@puppeteer/browsers": "3.0.4",
|
||||||
@ -159,7 +159,7 @@
|
|||||||
"terser": "^5.48.0",
|
"terser": "^5.48.0",
|
||||||
"webpack": "^5.107.2",
|
"webpack": "^5.107.2",
|
||||||
"webpack-bundle-analyzer": "^5.3.0",
|
"webpack-bundle-analyzer": "^5.3.0",
|
||||||
"webpack-dev-server": "^5.2.4",
|
"webpack-dev-server": "^5.2.5",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"worker-loader": "^3.0.8"
|
"worker-loader": "^3.0.8"
|
||||||
},
|
},
|
||||||
@ -1876,9 +1876,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/search": {
|
"node_modules/@codemirror/search": {
|
||||||
"version": "6.7.0",
|
"version": "6.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.1.tgz",
|
||||||
"integrity": "sha512-ZvGm99wc/s2cITtMT15LFdn8aH/aS+V+DqyGq/N5ZlV5vWtH+nILvC2nw0zX7ByNoHHDZ2IxxdW38O0tc5nVHg==",
|
"integrity": "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -5794,9 +5794,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bignumber.js": {
|
"node_modules/bignumber.js": {
|
||||||
"version": "11.1.3",
|
"version": "11.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.1.4.tgz",
|
||||||
"integrity": "sha512-+esZiNSo6VgFokTsYX6mYqNJfFd/IczzZCd4Z7cR8e+AQWhvIcj6nqQ1h9814D9u/TApU0jjTVmfWL0Pd1ZBdA==",
|
"integrity": "sha512-AJ9dSeaUGj2xu7tEwmdqb51dqdb633xo4njI9K8ZFfcLrNr0XN8/EPkkZUNaF9fkCblGt2zVwZymesUdGynEkQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
@ -8585,9 +8585,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.4.8",
|
"version": "3.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz",
|
||||||
"integrity": "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==",
|
"integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@types/trusted-types": "^2.0.7"
|
"@types/trusted-types": "^2.0.7"
|
||||||
@ -9865,17 +9865,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
||||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
"hasown": "^2.0.2",
|
"hasown": "^2.0.4",
|
||||||
"mime-types": "^2.1.12"
|
"mime-types": "^2.1.35"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@ -10907,9 +10907,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@ -12561,14 +12561,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/launch-editor": {
|
"node_modules/launch-editor": {
|
||||||
"version": "2.13.1",
|
"version": "2.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz",
|
||||||
"integrity": "sha512-lPSddlAAluRKJ7/cjRFoXUFzaX7q/YKI7yPHuEvSJVqoXvFnJov1/Ud87Aa4zULIbA9Nja4mSPK8l0z/7eV2wA==",
|
"integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"shell-quote": "^1.8.3"
|
"shell-quote": "^1.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lazystream": {
|
"node_modules/lazystream": {
|
||||||
@ -15086,9 +15086,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/protobufjs": {
|
"node_modules/protobufjs": {
|
||||||
"version": "8.6.2",
|
"version": "8.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.6.4.tgz",
|
||||||
"integrity": "sha512-CCERJxzRvKMeEdJSLwdQf40TXWNPc8M4RkN7j/lxY6FQB+4do8rETWqj60AqxP9n0XIsxnSefZ8uhAaGKg2njw==",
|
"integrity": "sha512-/+XMv9JalknuncEJSwsyEVlwcxVLKx2iaoSUXFZA86MJkdqyOdfrlB1sB7S6aKyUk9tl20YY+SgQe5J2sJHTcg==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"long": "^5.3.2"
|
"long": "^5.3.2"
|
||||||
@ -18112,9 +18112,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack-dev-server": {
|
"node_modules/webpack-dev-server": {
|
||||||
"version": "5.2.4",
|
"version": "5.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.5.tgz",
|
||||||
"integrity": "sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==",
|
"integrity": "sha512-4wZtCquSuv9CKX8oybo+mqxtxZqWz47uM1Ch94lxowBztOhWCbhqvRbfC/mODOwxgV2brY+JGZpHq58/SuVFYg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
12
package.json
12
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cyberchef",
|
"name": "cyberchef",
|
||||||
"version": "11.1.0",
|
"version": "11.2.0",
|
||||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||||
"author": "GCHQ <CyberChef@gchq.gov.uk>",
|
"author": "GCHQ <CyberChef@gchq.gov.uk>",
|
||||||
"homepage": "https://gchq.github.io/CyberChef",
|
"homepage": "https://gchq.github.io/CyberChef",
|
||||||
@ -46,7 +46,7 @@
|
|||||||
"@babel/runtime": "^7.29.7",
|
"@babel/runtime": "^7.29.7",
|
||||||
"@codemirror/commands": "^6.10.3",
|
"@codemirror/commands": "^6.10.3",
|
||||||
"@codemirror/language": "^6.12.3",
|
"@codemirror/language": "^6.12.3",
|
||||||
"@codemirror/search": "^6.7.0",
|
"@codemirror/search": "^6.7.1",
|
||||||
"@codemirror/state": "^6.5.4",
|
"@codemirror/state": "^6.5.4",
|
||||||
"@codemirror/view": "^6.43.1",
|
"@codemirror/view": "^6.43.1",
|
||||||
"@puppeteer/browsers": "3.0.4",
|
"@puppeteer/browsers": "3.0.4",
|
||||||
@ -89,7 +89,7 @@
|
|||||||
"terser": "^5.48.0",
|
"terser": "^5.48.0",
|
||||||
"webpack": "^5.107.2",
|
"webpack": "^5.107.2",
|
||||||
"webpack-bundle-analyzer": "^5.3.0",
|
"webpack-bundle-analyzer": "^5.3.0",
|
||||||
"webpack-dev-server": "^5.2.4",
|
"webpack-dev-server": "^5.2.5",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"worker-loader": "^3.0.8"
|
"worker-loader": "^3.0.8"
|
||||||
},
|
},
|
||||||
@ -105,7 +105,7 @@
|
|||||||
"assert": "^2.1.0",
|
"assert": "^2.1.0",
|
||||||
"avsc": "^5.7.9",
|
"avsc": "^5.7.9",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"bignumber.js": "^11.1.3",
|
"bignumber.js": "^11.1.4",
|
||||||
"blakejs": "^1.2.1",
|
"blakejs": "^1.2.1",
|
||||||
"bootstrap": "4.6.2",
|
"bootstrap": "4.6.2",
|
||||||
"bootstrap-colorpicker": "^3.4.0",
|
"bootstrap-colorpicker": "^3.4.0",
|
||||||
@ -123,7 +123,7 @@
|
|||||||
"d3": "7.9.0",
|
"d3": "7.9.0",
|
||||||
"d3-hexbin": "^0.2.2",
|
"d3-hexbin": "^0.2.2",
|
||||||
"diff": "^9.0.0",
|
"diff": "^9.0.0",
|
||||||
"dompurify": "^3.4.8",
|
"dompurify": "^3.4.11",
|
||||||
"es6-promisify": "^7.0.0",
|
"es6-promisify": "^7.0.0",
|
||||||
"escodegen": "^2.1.0",
|
"escodegen": "^2.1.0",
|
||||||
"esprima": "^4.0.1",
|
"esprima": "^4.0.1",
|
||||||
@ -169,7 +169,7 @@
|
|||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"protobufjs": "^8.6.2",
|
"protobufjs": "^8.6.4",
|
||||||
"punycode.js": "^2.3.1",
|
"punycode.js": "^2.3.1",
|
||||||
"qr-image": "^3.2.0",
|
"qr-image": "^3.2.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
|
|||||||
@ -32,6 +32,8 @@ class Ingredient {
|
|||||||
this.min = null;
|
this.min = null;
|
||||||
this.max = null;
|
this.max = null;
|
||||||
this.step = 1;
|
this.step = 1;
|
||||||
|
this.integer = false;
|
||||||
|
this.allowEmpty = true;
|
||||||
|
|
||||||
if (ingredientConfig) {
|
if (ingredientConfig) {
|
||||||
this._parseConfig(ingredientConfig);
|
this._parseConfig(ingredientConfig);
|
||||||
@ -59,6 +61,70 @@ class Ingredient {
|
|||||||
this.min = ingredientConfig.min;
|
this.min = ingredientConfig.min;
|
||||||
this.max = ingredientConfig.max;
|
this.max = ingredientConfig.max;
|
||||||
this.step = ingredientConfig.step;
|
this.step = ingredientConfig.step;
|
||||||
|
this.integer = typeof ingredientConfig.integer !== "undefined" ? !!ingredientConfig.integer : false;
|
||||||
|
this.allowEmpty = typeof ingredientConfig.allowEmpty !== "undefined" ? !!ingredientConfig.allowEmpty : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the given value against the constraints of this ingredient.
|
||||||
|
*
|
||||||
|
* @param {*} val
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
validate(val) {
|
||||||
|
if (this.disabled) return true;
|
||||||
|
|
||||||
|
let checkVal = val;
|
||||||
|
if (this.type === "toggleString" && val && typeof val === "object" && "string" in val) {
|
||||||
|
checkVal = val.string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. check if empty
|
||||||
|
let isEmpty = false;
|
||||||
|
if (checkVal === null || checkVal === undefined || checkVal === "") {
|
||||||
|
isEmpty = true;
|
||||||
|
} else if (typeof checkVal.length === "number" && checkVal.length === 0) {
|
||||||
|
isEmpty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
if (this.allowEmpty === false) {
|
||||||
|
throw new OperationError(`${this.name} cannot be empty.`);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. maxLength check
|
||||||
|
if (typeof this.maxLength === "number" && checkVal !== null && checkVal !== undefined) {
|
||||||
|
if (typeof checkVal === "string" && checkVal.length > this.maxLength) {
|
||||||
|
throw new OperationError(`${this.name} length cannot exceed ${this.maxLength}.`);
|
||||||
|
}
|
||||||
|
if (Array.isArray(checkVal) && checkVal.length > this.maxLength) {
|
||||||
|
throw new OperationError(`${this.name} length cannot exceed ${this.maxLength}.`);
|
||||||
|
}
|
||||||
|
if (checkVal instanceof Uint8Array && checkVal.length > this.maxLength) {
|
||||||
|
throw new OperationError(`${this.name} length cannot exceed ${this.maxLength}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. number checks
|
||||||
|
if (this.type === "number") {
|
||||||
|
if (val === null || val === undefined || isNaN(val)) {
|
||||||
|
throw new OperationError(`${this.name} must be a number.`);
|
||||||
|
}
|
||||||
|
if (this.integer && !Number.isInteger(val)) {
|
||||||
|
throw new OperationError(`${this.name} must be an integer.`);
|
||||||
|
}
|
||||||
|
if (typeof this.min === "number" && val < this.min) {
|
||||||
|
throw new OperationError(`${this.name} must be greater than or equal to ${this.min}.`);
|
||||||
|
}
|
||||||
|
if (typeof this.max === "number" && val > this.max) {
|
||||||
|
throw new OperationError(`${this.name} must be less than or equal to ${this.max}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -192,11 +192,30 @@ class Operation {
|
|||||||
if (typeof ing.min === "number") conf.min = ing.min;
|
if (typeof ing.min === "number") conf.min = ing.min;
|
||||||
if (typeof ing.max === "number") conf.max = ing.max;
|
if (typeof ing.max === "number") conf.max = ing.max;
|
||||||
if (ing.step) conf.step = ing.step;
|
if (ing.step) conf.step = ing.step;
|
||||||
|
if (typeof ing.integer !== "undefined") conf.integer = ing.integer;
|
||||||
|
if (typeof ing.allowEmpty !== "undefined") conf.allowEmpty = ing.allowEmpty;
|
||||||
return conf;
|
return conf;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the operation's ingredients against their defined constraints.
|
||||||
|
*
|
||||||
|
* @param {Object[]} [args] - Optional list of argument values to validate. If not provided, validates the current ingredient values.
|
||||||
|
* @returns {boolean} - True if valid, throws an OperationError if invalid.
|
||||||
|
*/
|
||||||
|
validateIngredients(args) {
|
||||||
|
const values = args || this.ingValues;
|
||||||
|
this._ingList.forEach((ing, i) => {
|
||||||
|
if (i < values.length) {
|
||||||
|
ing.validate(values[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value of the Operation as it should be displayed in a recipe config.
|
* Returns the value of the Operation as it should be displayed in a recipe config.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -212,6 +212,8 @@ class Recipe {
|
|||||||
self.sendProgressMessage(i + 1, this.opList.length);
|
self.sendProgressMessage(i + 1, this.opList.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
op.validateIngredients(op.ingValues);
|
||||||
|
|
||||||
if (op.flowControl) {
|
if (op.flowControl) {
|
||||||
// Package up the current state
|
// Package up the current state
|
||||||
let state = {
|
let state = {
|
||||||
|
|||||||
@ -559,7 +559,7 @@
|
|||||||
"Scatter chart",
|
"Scatter chart",
|
||||||
"Series chart",
|
"Series chart",
|
||||||
"Heatmap chart",
|
"Heatmap chart",
|
||||||
"Extract Audio Metadata"
|
"Render PDF"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -585,7 +585,8 @@
|
|||||||
"HTML To Text",
|
"HTML To Text",
|
||||||
"Generate Lorem Ipsum",
|
"Generate Lorem Ipsum",
|
||||||
"Numberwang",
|
"Numberwang",
|
||||||
"XKCD Random Number"
|
"XKCD Random Number",
|
||||||
|
"Automated Validation Test Op"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -652,4 +653,4 @@
|
|||||||
"Comment"
|
"Comment"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -108,14 +108,17 @@ export function mean(data) {
|
|||||||
* @returns {BigNumber}
|
* @returns {BigNumber}
|
||||||
*/
|
*/
|
||||||
export function median(data) {
|
export function median(data) {
|
||||||
if ((data.length % 2) === 0 && data.length > 0) {
|
if (data.length > 0) {
|
||||||
data.sort(function(a, b) {
|
data.sort(function(a, b) {
|
||||||
return a.minus(b);
|
return a.minus(b);
|
||||||
});
|
});
|
||||||
const first = data[Math.floor(data.length / 2)];
|
|
||||||
const second = data[Math.floor(data.length / 2) - 1];
|
if ((data.length % 2) === 0) {
|
||||||
return mean([first, second]);
|
const first = data[Math.floor(data.length / 2)];
|
||||||
} else {
|
const second = data[Math.floor(data.length / 2) - 1];
|
||||||
|
return mean([first, second]);
|
||||||
|
}
|
||||||
|
|
||||||
return data[Math.floor(data.length / 2)];
|
return data[Math.floor(data.length / 2)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -153,7 +153,7 @@ export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHe
|
|||||||
);
|
);
|
||||||
|
|
||||||
let xValues = new Set();
|
let xValues = new Set();
|
||||||
const series = {};
|
const series = Object.create(null);
|
||||||
|
|
||||||
values.forEach(row => {
|
values.forEach(row => {
|
||||||
const serie = row[0],
|
const serie = row[0],
|
||||||
@ -163,14 +163,14 @@ export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHe
|
|||||||
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
|
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
|
||||||
|
|
||||||
xValues.add(xVal);
|
xValues.add(xVal);
|
||||||
if (typeof series[serie] === "undefined") series[serie] = {};
|
if (typeof series[serie] === "undefined") series[serie] = Object.create(null);
|
||||||
series[serie][xVal] = val;
|
series[serie][xVal] = val;
|
||||||
});
|
});
|
||||||
|
|
||||||
xValues = new Array(...xValues);
|
xValues = new Array(...xValues);
|
||||||
|
|
||||||
const seriesList = [];
|
const seriesList = [];
|
||||||
for (const seriesName in series) {
|
for (const seriesName of Object.keys(series)) {
|
||||||
const serie = series[seriesName];
|
const serie = series[seriesName];
|
||||||
seriesList.push({name: seriesName, data: serie});
|
seriesList.push({name: seriesName, data: serie});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import BigNumber from "bignumber.js";
|
import BigNumber from "bignumber.js";
|
||||||
import {toHexFast} from "../lib/Hex.mjs";
|
import {toHexFast} from "../lib/Hex.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively displays a JSON object as an HTML table
|
* Recursively displays a JSON object as an HTML table
|
||||||
@ -25,15 +26,16 @@ export function objToTable(obj, nested=false) {
|
|||||||
<th>Value</th>
|
<th>Value</th>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|
||||||
for (const key in obj) {
|
for (const key of Object.keys(obj)) {
|
||||||
if (typeof obj[key] === "function")
|
const value = obj[key];
|
||||||
|
if (typeof value === "function")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
html += `<tr><td style='word-wrap: break-word'>${Utils.escapeHtml(String(key))}</td>`;
|
||||||
if (typeof obj[key] === "object")
|
if (value !== null && typeof value === "object")
|
||||||
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
|
html += `<td style='padding: 0'>${objToTable(value, true)}</td>`;
|
||||||
else
|
else
|
||||||
html += `<td>${obj[key]}</td>`;
|
html += `<td>${Utils.escapeHtml(String(value))}</td>`;
|
||||||
html += "</tr>";
|
html += "</tr>";
|
||||||
}
|
}
|
||||||
html += "</table>";
|
html += "</table>";
|
||||||
|
|||||||
@ -33,20 +33,29 @@ export default class TLVParser {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
getLength() {
|
getLength() {
|
||||||
|
let bytesInLength = this.bytesInLength;
|
||||||
|
let bigEndian = false;
|
||||||
|
|
||||||
if (this.basicEncodingRules) {
|
if (this.basicEncodingRules) {
|
||||||
const bit = this.input[this.location];
|
const firstLengthByte = this.input[this.location];
|
||||||
if (bit & 0x80) {
|
this.location++;
|
||||||
this.bytesInLength = bit & ~0x80;
|
|
||||||
|
if (firstLengthByte & 0x80) {
|
||||||
|
bytesInLength = firstLengthByte & ~0x80;
|
||||||
|
bigEndian = true;
|
||||||
} else {
|
} else {
|
||||||
this.location++;
|
return firstLengthByte & ~0x80;
|
||||||
return bit & ~0x80;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let length = 0;
|
let length = 0;
|
||||||
|
|
||||||
for (let i = 0; i < this.bytesInLength; i++) {
|
for (let i = 0; i < bytesInLength; i++) {
|
||||||
length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);
|
if (bigEndian) {
|
||||||
|
length = (length << 8) + this.input[this.location];
|
||||||
|
} else {
|
||||||
|
length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);
|
||||||
|
}
|
||||||
this.location++;
|
this.location++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
78
src/core/operations/AutomatedValidationTestOp.mjs
Normal file
78
src/core/operations/AutomatedValidationTestOp.mjs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* @author CyberChef
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automated validation test operation
|
||||||
|
*/
|
||||||
|
class AutomatedValidationTestOp extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AutomatedValidationTestOp constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Automated Validation Test Op";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Operation used specifically to test automated parameter validation.";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Integer Number",
|
||||||
|
"type": "number",
|
||||||
|
"value": 5,
|
||||||
|
"min": 5,
|
||||||
|
"max": 10,
|
||||||
|
"integer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Real Number",
|
||||||
|
"type": "number",
|
||||||
|
"value": 1.5,
|
||||||
|
"min": 1.5,
|
||||||
|
"max": 5.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Non Empty String",
|
||||||
|
"type": "string",
|
||||||
|
"value": "hello",
|
||||||
|
"maxLength": 5,
|
||||||
|
"allowEmpty": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Empty Allowed String",
|
||||||
|
"type": "string",
|
||||||
|
"value": "",
|
||||||
|
"allowEmpty": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Non Empty Toggle String",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": {
|
||||||
|
"option": "Option A",
|
||||||
|
"string": "test"
|
||||||
|
},
|
||||||
|
"toggleValues": ["Option A", "Option B"],
|
||||||
|
"allowEmpty": false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
return "Success";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AutomatedValidationTestOp;
|
||||||
@ -30,7 +30,11 @@ class BLAKE3 extends Operation {
|
|||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Size (bytes)",
|
"name": "Size (bytes)",
|
||||||
"type": "number"
|
"type": "number",
|
||||||
|
"value": 16,
|
||||||
|
"min": 1,
|
||||||
|
"max": 65535, // arbitrary limit to prevent resource exhaustion
|
||||||
|
"integer": true,
|
||||||
}, {
|
}, {
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@ -27,7 +27,10 @@ class BitShiftLeft extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Amount",
|
"name": "Amount",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 1
|
"value": 1,
|
||||||
|
"min": 0,
|
||||||
|
"max": 7,
|
||||||
|
"integer": true,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,12 +45,15 @@ class DechunkHTTPResponse extends Operation {
|
|||||||
const lineEndingsLength = lineEndings.length;
|
const lineEndingsLength = lineEndings.length;
|
||||||
let chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16);
|
let chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16);
|
||||||
while (!isNaN(chunkSize)) {
|
while (!isNaN(chunkSize)) {
|
||||||
|
if (chunkSize === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
chunks.push(input.slice(chunkSizeEnd, chunkSize + chunkSizeEnd));
|
chunks.push(input.slice(chunkSizeEnd, chunkSize + chunkSizeEnd));
|
||||||
input = input.slice(chunkSizeEnd + chunkSize + lineEndingsLength);
|
input = input.slice(chunkSizeEnd + chunkSize + lineEndingsLength);
|
||||||
chunkSizeEnd = input.indexOf(lineEndings) + lineEndingsLength;
|
chunkSizeEnd = input.indexOf(lineEndings) + lineEndingsLength;
|
||||||
chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16);
|
chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16);
|
||||||
}
|
}
|
||||||
return chunks.join("") + input;
|
return chunks.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,9 +51,10 @@ class FromBase extends Operation {
|
|||||||
if (number.length === 1) return result;
|
if (number.length === 1) return result;
|
||||||
|
|
||||||
// Fractional part
|
// Fractional part
|
||||||
|
const radixBN = new BigNumber(radix);
|
||||||
for (let i = 0; i < number[1].length; i++) {
|
for (let i = 0; i < number[1].length; i++) {
|
||||||
const digit = new BigNumber(number[1][i], radix);
|
const digit = new BigNumber(number[1][i], radix);
|
||||||
result += digit.div(Math.pow(radix, i+1));
|
result = result.plus(digit.div(radixBN.pow(i + 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@ -74,13 +74,11 @@ class Gzip extends Operation {
|
|||||||
}
|
}
|
||||||
if (comment.length) {
|
if (comment.length) {
|
||||||
options.flags.comment = true;
|
options.flags.comment = true;
|
||||||
|
options.flags.fcomment = true;
|
||||||
options.comment = comment;
|
options.comment = comment;
|
||||||
}
|
}
|
||||||
const gzipObj = new Zlib.Gzip(new Uint8Array(input), options);
|
const gzipObj = new Zlib.Gzip(new Uint8Array(input), options);
|
||||||
const compressed = new Uint8Array(gzipObj.compress());
|
const compressed = new Uint8Array(gzipObj.compress());
|
||||||
if (options.flags.comment && !(compressed[3] & 0x10)) {
|
|
||||||
compressed[3] |= 0x10;
|
|
||||||
}
|
|
||||||
return compressed.buffer;
|
return compressed.buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -51,6 +51,18 @@ class JsonataQuery extends Operation {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const expression = jsonata(query);
|
const expression = jsonata(query);
|
||||||
|
// Override built-in base64 functions which fail in Web Worker
|
||||||
|
// context where `window` is undefined. The jsonata library falls
|
||||||
|
// back to `global.Buffer` which also does not exist in workers.
|
||||||
|
// `atob`/`btoa` are available in both browser and worker scopes.
|
||||||
|
expression.registerFunction("base64decode", (str) => {
|
||||||
|
if (typeof str === "undefined") return undefined;
|
||||||
|
return atob(str);
|
||||||
|
}, "<s-:s>");
|
||||||
|
expression.registerFunction("base64encode", (str) => {
|
||||||
|
if (typeof str === "undefined") return undefined;
|
||||||
|
return btoa(str);
|
||||||
|
}, "<s-:s>");
|
||||||
result = await expression.evaluate(jsonObj);
|
result = await expression.evaluate(jsonObj);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(
|
throw new OperationError(
|
||||||
|
|||||||
@ -87,7 +87,7 @@ class MIMEDecoding extends Operation {
|
|||||||
end = cur + j + "?=".length;
|
end = cur + j + "?=".length;
|
||||||
|
|
||||||
if (encoding.toLowerCase() === "b") {
|
if (encoding.toLowerCase() === "b") {
|
||||||
text = fromBase64(text);
|
text = fromBase64(text, undefined, "byteArray");
|
||||||
} else if (encoding.toLowerCase() === "q") {
|
} else if (encoding.toLowerCase() === "q") {
|
||||||
text = this.parseQEncodedWord(text);
|
text = this.parseQEncodedWord(text);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -31,7 +31,8 @@ class PseudoRandomNumberGenerator extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Number of bytes",
|
"name": "Number of bytes",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 32
|
"value": 32,
|
||||||
|
"min": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Output as",
|
"name": "Output as",
|
||||||
|
|||||||
100
src/core/operations/RenderPDF.mjs
Normal file
100
src/core/operations/RenderPDF.mjs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* @author Shailendra [singhshailendra.in]
|
||||||
|
* @copyright Crown Copyright 2017
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fromBase64, toBase64 } from "../lib/Base64.mjs";
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render PDF operation
|
||||||
|
*/
|
||||||
|
class RenderPDF extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RenderPDF constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Render PDF";
|
||||||
|
this.module = "File";
|
||||||
|
this.description = "Displays the input as a PDF preview. Supports Raw and Base64 input formats.";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "byteArray";
|
||||||
|
this.presentType = "html";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Input format",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Base64", "Raw"],
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
pattern: "^%PDF-",
|
||||||
|
flags: "",
|
||||||
|
args: ["Raw"],
|
||||||
|
useful: true,
|
||||||
|
output: {
|
||||||
|
mime: "application/pdf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {byteArray}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const inputFormat = args[0];
|
||||||
|
|
||||||
|
if (!input.length) return [];
|
||||||
|
|
||||||
|
// Convert input to raw bytes
|
||||||
|
switch (inputFormat) {
|
||||||
|
case "Base64":
|
||||||
|
input = fromBase64(input, undefined, "byteArray");
|
||||||
|
break;
|
||||||
|
case "Raw":
|
||||||
|
default:
|
||||||
|
input = Utils.strToByteArray(input);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check PDF signature
|
||||||
|
if (
|
||||||
|
input[0] !== 0x25 || // %
|
||||||
|
input[1] !== 0x50 || // P
|
||||||
|
input[2] !== 0x44 || // D
|
||||||
|
input[3] !== 0x46 // F
|
||||||
|
) {
|
||||||
|
throw new OperationError("Input does not appear to be a PDF file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the PDF using HTML for web apps.
|
||||||
|
*
|
||||||
|
* @param {byteArray} data
|
||||||
|
* @returns {html}
|
||||||
|
*/
|
||||||
|
async present(data) {
|
||||||
|
if (!data.length) return "";
|
||||||
|
|
||||||
|
const base64 = toBase64(data);
|
||||||
|
const dataURI = "data:application/pdf;base64," + base64;
|
||||||
|
|
||||||
|
return `<iframe src="${dataURI}" style="width:100%;height:100%;border:1px solid #ccc;"></iframe>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RenderPDF;
|
||||||
@ -75,9 +75,16 @@ class SetDifference extends Operation {
|
|||||||
* @returns {Object[]}
|
* @returns {Object[]}
|
||||||
*/
|
*/
|
||||||
runSetDifference(a, b) {
|
runSetDifference(a, b) {
|
||||||
|
const excluded = new Set(b);
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
return a
|
return a
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
return b.indexOf(item) === -1;
|
if (excluded.has(item) || seen.has(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seen.add(item);
|
||||||
|
return true;
|
||||||
})
|
})
|
||||||
.join(this.itemDelimiter);
|
.join(this.itemDelimiter);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,9 +75,16 @@ class SetIntersection extends Operation {
|
|||||||
* @returns {Object[]}
|
* @returns {Object[]}
|
||||||
*/
|
*/
|
||||||
runIntersect(a, b) {
|
runIntersect(a, b) {
|
||||||
|
const included = new Set(b);
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
return a
|
return a
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
return b.indexOf(item) > -1;
|
if (!included.has(item) || seen.has(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seen.add(item);
|
||||||
|
return true;
|
||||||
})
|
})
|
||||||
.join(this.itemDelimiter);
|
.join(this.itemDelimiter);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,16 @@ class ShowOnMap extends Operation {
|
|||||||
}
|
}
|
||||||
latLong = latLong.replace(/[,]$/, "");
|
latLong = latLong.replace(/[,]$/, "");
|
||||||
latLong = latLong.replace(/°/g, "");
|
latLong = latLong.replace(/°/g, "");
|
||||||
|
|
||||||
|
// The map requires a latitude and longitude pair. If the conversion only produced a
|
||||||
|
// single value (e.g. because the chosen input delimiter didn't match the input), bail
|
||||||
|
// out with a helpful message rather than passing it on to the map, which would throw an
|
||||||
|
// uncaught TypeError in the browser.
|
||||||
|
const coords = latLong.split(",").map(v => v.trim());
|
||||||
|
if (coords.length !== 2 || coords.some(v => v === "" || isNaN(Number(v)))) {
|
||||||
|
throw new OperationError(`Could not show coordinates '${latLong}' on the map. Expected a latitude and longitude pair - check that the input format and delimiter are correct.`);
|
||||||
|
}
|
||||||
|
|
||||||
return latLong;
|
return latLong;
|
||||||
}
|
}
|
||||||
return input;
|
return input;
|
||||||
|
|||||||
@ -28,7 +28,10 @@ class ToBase extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Radix",
|
"name": "Radix",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 36
|
"value": 36,
|
||||||
|
"min": 2,
|
||||||
|
"max": 36,
|
||||||
|
"integer": true,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -43,9 +46,6 @@ class ToBase extends Operation {
|
|||||||
throw new OperationError("Error: Input must be a number");
|
throw new OperationError("Error: Input must be a number");
|
||||||
}
|
}
|
||||||
const radix = args[0];
|
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);
|
return input.toString(radix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,10 @@ class ToBinary extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Byte Length",
|
"name": "Byte Length",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 8
|
"value": 8,
|
||||||
|
"min": 1,
|
||||||
|
"max": 256, // arbitrary - significantly larger than word size for any known machine ("640k ought to be enough for anybody")
|
||||||
|
"integer": true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ class URLEncode extends Operation {
|
|||||||
this.module = "URL";
|
this.module = "URL";
|
||||||
this.description = "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.<br><br>e.g. <code>=</code> becomes <code>%3d</code>";
|
this.description = "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.<br><br>e.g. <code>=</code> becomes <code>%3d</code>";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Percent-encoding";
|
this.infoURL = "https://wikipedia.org/wiki/Percent-encoding";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
@ -33,34 +33,38 @@ class URLEncode extends Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const encodeAll = args[0];
|
const encodeAll = args[0];
|
||||||
return encodeAll ? this.encodeAllChars(input) : encodeURI(input);
|
return this.encodeBytes(input, encodeAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode characters in URL outside of encodeURI() function spec
|
* Encode bytes in URL using percent encoding.
|
||||||
*
|
*
|
||||||
* @param {string} str
|
* @param {byteArray} bytes
|
||||||
|
* @param {boolean} encodeAll
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
encodeAllChars (str) {
|
encodeBytes(bytes, encodeAll) {
|
||||||
// TODO Do this programmatically
|
const safeChars = encodeAll ?
|
||||||
return encodeURIComponent(str)
|
/^[A-Za-z0-9]$/ :
|
||||||
.replace(/!/g, "%21")
|
/^[A-Za-z0-9:/?#[\]@!$&'()*+,;=%]$/;
|
||||||
.replace(/#/g, "%23")
|
|
||||||
.replace(/'/g, "%27")
|
let output = "";
|
||||||
.replace(/\(/g, "%28")
|
|
||||||
.replace(/\)/g, "%29")
|
for (const byte of bytes) {
|
||||||
.replace(/\*/g, "%2A")
|
const char = String.fromCharCode(byte);
|
||||||
.replace(/-/g, "%2D")
|
|
||||||
.replace(/\./g, "%2E")
|
output += safeChars.test(char) ?
|
||||||
.replace(/_/g, "%5F")
|
char :
|
||||||
.replace(/~/g, "%7E");
|
"%" + byte.toString(16).toUpperCase().padStart(2, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,8 @@ class UnescapeUnicodeCharacters extends Operation {
|
|||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const prefix = prefixToRegex[args[0]],
|
const prefix = prefixToRegex[args[0]],
|
||||||
regex = new RegExp(prefix+"([a-f\\d]{4})", "ig");
|
quantifier = args[0] === "U+" ? "{4,6}" : "{4}",
|
||||||
|
regex = new RegExp(prefix+"([a-f\\d]"+quantifier+")", "ig");
|
||||||
let output = "",
|
let output = "",
|
||||||
m,
|
m,
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|||||||
@ -31,7 +31,10 @@ class XORBruteForce extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Key length",
|
"name": "Key length",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 1
|
"value": 1,
|
||||||
|
"min": 1,
|
||||||
|
"max": 2,
|
||||||
|
"integer": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Sample length",
|
"name": "Sample length",
|
||||||
|
|||||||
@ -7,12 +7,12 @@
|
|||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import { toHex } from "../lib/Hex.mjs";
|
import { toHex } from "../lib/Hex.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XOR Checksum operation
|
* XOR Checksum operation
|
||||||
*/
|
*/
|
||||||
class XORChecksum extends Operation {
|
class XORChecksum extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XORChecksum constructor
|
* XORChecksum constructor
|
||||||
*/
|
*/
|
||||||
@ -21,7 +21,8 @@ class XORChecksum extends Operation {
|
|||||||
|
|
||||||
this.name = "XOR Checksum";
|
this.name = "XOR Checksum";
|
||||||
this.module = "Crypto";
|
this.module = "Crypto";
|
||||||
this.description = "XOR Checksum splits the input into blocks of a configurable size and performs the XOR operation on these blocks.";
|
this.description =
|
||||||
|
"XOR Checksum splits the input into blocks of a configurable size and performs the XOR operation on these blocks.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/XOR";
|
this.infoURL = "https://wikipedia.org/wiki/XOR";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
@ -29,7 +30,7 @@ class XORChecksum extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Blocksize",
|
name: "Blocksize",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 4
|
value: 4,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -41,6 +42,12 @@ class XORChecksum extends Operation {
|
|||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const blocksize = args[0];
|
const blocksize = args[0];
|
||||||
|
|
||||||
|
|
||||||
|
if (!Number.isInteger(blocksize) || blocksize <= 0) {
|
||||||
|
throw new OperationError("Blocksize must be a positive integer.");
|
||||||
|
}
|
||||||
|
|
||||||
input = new Uint8Array(input);
|
input = new Uint8Array(input);
|
||||||
|
|
||||||
const res = Array(blocksize);
|
const res = Array(blocksize);
|
||||||
|
|||||||
@ -193,6 +193,8 @@ export function _wrap(OpClass) {
|
|||||||
wrapped = async (input, args=null) => {
|
wrapped = async (input, args=null) => {
|
||||||
const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);
|
const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);
|
||||||
|
|
||||||
|
opInstance.validateIngredients(transformedArgs);
|
||||||
|
|
||||||
// SPECIAL CASE for Magic. Other flowControl operations will
|
// SPECIAL CASE for Magic. Other flowControl operations will
|
||||||
// not work because the opList is not passed in.
|
// not work because the opList is not passed in.
|
||||||
if (isFlowControl) {
|
if (isFlowControl) {
|
||||||
@ -229,6 +231,7 @@ export function _wrap(OpClass) {
|
|||||||
*/
|
*/
|
||||||
wrapped = (input, args=null) => {
|
wrapped = (input, args=null) => {
|
||||||
const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);
|
const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);
|
||||||
|
opInstance.validateIngredients(transformedArgs);
|
||||||
const result = opInstance.run(transformedInput, transformedArgs);
|
const result = opInstance.run(transformedInput, transformedArgs);
|
||||||
return new NodeDish({
|
return new NodeDish({
|
||||||
value: result,
|
value: result,
|
||||||
|
|||||||
@ -58,9 +58,10 @@ class HTMLOperation {
|
|||||||
|
|
||||||
if (this.description) {
|
if (this.description) {
|
||||||
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
|
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
|
||||||
|
const content = Utils.escapeHtml(this.description + infoLink);
|
||||||
|
|
||||||
html += ` data-container='body' data-toggle='popover' data-placement='right'
|
html += ` data-container='body' data-toggle='popover' data-placement='right'
|
||||||
data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover'
|
data-content="${content}" data-html='true' data-trigger='hover'
|
||||||
data-boundary='viewport' role='button'`;
|
data-boundary='viewport' role='button'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,14 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
user-select: auto;
|
user-select: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#output-html > img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
#output-text.html-output .cm-line .cm-widgetBuffer,
|
#output-text.html-output .cm-line .cm-widgetBuffer,
|
||||||
#output-text.html-output .cm-line>br {
|
#output-text.html-output .cm-line>br {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@ -56,6 +56,32 @@ module.exports = {
|
|||||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Play Media']").to.be.present;
|
browser.expect.element("//li[contains(@class, 'operation') and text()='Play Media']").to.be.present;
|
||||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Disassemble x86']").to.be.present;
|
browser.expect.element("//li[contains(@class, 'operation') and text()='Disassemble x86']").to.be.present;
|
||||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Register']").to.be.present;
|
browser.expect.element("//li[contains(@class, 'operation') and text()='Register']").to.be.present;
|
||||||
|
browser.expect.element("//li[contains(@class, 'operation') and text()='Escape Smart Characters']").to.be.present;
|
||||||
|
},
|
||||||
|
|
||||||
|
"Operation popover descriptions render HTML safely": browser => {
|
||||||
|
const favouritesCat = "//a[contains(@class, 'category-title') and contains(@data-target, '#catFavourites')]",
|
||||||
|
op = "//ul[@id='search-results']//li[contains(@class, 'operation') and contains(., 'Escape Smart Characters')]";
|
||||||
|
|
||||||
|
browser
|
||||||
|
.useCss()
|
||||||
|
.clearValue("#search")
|
||||||
|
.setValue("#search", "Escape Smart Characters")
|
||||||
|
.useXpath()
|
||||||
|
.waitForElementVisible(op, 1000)
|
||||||
|
.moveToElement(op, 10, 10)
|
||||||
|
.useCss()
|
||||||
|
.waitForElementVisible(".popover-body code:last-of-type", 1000)
|
||||||
|
.expect.element(".popover-body code:last-of-type").text.to.contain("\"Hello\" -- world...");
|
||||||
|
|
||||||
|
browser
|
||||||
|
.useCss()
|
||||||
|
.moveToElement("#operations .title", 1, 1)
|
||||||
|
.waitForElementNotPresent(".popover-body", 1000)
|
||||||
|
.clearValue("#search")
|
||||||
|
.useXpath()
|
||||||
|
.getLocationInView(favouritesCat)
|
||||||
|
.click(favouritesCat);
|
||||||
},
|
},
|
||||||
|
|
||||||
"Recipe can be run": browser => {
|
"Recipe can be run": browser => {
|
||||||
|
|||||||
@ -218,6 +218,7 @@ module.exports = {
|
|||||||
testOpHtml(browser, "JSON Beautify", "{a:1}", ".json-dict .json-literal", "1");
|
testOpHtml(browser, "JSON Beautify", "{a:1}", ".json-dict .json-literal", "1");
|
||||||
// testOp(browser, "JSON Minify", "test input", "test_output");
|
// testOp(browser, "JSON Minify", "test input", "test_output");
|
||||||
// testOp(browser, "JSON to CSV", "test input", "test_output");
|
// testOp(browser, "JSON to CSV", "test input", "test_output");
|
||||||
|
testOp(browser, "Jsonata Query", '{"a": "SGVsbG8gV29ybGQh"}', '"Hello World!"', ["$base64decode($.a)"]);
|
||||||
// testOp(browser, "JWT Decode", "test input", "test_output");
|
// testOp(browser, "JWT Decode", "test input", "test_output");
|
||||||
// testOp(browser, "JWT Sign", "test input", "test_output");
|
// testOp(browser, "JWT Sign", "test input", "test_output");
|
||||||
// testOp(browser, "JWT Verify", "test input", "test_output");
|
// testOp(browser, "JWT Verify", "test input", "test_output");
|
||||||
@ -492,7 +493,22 @@ function testOpImage(browser, opName, filename, args=[]) {
|
|||||||
|
|
||||||
browser
|
browser
|
||||||
.waitForElementVisible("#output-html img")
|
.waitForElementVisible("#output-html img")
|
||||||
.expect.element("#output-html img").to.have.css("width").which.matches(/^[^0]\d*px/);
|
.expect.element("#output-html img").to.have.css("width").which.matches(/^(?!0+(?:\.0+)?px$)\d+(?:\.\d+)?px$/);
|
||||||
|
|
||||||
|
browser.execute(function() {
|
||||||
|
const output = document.getElementById("output-html");
|
||||||
|
const img = output.querySelector("img");
|
||||||
|
const outputRect = output.getBoundingClientRect();
|
||||||
|
const imgRect = img.getBoundingClientRect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
imageFitsWidth: imgRect.width <= outputRect.width,
|
||||||
|
imageFitsHeight: imgRect.height <= outputRect.height,
|
||||||
|
};
|
||||||
|
}, [], function({value}) {
|
||||||
|
browser.expect(value.imageFitsWidth).to.be.equal(true);
|
||||||
|
browser.expect(value.imageFitsHeight).to.be.equal(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @function
|
/** @function
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import "./tests/NodeDish.mjs";
|
|||||||
import "./tests/Utils.mjs";
|
import "./tests/Utils.mjs";
|
||||||
import "./tests/Categories.mjs";
|
import "./tests/Categories.mjs";
|
||||||
import "./tests/lib/BigIntUtils.mjs";
|
import "./tests/lib/BigIntUtils.mjs";
|
||||||
|
import "./tests/lib/ChartsProtocolPrototypePollution.mjs";
|
||||||
|
|
||||||
const testStatus = {
|
const testStatus = {
|
||||||
allTestsPassing: true,
|
allTestsPassing: true,
|
||||||
|
|||||||
90
tests/node/tests/lib/ChartsProtocolPrototypePollution.mjs
Normal file
90
tests/node/tests/lib/ChartsProtocolPrototypePollution.mjs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import TestRegister from "../../../lib/TestRegister.mjs";
|
||||||
|
import {getSeriesValues} from "../../../../src/core/lib/Charts.mjs";
|
||||||
|
import {objToTable} from "../../../../src/core/lib/Protocol.mjs";
|
||||||
|
import SeriesChart from "../../../../src/core/operations/SeriesChart.mjs";
|
||||||
|
import ParseUDP from "../../../../src/core/operations/ParseUDP.mjs";
|
||||||
|
import it from "../../assertionHandler.mjs";
|
||||||
|
import assert from "assert";
|
||||||
|
|
||||||
|
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
||||||
|
|
||||||
|
TestRegister.addApiTests([
|
||||||
|
it("Charts: should not pollute Object.prototype from a __proto__ series name", () => {
|
||||||
|
const xVal = "<img src=x onerror=alert(1)>";
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = getSeriesValues(`__proto__,${xVal},1`, "\n", ",", false);
|
||||||
|
|
||||||
|
assert.equal(Object.prototype[xVal], undefined);
|
||||||
|
assert.deepEqual(result.xValues, [xVal]);
|
||||||
|
assert.equal(result.series.length, 1);
|
||||||
|
assert.equal(result.series[0].name, "__proto__");
|
||||||
|
assert.equal(Object.getPrototypeOf(result.series[0].data), null);
|
||||||
|
assert(hasOwn(result.series[0].data, xVal));
|
||||||
|
assert.equal(result.series[0].data[xVal], 1);
|
||||||
|
} finally {
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Charts: should keep __proto__ x-axis names as own data keys", () => {
|
||||||
|
const result = getSeriesValues("safe,__proto__,1", "\n", ",", false);
|
||||||
|
|
||||||
|
assert.equal(result.series.length, 1);
|
||||||
|
assert.equal(Object.getPrototypeOf(result.series[0].data), null);
|
||||||
|
assert(hasOwn(result.series[0].data, "__proto__"));
|
||||||
|
assert.equal(result.series[0].data.__proto__, 1);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Protocol: should ignore inherited properties when rendering tables", () => {
|
||||||
|
const inheritedKey = "<img src=x onerror=alert(1)>";
|
||||||
|
delete Object.prototype[inheritedKey];
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object.prototype[inheritedKey] = "polluted";
|
||||||
|
|
||||||
|
const html = objToTable({safe: "value"});
|
||||||
|
|
||||||
|
assert(!html.includes(inheritedKey));
|
||||||
|
assert(!html.includes("polluted"));
|
||||||
|
assert(html.includes("safe"));
|
||||||
|
assert(html.includes("value"));
|
||||||
|
} finally {
|
||||||
|
delete Object.prototype[inheritedKey];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Protocol: should escape table keys and scalar values", () => {
|
||||||
|
const obj = {
|
||||||
|
"<b>field</b>": "<img src=x onerror=alert(1)>",
|
||||||
|
};
|
||||||
|
|
||||||
|
const html = objToTable(obj);
|
||||||
|
|
||||||
|
assert(!html.includes("<b>field</b>"));
|
||||||
|
assert(!html.includes("<img src=x onerror=alert(1)>"));
|
||||||
|
assert(html.includes("<b>field</b>"));
|
||||||
|
assert(html.includes("<img src=x onerror=alert(1)>"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Series chart and Parse UDP: should not expose polluted prototype data as HTML", () => {
|
||||||
|
const xVal = "<img src=x onerror=alert(document.domain)>";
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const chartHtml = new SeriesChart().run(
|
||||||
|
`__proto__,${xVal},1`,
|
||||||
|
["Line feed", "Comma", "", 1, "red"]
|
||||||
|
);
|
||||||
|
assert.equal(Object.prototype[xVal], undefined);
|
||||||
|
|
||||||
|
const parseUDP = new ParseUDP();
|
||||||
|
const tableHtml = parseUDP.present(parseUDP.run(chartHtml, ["Raw"]));
|
||||||
|
|
||||||
|
assert(!/<img|onerror|alert\(/.test(tableHtml));
|
||||||
|
} finally {
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]);
|
||||||
121
tests/operations/tests/AutomatedValidation.mjs
Normal file
121
tests/operations/tests/AutomatedValidation.mjs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* Automated Parameter Validation tests
|
||||||
|
*
|
||||||
|
* @author CyberChef
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Valid values",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Success",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Integer Number under min limit",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Integer Number must be greater than or equal to 5.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [4, 1.5, "hello", "", { "option": "Option A", "string": "test" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Integer Number over max limit",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Integer Number must be less than or equal to 10.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [11, 1.5, "hello", "", { "option": "Option A", "string": "test" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Integer Number not an integer",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Integer Number must be an integer.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5.5, 1.5, "hello", "", { "option": "Option A", "string": "test" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Real Number under min limit",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Real Number must be greater than or equal to 1.5.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.4, "hello", "", { "option": "Option A", "string": "test" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Real Number over max limit",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Real Number must be less than or equal to 5.5.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 5.6, "hello", "", { "option": "Option A", "string": "test" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Non Empty String over maxLength limit",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Non Empty String length cannot exceed 5.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "helloooo", "", { "option": "Option A", "string": "test" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Non Empty String is empty",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Non Empty String cannot be empty.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "", "", { "option": "Option A", "string": "test" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Empty Allowed String is empty (allowed)",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Success",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Non Empty Toggle String is empty",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Non Empty Toggle String cannot be empty.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "hello", "", { "option": "Option A", "string": "" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
@ -69,5 +69,43 @@ TestRegister.addTests([
|
|||||||
{ "op": "BLAKE3",
|
{ "op": "BLAKE3",
|
||||||
"args": [16390, "ThiskeyisexactlythirtytwoBytesLo"] }
|
"args": [16390, "ThiskeyisexactlythirtytwoBytesLo"] }
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
// test vectors from https://github.com/BLAKE3-team/BLAKE3/blob/master/test_vectors/test_vectors.json
|
||||||
|
{
|
||||||
|
name: "BLAKE3: Std test vector - 0 bytes input, plain hash",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262e00f03e7b69af26b7faaf09fcd333050338ddfe085b8cc869ca98b206c08243a26f5487789e8f660afe6c99ef9e0c52b92e7393024a80459cf91f476f9ffdbda7001c22e159b402631f277ca96f2defdf1078282314e763699a31c5363165421cce14d",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "BLAKE3",
|
||||||
|
"args": [131, ""]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BLAKE3: Std test vector - 0 bytes input, keyed hash",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "92b2b75604ed3c761f9d6f62392c8a9227ad0ea3f09573e783f1498a4ed60d26b18171a2f22a4b94822c701f107153dba24918c4bae4d2945c20ece13387627d3b73cbf97b797d5e59948c7ef788f54372df45e45e4293c7dc18c1d41144a9758be58960856be1eabbe22c2653190de560ca3b2ac4aa692a9210694254c371e851bc8f",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "BLAKE3",
|
||||||
|
"args": [131, "whats the Elvish word for friend"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BLAKE3: Std test vector - 7 bytes input, keyed hash",
|
||||||
|
input: "0001020304050607",
|
||||||
|
expectedOutput: "be2f5495c61cba1bb348a34948c004045e3bd4dae8f0fe82bf44d0da245a060048eb5e68ce6dea1eb0229e144f578b3aa7e9f4f85febd135df8525e6fe40c6f0340d13dd09b255ccd5112a94238f2be3c0b5b7ecde06580426a93e0708555a265305abf86d874e34b4995b788e37a823491f25127a502fe0704baa6bfdf04e76c13276",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "BLAKE3",
|
||||||
|
"args": [131, "whats the Elvish word for friend"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
66
tests/operations/tests/DechunkHTTPResponse.mjs
Normal file
66
tests/operations/tests/DechunkHTTPResponse.mjs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* DechunkHTTPResponse operation tests.
|
||||||
|
*
|
||||||
|
* @author Willi Ballenthin
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Dechunk HTTP response: CRLF line endings",
|
||||||
|
input: "7\r\nMozilla\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\n",
|
||||||
|
expectedOutput: "MozillaDeveloperNetwork",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Dechunk HTTP response",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dechunk HTTP response: LF line endings",
|
||||||
|
input: "7\nMozilla\n9\nDeveloper\n7\nNetwork\n0\n\n",
|
||||||
|
expectedOutput: "MozillaDeveloperNetwork",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Dechunk HTTP response",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dechunk HTTP response: single chunk",
|
||||||
|
input: "5\r\nHello\r\n0\r\n\r\n",
|
||||||
|
expectedOutput: "Hello",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Dechunk HTTP response",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dechunk HTTP response: trailing headers discarded",
|
||||||
|
input: "7\nMozilla\n9\nDeveloper\n7\nNetwork\n0\nExpires: Wed, 21 Oct 2015 07:28:00 GMT\n",
|
||||||
|
expectedOutput: "MozillaDeveloperNetwork",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Dechunk HTTP response",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dechunk HTTP response: hex chunk sizes",
|
||||||
|
input: "a\r\n0123456789\r\n0\r\n\r\n",
|
||||||
|
expectedOutput: "0123456789",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Dechunk HTTP response",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
66
tests/operations/tests/FromBase.mjs
Normal file
66
tests/operations/tests/FromBase.mjs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* From Base operation tests.
|
||||||
|
*
|
||||||
|
* @author Willi Ballenthin
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "From Base: binary integer",
|
||||||
|
input: "1010",
|
||||||
|
expectedOutput: "10",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Base",
|
||||||
|
args: [2],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "From Base: binary fraction",
|
||||||
|
input: "10.1",
|
||||||
|
expectedOutput: "2.5",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Base",
|
||||||
|
args: [2],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "From Base: hex fraction",
|
||||||
|
input: "a.8",
|
||||||
|
expectedOutput: "10.5",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Base",
|
||||||
|
args: [16],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "From Base: octal integer",
|
||||||
|
input: "77",
|
||||||
|
expectedOutput: "63",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Base",
|
||||||
|
args: [8],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "From Base: octal fraction",
|
||||||
|
input: "7.4",
|
||||||
|
expectedOutput: "7.5",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Base",
|
||||||
|
args: [8],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
@ -86,4 +86,64 @@ TestRegister.addTests([
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Gzip: Comment with checksum round-trips through Gunzip",
|
||||||
|
input: "hello hello hello",
|
||||||
|
expectedOutput: "hello hello hello",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Gzip",
|
||||||
|
args: ["Dynamic Huffman Coding", "", "test", true]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "Gunzip",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gzip: Filename and comment with checksum round-trips through Gunzip",
|
||||||
|
input: "The quick brown fox jumped over the slow dog",
|
||||||
|
expectedOutput: "The quick brown fox jumped over the slow dog",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Gzip",
|
||||||
|
args: ["Dynamic Huffman Coding", "file.txt", "a comment", true]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "Gunzip",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gzip: No comment, with checksum round-trips through Gunzip",
|
||||||
|
input: "The quick brown fox jumped over the slow dog",
|
||||||
|
expectedOutput: "The quick brown fox jumped over the slow dog",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Gzip",
|
||||||
|
args: ["Dynamic Huffman Coding", "", "", true]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "Gunzip",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gzip: No options round-trips through Gunzip",
|
||||||
|
input: "The quick brown fox jumped over the slow dog",
|
||||||
|
expectedOutput: "The quick brown fox jumped over the slow dog",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Gzip",
|
||||||
|
args: ["Dynamic Huffman Coding", "", "", false]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "Gunzip",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -129,7 +129,7 @@ TestRegister.addTests([
|
|||||||
{
|
{
|
||||||
name: "To Hexdump: Width too large",
|
name: "To Hexdump: Width too large",
|
||||||
input: "H",
|
input: "H",
|
||||||
expectedOutput: "Width must be no more than 65536",
|
expectedOutput: "Width must be less than or equal to 65536.",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "To Hexdump",
|
op: "To Hexdump",
|
||||||
|
|||||||
@ -548,4 +548,27 @@ TestRegister.addTests([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// Base64 functions (issue #2063)
|
||||||
|
{
|
||||||
|
name: "Jsonata: $base64decode",
|
||||||
|
input: "{}",
|
||||||
|
expectedOutput: '"Hello World!"',
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Jsonata Query",
|
||||||
|
args: ['$base64decode("SGVsbG8gV29ybGQh")'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Jsonata: $base64encode",
|
||||||
|
input: "{}",
|
||||||
|
expectedOutput: '"SGVsbG8gV29ybGQh"',
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Jsonata Query",
|
||||||
|
args: ['$base64encode("Hello World!")'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -75,6 +75,39 @@ TestRegister.addTests([
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "UTF-8 Base64 non-ASCII",
|
||||||
|
input: "Subject: =?UTF-8?B?Y2Fmw6k=?=",
|
||||||
|
expectedOutput: "Subject: café",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "MIME Decoding",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UTF-8 Base64 multibyte CJK",
|
||||||
|
input: "Subject: =?UTF-8?B?5pel5pys6Kqe?=",
|
||||||
|
expectedOutput: "Subject: 日本語",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "MIME Decoding",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UTF-8 Base64 ASCII-only",
|
||||||
|
input: "Subject: =?UTF-8?B?aGVsbG8=?=",
|
||||||
|
expectedOutput: "Subject: hello",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "MIME Decoding",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "ISO Decoding",
|
name: "ISO Decoding",
|
||||||
input: "From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>\nTo: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>\nCC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>\nSubject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\n=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=",
|
input: "From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>\nTo: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>\nCC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>\nSubject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\n=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=",
|
||||||
|
|||||||
33
tests/operations/tests/Median.mjs
Normal file
33
tests/operations/tests/Median.mjs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Median operation tests.
|
||||||
|
*
|
||||||
|
* @author copilot-swe-agent[bot]
|
||||||
|
* @copyright Crown Copyright 2018
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Median: odd-length input",
|
||||||
|
input: "10 1 2",
|
||||||
|
expectedOutput: "2",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Median",
|
||||||
|
args: ["Space"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Median: even-length input",
|
||||||
|
input: "10 1 2 5",
|
||||||
|
expectedOutput: "3.5",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Median",
|
||||||
|
args: ["Space"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
@ -52,5 +52,46 @@ TestRegister.addTests([
|
|||||||
"args": [1, 4, true] // length value is patently wrong, should be ignored by BER.
|
"args": [1, 4, true] // length value is patently wrong, should be ignored by BER.
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Parse TLV: BER long-form length (two-byte length encoding)",
|
||||||
|
input: "\x01\x82\x01\x00" + "A".repeat(256) + "\x02\x03\x41\x42\x43",
|
||||||
|
expectedOutput: JSON.stringify([
|
||||||
|
{"key": [1], "length": 256, "value": Array(256).fill(65)},
|
||||||
|
{"key": [2], "length": 3, "value": [65, 66, 67]}
|
||||||
|
], null, 4),
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Parse TLV",
|
||||||
|
"args": [1, 1, true]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Parse TLV: BER long-form length (one-byte length encoding)",
|
||||||
|
input: "\x01\x81\x80" + "B".repeat(128),
|
||||||
|
expectedOutput: JSON.stringify([
|
||||||
|
{"key": [1], "length": 128, "value": Array(128).fill(66)}
|
||||||
|
], null, 4),
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Parse TLV",
|
||||||
|
"args": [1, 1, true]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Parse TLV: BER multiple entries with mixed short and long-form lengths",
|
||||||
|
input: "\x01\x05\x48\x65\x6c\x6c\x6f\x02\x81\x05\x57\x6f\x72\x6c\x64",
|
||||||
|
expectedOutput: JSON.stringify([
|
||||||
|
{"key": [1], "length": 5, "value": [72, 101, 108, 108, 111]},
|
||||||
|
{"key": [2], "length": 5, "value": [87, 111, 114, 108, 100]}
|
||||||
|
], null, 4),
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Parse TLV",
|
||||||
|
"args": [1, 1, true]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|||||||
37
tests/operations/tests/RenderPDF.mjs
Normal file
37
tests/operations/tests/RenderPDF.mjs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* RenderPDF tests.
|
||||||
|
*
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "RenderPDF",
|
||||||
|
input: "Not a PDF",
|
||||||
|
expectedOutput: "Input does not appear to be a PDF file.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Render PDF",
|
||||||
|
args: ["Raw"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RenderPDF",
|
||||||
|
input: "",
|
||||||
|
expectedMatch: /^<iframe src="data:application\/pdf;base64,JVBERi0xLjAKCjEgMCBvYmogPDwg/,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Generate QR Code",
|
||||||
|
"args": ["PDF", 1, 1, "Low"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Render PDF",
|
||||||
|
"args": ["Raw"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
@ -53,4 +53,26 @@ TestRegister.addTests([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Set Difference: duplicates in first set are removed",
|
||||||
|
input: "red,red,blue\n\nblue",
|
||||||
|
expectedOutput: "red",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Set Difference",
|
||||||
|
args: ["\n\n", ","],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set Difference: duplicates in both sets",
|
||||||
|
input: "1 1 2 2 3\n\n2 2 3 3",
|
||||||
|
expectedOutput: "1",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Set Difference",
|
||||||
|
args: ["\n\n", " "],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -52,5 +52,27 @@ TestRegister.addTests([
|
|||||||
args: ["z", "-"],
|
args: ["z", "-"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name: "Set Intersection: duplicates in first set are removed",
|
||||||
|
input: "red,red,blue\n\nred,blue",
|
||||||
|
expectedOutput: "red,blue",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Set Intersection",
|
||||||
|
args: ["\n\n", ","],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set Intersection: duplicates in both sets",
|
||||||
|
input: "1 1 2 2 3\n\n2 2 3 3 4",
|
||||||
|
expectedOutput: "2 3",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Set Intersection",
|
||||||
|
args: ["\n\n", " "],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
39
tests/operations/tests/ShowOnMap.mjs
Normal file
39
tests/operations/tests/ShowOnMap.mjs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Show on map tests
|
||||||
|
*
|
||||||
|
* @author Leon Zandman [leon@wirwar.com]
|
||||||
|
*
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Show on map: valid coordinate pair",
|
||||||
|
input: "51.5007, -0.1246",
|
||||||
|
// The presented output is the Leaflet map HTML; just check the coordinates made it through.
|
||||||
|
expectedMatch: /51\.5007,-0\.1246/,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Show on map",
|
||||||
|
args: [13, "Auto", "Auto"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Regression test: a comma-separated input with the delimiter set to "\n" used to be
|
||||||
|
// mis-detected as a single Degrees Decimal Minutes value (1° 24' = 1.4°), producing a single
|
||||||
|
// coordinate. That single value was then passed to Leaflet's setView([1.4], ...), throwing
|
||||||
|
// an uncaught "Cannot read properties of null (reading 'lat')" TypeError in the browser.
|
||||||
|
name: "Show on map: single value is rejected with a helpful error",
|
||||||
|
input: "1, 24",
|
||||||
|
expectedOutput: "Could not show coordinates '1.4' on the map. Expected a latitude and longitude pair - check that the input format and delimiter are correct.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Show on map",
|
||||||
|
args: [13, "Auto", "\\n"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
@ -89,4 +89,30 @@ TestRegister.addTests([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "URLEncode: encodes UTF-8 text as UTF-8 bytes",
|
||||||
|
input: "你好",
|
||||||
|
expectedOutput: "%E4%BD%A0%E5%A5%BD",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "URL Encode",
|
||||||
|
args: [false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URLEncode: preserves raw bytes from From Hex",
|
||||||
|
input: "6c6567697466696c6580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090746869737761737375706f736564746f6265616e6578706c6f6974",
|
||||||
|
expectedOutput: "legitfile%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90thiswassuposedtobeanexploit",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Hex",
|
||||||
|
args: ["None"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "URL Encode",
|
||||||
|
args: [false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
88
tests/operations/tests/UnescapeUnicodeCharacters.mjs
Normal file
88
tests/operations/tests/UnescapeUnicodeCharacters.mjs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Unescape Unicode Characters operation tests.
|
||||||
|
*
|
||||||
|
* @author williballenthin
|
||||||
|
* @copyright Crown Copyright 2024
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Unescape Unicode Characters: \\u 4-digit BMP",
|
||||||
|
input: "\\u03c3\\u03bf\\u03c5",
|
||||||
|
expectedOutput: "σου",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Unescape Unicode Characters",
|
||||||
|
args: ["\\u"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unescape Unicode Characters: %u 4-digit BMP",
|
||||||
|
input: "%u03c3%u03bf%u03c5",
|
||||||
|
expectedOutput: "σου",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Unescape Unicode Characters",
|
||||||
|
args: ["%u"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unescape Unicode Characters: U+ 4-digit BMP",
|
||||||
|
input: "U+0041",
|
||||||
|
expectedOutput: "A",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Unescape Unicode Characters",
|
||||||
|
args: ["U+"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unescape Unicode Characters: U+ 5-digit astral plane emoji",
|
||||||
|
input: "U+1F600",
|
||||||
|
expectedOutput: "\u{1F600}",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Unescape Unicode Characters",
|
||||||
|
args: ["U+"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unescape Unicode Characters: U+ 6-digit zero-padded",
|
||||||
|
input: "U+000041",
|
||||||
|
expectedOutput: "A",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Unescape Unicode Characters",
|
||||||
|
args: ["U+"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unescape Unicode Characters: U+ mixed lengths",
|
||||||
|
input: "U+0041 U+1F600 U+000042",
|
||||||
|
expectedOutput: "A \u{1F600} B",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Unescape Unicode Characters",
|
||||||
|
args: ["U+"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unescape Unicode Characters: passthrough with no matches",
|
||||||
|
input: "hello world",
|
||||||
|
expectedOutput: "hello world",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Unescape Unicode Characters",
|
||||||
|
args: ["\\u"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
@ -88,8 +88,8 @@ module.exports = {
|
|||||||
from: "tesseract/**/*",
|
from: "tesseract/**/*",
|
||||||
to: "assets/"
|
to: "assets/"
|
||||||
}, {
|
}, {
|
||||||
context: "node_modules/tesseract.js/",
|
context: "node_modules/tesseract.js/dist",
|
||||||
from: "dist/worker.min.js",
|
from: "worker.min.js",
|
||||||
to: "assets/tesseract"
|
to: "assets/tesseract"
|
||||||
}, {
|
}, {
|
||||||
context: "node_modules/tesseract.js-core/",
|
context: "node_modules/tesseract.js-core/",
|
||||||
@ -221,7 +221,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{ // Third party images are inlined
|
{ // Third party images are inlined
|
||||||
test: /\.(png|jpg|gif)$/,
|
test: /\.(png|jpg|gif)$/,
|
||||||
exclude: /web\/static/,
|
include: /node_modules/,
|
||||||
type: "asset/inline",
|
type: "asset/inline",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user