mirror of
https://github.com/gchq/CyberChef
synced 2025-12-27 05:33:23 +00:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c35205315 | ||
|
|
10751934e4 | ||
|
|
d658f91106 | ||
|
|
4c3324aea1 | ||
|
|
ac2fcee90f | ||
|
|
94e00115fe | ||
|
|
29255d2338 | ||
|
|
39278cfce7 | ||
|
|
46cc48cfb9 | ||
|
|
eb4009949d | ||
|
|
57c48a4bd2 | ||
|
|
45011de494 | ||
|
|
5e51ed0a5f | ||
|
|
875802ef2a | ||
|
|
bbc255ef83 | ||
|
|
fc155ec3fc | ||
|
|
3a0c8a199a | ||
|
|
9c729c4490 | ||
|
|
19bdbd66e5 | ||
|
|
ea090f79ee | ||
|
|
1be6c54be2 | ||
|
|
a4eeb226b1 | ||
|
|
d136717636 | ||
|
|
ad0a2e6f58 | ||
|
|
0212bfb46e | ||
|
|
5e0d661542 | ||
|
|
4c5f529ef4 | ||
|
|
b765534b8b | ||
|
|
7c672c5ee9 | ||
|
|
090bf3f8ec | ||
|
|
9f4ef9cdad | ||
|
|
26fa66ef64 | ||
|
|
b69e4567c0 | ||
|
|
26b19350f2 | ||
|
|
2018b7e247 | ||
|
|
cbff4161a1 | ||
|
|
130bdfb7f2 | ||
|
|
d0c43f5aa9 | ||
|
|
f864a5f31e | ||
|
|
ff585584f6 | ||
|
|
8a029e5147 | ||
|
|
4251089687 | ||
|
|
dbcd670ca8 | ||
|
|
cecae671d8 | ||
|
|
b4e23ac454 | ||
|
|
e7209ca085 | ||
|
|
022ef71d2c | ||
|
|
0fad891a3a | ||
|
|
47f608b502 | ||
|
|
4ab745f730 | ||
|
|
1f89ac11d2 | ||
|
|
1a5dae76c2 | ||
|
|
032c7f529a | ||
|
|
707818abcc | ||
|
|
58e8b4c618 | ||
|
|
9c0c2867dd | ||
|
|
4308c717c3 | ||
|
|
342b67581b | ||
|
|
75da5b650c | ||
|
|
5b6a53be3e | ||
|
|
5b5105c864 | ||
|
|
9d09146f68 | ||
|
|
1b19d20d0c | ||
|
|
0eacab5ddc | ||
|
|
0d7874bac1 | ||
|
|
5cddfafbd0 | ||
|
|
5ce133c47e | ||
|
|
570a84b67a | ||
|
|
a68bfd7223 | ||
|
|
fd7176a445 | ||
|
|
0a06472639 | ||
|
|
3f3a7cd4f6 | ||
|
|
99415359d0 | ||
|
|
54cb2d268b | ||
|
|
0e40daecb6 | ||
|
|
21a822b082 | ||
|
|
a770d09687 | ||
|
|
27b81c4e11 | ||
|
|
8826c80e07 | ||
|
|
02e3ce7fc1 | ||
|
|
673e6aede5 | ||
|
|
5809dea0fc | ||
|
|
165ca9ebc1 | ||
|
|
154b9386f7 | ||
|
|
62dd7c3dbc | ||
|
|
82d098fc1a | ||
|
|
14190fc533 | ||
|
|
20d0ae5304 | ||
|
|
2ba37af109 | ||
|
|
728f8e65d6 | ||
|
|
0f0674daf6 | ||
|
|
462f619f43 | ||
|
|
ef61735f64 | ||
|
|
a2780ca056 | ||
|
|
d025c8bd9a | ||
|
|
acf38e47ba | ||
|
|
4122d4207d | ||
|
|
d550ae7d93 | ||
|
|
815a542cc1 |
@@ -11,7 +11,7 @@ before_script:
|
||||
- export NODE_OPTIONS=--max_old_space_size=2048
|
||||
script:
|
||||
- grunt lint
|
||||
- grunt test
|
||||
- npm test
|
||||
- grunt testnodeconsumer
|
||||
- grunt prod --msg="$COMPILE_MSG"
|
||||
- xvfb-run --server-args="-screen 0 1200x800x24" grunt testui
|
||||
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -2,6 +2,18 @@
|
||||
All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
|
||||
|
||||
|
||||
### [9.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]
|
||||
|
||||
@@ -212,6 +224,10 @@ All major and minor version changes will be documented in this file. Details of
|
||||
|
||||
|
||||
|
||||
[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
|
||||
@@ -304,6 +320,9 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[@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
|
||||
|
||||
[#95]: https://github.com/gchq/CyberChef/pull/299
|
||||
[#173]: https://github.com/gchq/CyberChef/pull/173
|
||||
@@ -366,9 +385,13 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[#627]: https://github.com/gchq/CyberChef/pull/627
|
||||
[#632]: https://github.com/gchq/CyberChef/pull/632
|
||||
[#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
|
||||
[#912]: https://github.com/gchq/CyberChef/pull/912
|
||||
[#917]: https://github.com/gchq/CyberChef/pull/917
|
||||
[#948]: https://github.com/gchq/CyberChef/pull/948
|
||||
[#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
|
||||
|
||||
14
Gruntfile.js
14
Gruntfile.js
@@ -36,11 +36,10 @@ module.exports = function (grunt) {
|
||||
"clean:node", "clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex"
|
||||
]);
|
||||
|
||||
grunt.registerTask("test",
|
||||
"A task which runs all the operation tests in the tests directory.",
|
||||
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",
|
||||
"exec:nodeTests", "exec:opTests"
|
||||
"clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex"
|
||||
]);
|
||||
|
||||
grunt.registerTask("testui",
|
||||
@@ -55,7 +54,6 @@ module.exports = function (grunt) {
|
||||
"Lints the code base",
|
||||
["eslint", "exec:repoSize"]);
|
||||
|
||||
grunt.registerTask("tests", "test");
|
||||
grunt.registerTask("lint", "eslint");
|
||||
|
||||
grunt.registerTask("findModules",
|
||||
@@ -385,15 +383,9 @@ module.exports = function (grunt) {
|
||||
]),
|
||||
sync: true
|
||||
},
|
||||
opTests: {
|
||||
command: "node --experimental-modules --no-warnings --no-deprecation tests/operations/index.mjs"
|
||||
},
|
||||
browserTests: {
|
||||
command: "./node_modules/.bin/nightwatch --env prod"
|
||||
},
|
||||
nodeTests: {
|
||||
command: "node --experimental-modules --no-warnings --no-deprecation tests/node/index.mjs"
|
||||
},
|
||||
setupNodeConsumers: {
|
||||
command: chainCommands([
|
||||
"echo '\n--- Testing node consumers ---'",
|
||||
|
||||
9536
package-lock.json
generated
9536
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
93
package.json
93
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cyberchef",
|
||||
"version": "9.16.0",
|
||||
"version": "9.20.4",
|
||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||
"author": "n1474335 <n1474335@gmail.com>",
|
||||
"homepage": "https://gchq.github.io/CyberChef",
|
||||
@@ -36,21 +36,22 @@
|
||||
"node >= 10"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
"@babel/preset-env": "^7.7.6",
|
||||
"autoprefixer": "^9.7.3",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"@babel/core": "^7.8.7",
|
||||
"@babel/plugin-transform-runtime": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.7",
|
||||
"autoprefixer": "^9.7.4",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.0",
|
||||
"chromedriver": "^80.0.1",
|
||||
"chromedriver": "^83.0.0",
|
||||
"cli-progress": "^3.6.0",
|
||||
"colors": "^1.4.0",
|
||||
"copy-webpack-plugin": "^5.0.5",
|
||||
"css-loader": "^3.2.1",
|
||||
"eslint": "^6.7.2",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"css-loader": "^3.4.2",
|
||||
"eslint": "^6.8.0",
|
||||
"exports-loader": "^0.7.0",
|
||||
"file-loader": "^5.0.2",
|
||||
"grunt": "^1.0.4",
|
||||
"file-loader": "^6.0.0",
|
||||
"grunt": "^1.1.0",
|
||||
"grunt-accessibility": "~6.0.0",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-concurrent": "^3.0.0",
|
||||
@@ -64,29 +65,29 @@
|
||||
"grunt-zip": "^0.18.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"imports-loader": "^0.8.0",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"nightwatch": "^1.3.4",
|
||||
"node-sass": "^4.13.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"postcss-css-variables": "^0.14.0",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"prompt": "^1.0.0",
|
||||
"sass-loader": "^8.0.0",
|
||||
"sitemap": "^5.1.0",
|
||||
"style-loader": "^1.0.1",
|
||||
"svg-url-loader": "^3.0.3",
|
||||
"url-loader": "^3.0.0",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-bundle-analyzer": "^3.6.0",
|
||||
"webpack-dev-server": "^3.9.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"sitemap": "^6.1.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"svg-url-loader": "^5.0.0",
|
||||
"url-loader": "^4.0.0",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-bundle-analyzer": "^3.6.1",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
"webpack-node-externals": "^1.7.2",
|
||||
"worker-loader": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.7.0",
|
||||
"@babel/runtime": "^7.7.6",
|
||||
"@babel/polyfill": "^7.8.7",
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"arrive": "^2.4.1",
|
||||
"avsc": "^5.4.16",
|
||||
"avsc": "^5.4.19",
|
||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bignumber.js": "^9.0.0",
|
||||
@@ -94,26 +95,25 @@
|
||||
"bootstrap": "4.4.1",
|
||||
"bootstrap-colorpicker": "^3.2.0",
|
||||
"bootstrap-material-design": "^4.1.2",
|
||||
"bson": "^4.0.2",
|
||||
"bson": "^4.0.3",
|
||||
"chi-squared": "^1.1.0",
|
||||
"codepage": "^1.14.0",
|
||||
"core-js": "^3.4.8",
|
||||
"core-js": "^3.6.4",
|
||||
"crypto-api": "^0.8.5",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"crypto-js": "^4.0.0",
|
||||
"ctph.js": "0.0.5",
|
||||
"d3": "^5.14.2",
|
||||
"d3": "^5.15.0",
|
||||
"d3-hexbin": "^0.2.2",
|
||||
"diff": "^4.0.1",
|
||||
"es6-promisify": "^6.0.2",
|
||||
"escodegen": "^1.12.0",
|
||||
"diff": "^4.0.2",
|
||||
"es6-promisify": "^6.1.0",
|
||||
"escodegen": "^1.14.1",
|
||||
"esm": "^3.2.25",
|
||||
"esmangle": "^1.0.1",
|
||||
"esprima": "^4.0.1",
|
||||
"exif-parser": "^0.1.12",
|
||||
"file-saver": "^2.0.2",
|
||||
"geodesy": "^1.1.3",
|
||||
"highlight.js": "^9.16.2",
|
||||
"jimp": "^0.9.3",
|
||||
"highlight.js": "^9.18.1",
|
||||
"jimp": "^0.9.5",
|
||||
"jquery": "3.4.1",
|
||||
"js-crc": "^0.2.0",
|
||||
"js-sha3": "^0.8.0",
|
||||
@@ -122,44 +122,45 @@
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jsqr": "^1.2.0",
|
||||
"jsrsasign": "8.0.12",
|
||||
"kbpgp": "2.1.6",
|
||||
"kbpgp": "2.1.13",
|
||||
"libbzip2-wasm": "0.0.4",
|
||||
"libyara-wasm": "^1.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"loglevel": "^1.6.6",
|
||||
"loglevel": "^1.6.7",
|
||||
"loglevel-message-prefix": "^3.0.0",
|
||||
"markdown-it": "^10.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.27",
|
||||
"moment-timezone": "^0.5.28",
|
||||
"ngeohash": "^0.6.3",
|
||||
"node-forge": "^0.9.1",
|
||||
"node-md6": "^0.1.0",
|
||||
"nodom": "^2.4.0",
|
||||
"notepack.io": "^2.2.0",
|
||||
"notepack.io": "^2.3.0",
|
||||
"nwmatcher": "^1.4.4",
|
||||
"otp": "^0.1.3",
|
||||
"popper.js": "^1.16.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"qr-image": "^3.2.0",
|
||||
"scryptsy": "^2.1.0",
|
||||
"snackbarjs": "^1.1.0",
|
||||
"sortablejs": "^1.10.1",
|
||||
"sortablejs": "^1.10.2",
|
||||
"split.js": "^1.5.11",
|
||||
"ssdeep.js": "0.0.2",
|
||||
"tesseract.js": "^2.0.0-alpha.15",
|
||||
"ua-parser-js": "^0.7.20",
|
||||
"terser": "^4.3.9",
|
||||
"tesseract.js": "^2.0.2",
|
||||
"ua-parser-js": "^0.7.21",
|
||||
"unorm": "^1.6.0",
|
||||
"utf8": "^3.0.0",
|
||||
"vkbeautify": "^0.99.3",
|
||||
"xmldom": "^0.1.27",
|
||||
"xmldom": "^0.3.0",
|
||||
"xpath": "0.0.27",
|
||||
"xregexp": "^4.2.4",
|
||||
"xregexp": "^4.3.0",
|
||||
"zlibjs": "^0.3.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "grunt dev",
|
||||
"build": "grunt prod",
|
||||
"repl": "node src/node/repl.js",
|
||||
"test": "grunt test",
|
||||
"test": "grunt configTests && node --experimental-modules --no-warnings --no-deprecation tests/node/index.mjs && node --experimental-modules --no-warnings --no-deprecation tests/operations/index.mjs",
|
||||
"test-node-consumer": "grunt testnodeconsumer",
|
||||
"testui": "grunt testui",
|
||||
"testuidev": "npx nightwatch --env=dev",
|
||||
|
||||
@@ -39,7 +39,7 @@ class Chef {
|
||||
*/
|
||||
async bake(input, recipeConfig, options) {
|
||||
log.debug("Chef baking");
|
||||
const startTime = new Date().getTime(),
|
||||
const startTime = Date.now(),
|
||||
recipe = new Recipe(recipeConfig),
|
||||
containsFc = recipe.containsFlowControl(),
|
||||
notUTF8 = options && "treatAsUtf8" in options && !options.treatAsUtf8;
|
||||
@@ -84,7 +84,7 @@ class Chef {
|
||||
result: await this.dish.get(returnType, notUTF8),
|
||||
type: Dish.enumLookup(this.dish.type),
|
||||
progress: progress,
|
||||
duration: new Date().getTime() - startTime,
|
||||
duration: Date.now() - startTime,
|
||||
error: error
|
||||
};
|
||||
}
|
||||
@@ -110,7 +110,7 @@ class Chef {
|
||||
silentBake(recipeConfig) {
|
||||
log.debug("Running silent bake");
|
||||
|
||||
const startTime = new Date().getTime(),
|
||||
const startTime = Date.now(),
|
||||
recipe = new Recipe(recipeConfig),
|
||||
dish = new Dish();
|
||||
|
||||
@@ -119,7 +119,7 @@ class Chef {
|
||||
} catch (err) {
|
||||
// Suppress all errors
|
||||
}
|
||||
return new Date().getTime() - startTime;
|
||||
return Date.now() - startTime;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,12 @@ class Chef {
|
||||
const func = direction === "forward" ? highlights[i].f : highlights[i].b;
|
||||
|
||||
if (typeof func == "function") {
|
||||
pos = func(pos, highlights[i].args);
|
||||
try {
|
||||
pos = func(pos, highlights[i].args);
|
||||
} catch (err) {
|
||||
// Throw away highlighting errors
|
||||
pos = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1182,6 +1182,7 @@ class Utils {
|
||||
"CRLF": /\r\n/g,
|
||||
"Forward slash": /\//g,
|
||||
"Backslash": /\\/g,
|
||||
"0x with comma": /,?0x/g,
|
||||
"0x": /0x/g,
|
||||
"\\x": /\\x/g,
|
||||
"None": /\s+/g // Included here to remove whitespace when there shouldn't be any
|
||||
@@ -1335,7 +1336,7 @@ export function sendStatusMessage(msg) {
|
||||
self.sendStatusMessage(msg);
|
||||
else if (isWebEnvironment())
|
||||
app.alert(msg, 10000);
|
||||
else if (isNodeEnvironment())
|
||||
else if (isNodeEnvironment() && !global.TESTING)
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(msg);
|
||||
}
|
||||
|
||||
@@ -201,7 +201,8 @@
|
||||
"Encode text",
|
||||
"Decode text",
|
||||
"Remove Diacritics",
|
||||
"Unescape Unicode Characters"
|
||||
"Unescape Unicode Characters",
|
||||
"Convert to NATO alphabet"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -241,6 +242,7 @@
|
||||
"Convert co-ordinate format",
|
||||
"Show on map",
|
||||
"Parse UNIX file permissions",
|
||||
"Parse ObjectID timestamp",
|
||||
"Swap endianness",
|
||||
"Parse colour code",
|
||||
"Escape string",
|
||||
@@ -395,6 +397,7 @@
|
||||
"ops": [
|
||||
"Render Image",
|
||||
"Play Media",
|
||||
"Generate Image",
|
||||
"Optical Character Recognition",
|
||||
"Remove EXIF",
|
||||
"Extract EXIF",
|
||||
|
||||
@@ -42,13 +42,10 @@ for (const opObj in Ops) {
|
||||
outputType: op.presentType,
|
||||
flowControl: op.flowControl,
|
||||
manualBake: op.manualBake,
|
||||
args: op.args
|
||||
args: op.args,
|
||||
checks: op.checks
|
||||
};
|
||||
|
||||
if ("patterns" in op) {
|
||||
operationConfig[op.name].patterns = op.patterns;
|
||||
}
|
||||
|
||||
if (!(op.module in modules))
|
||||
modules[op.module] = {};
|
||||
modules[op.module][op.name] = opObj;
|
||||
|
||||
@@ -17,7 +17,7 @@ class DishJSON extends DishType {
|
||||
*/
|
||||
static toArrayBuffer() {
|
||||
DishJSON.checkForValue(this.value);
|
||||
this.value = this.value ? Utils.strToArrayBuffer(JSON.stringify(this.value, null, 4)) : new ArrayBuffer;
|
||||
this.value = this.value !== undefined ? Utils.strToArrayBuffer(JSON.stringify(this.value, null, 4)) : new ArrayBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -468,6 +468,34 @@ export const FILE_SIGNATURES = {
|
||||
],
|
||||
extractor: null
|
||||
},
|
||||
{
|
||||
name: "Targa Image",
|
||||
extension: "tga",
|
||||
mime: "image/x-targa",
|
||||
description: "",
|
||||
signature: [
|
||||
{ // This signature is not at the beginning of the file. The extractor works backwards.
|
||||
0: 0x54,
|
||||
1: 0x52,
|
||||
2: 0x55,
|
||||
3: 0x45,
|
||||
4: 0x56,
|
||||
5: 0x49,
|
||||
6: 0x53,
|
||||
7: 0x49,
|
||||
8: 0x4f,
|
||||
9: 0x4e,
|
||||
10: 0x2d,
|
||||
11: 0x58,
|
||||
12: 0x46,
|
||||
13: 0x49,
|
||||
14: 0x4c,
|
||||
15: 0x45,
|
||||
16: 0x2e
|
||||
}
|
||||
],
|
||||
extractor: extractTARGA
|
||||
}
|
||||
],
|
||||
"Video": [
|
||||
{ // Place before webm
|
||||
@@ -780,7 +808,7 @@ export const FILE_SIGNATURES = {
|
||||
1: 0xfb
|
||||
}
|
||||
],
|
||||
extractor: null
|
||||
extractor: extractMP3
|
||||
},
|
||||
{
|
||||
name: "MPEG-4 Part 14 audio",
|
||||
@@ -1724,6 +1752,25 @@ export const FILE_SIGNATURES = {
|
||||
},
|
||||
extractor: null
|
||||
},
|
||||
{
|
||||
name: "Jar Archive",
|
||||
extension: "jar",
|
||||
mime: "application/java-archive",
|
||||
description: "",
|
||||
signature: {
|
||||
0: 0x50,
|
||||
1: 0x4B,
|
||||
2: 0x03,
|
||||
3: 0x04,
|
||||
4: 0x14,
|
||||
5: 0x00,
|
||||
6: 0x08,
|
||||
7: 0x00,
|
||||
8: 0x08,
|
||||
9: 0x00
|
||||
},
|
||||
extractor: extractZIP
|
||||
},
|
||||
{
|
||||
name: "lzop compressed",
|
||||
extension: "lzop,lzo",
|
||||
@@ -1739,10 +1786,10 @@ export const FILE_SIGNATURES = {
|
||||
6: 0x0a,
|
||||
7: 0x1a
|
||||
},
|
||||
extractor: null
|
||||
extractor: extractLZOP
|
||||
},
|
||||
{
|
||||
name: "Linux deb",
|
||||
name: "Linux deb package",
|
||||
extension: "deb",
|
||||
mime: "application/vnd.debian.binary-package",
|
||||
description: "",
|
||||
@@ -1755,7 +1802,7 @@ export const FILE_SIGNATURES = {
|
||||
5: 0x68,
|
||||
6: 0x3e
|
||||
},
|
||||
extractor: null
|
||||
extractor: extractDEB
|
||||
},
|
||||
{
|
||||
name: "Apple Disk Image",
|
||||
@@ -2070,7 +2117,7 @@ export const FILE_SIGNATURES = {
|
||||
6: [0x4d, 0x36],
|
||||
7: [0x50, 0x34]
|
||||
},
|
||||
extractor: null
|
||||
extractor: extractDMP
|
||||
},
|
||||
{
|
||||
name: "Windows Prefetch",
|
||||
@@ -2087,7 +2134,7 @@ export const FILE_SIGNATURES = {
|
||||
6: 0x43,
|
||||
7: 0x41
|
||||
},
|
||||
extractor: null
|
||||
extractor: extractPF
|
||||
},
|
||||
{
|
||||
name: "Windows Prefetch (Win 10)",
|
||||
@@ -2101,7 +2148,7 @@ export const FILE_SIGNATURES = {
|
||||
3: 0x04,
|
||||
7: 0x0
|
||||
},
|
||||
extractor: null
|
||||
extractor: extractPFWin10
|
||||
},
|
||||
{
|
||||
name: "PList (XML)",
|
||||
@@ -2374,7 +2421,7 @@ export const FILE_SIGNATURES = {
|
||||
18: 0x00,
|
||||
19: 0x46
|
||||
},
|
||||
extractor: null
|
||||
extractor: extractLNK
|
||||
},
|
||||
{
|
||||
name: "Bash",
|
||||
@@ -3028,6 +3075,90 @@ export function extractICO(bytes, offset) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TARGA extractor.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {number} offset
|
||||
*/
|
||||
export function extractTARGA(bytes, offset) {
|
||||
// Need all the bytes since we do not know how far up the image goes.
|
||||
const stream = new Stream(bytes);
|
||||
stream.moveTo(offset - 8);
|
||||
|
||||
// Read in the offsets of the possible areas.
|
||||
const extensionOffset = stream.readInt(4, "le");
|
||||
const developerOffset = stream.readInt(4, "le");
|
||||
|
||||
stream.moveBackwardsBy(8);
|
||||
|
||||
/**
|
||||
* Moves backwards in the stream until it meet bytes that are the same as the amount of bytes moved.
|
||||
*
|
||||
* @param {number} sizeOfSize
|
||||
* @param {number} maxSize
|
||||
*/
|
||||
function moveBackwardsUntilSize(maxSize, sizeOfSize) {
|
||||
for (let i = 0; i < maxSize; i++) {
|
||||
stream.moveBackwardsBy(1);
|
||||
|
||||
// Read in sizeOfSize amount of bytes in.
|
||||
const size = stream.readInt(sizeOfSize, "le") - 1;
|
||||
stream.moveBackwardsBy(sizeOfSize);
|
||||
|
||||
// If the size matches.
|
||||
if (size === i)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves backwards in the stream until we meet bytes(when calculated) that are the same as the amount of bytes moved.
|
||||
*/
|
||||
function moveBackwardsUntilImageSize() {
|
||||
stream.moveBackwardsBy(5);
|
||||
|
||||
// The documentation said that 0x100000 was the largest the file could be.
|
||||
for (let i = 0; i < 0x100000; i++) {
|
||||
|
||||
// (Height * Width * pixel depth in bits)/8
|
||||
const total = (stream.readInt(2, "le") * stream.readInt(2, "le") * stream.readInt(1))/8;
|
||||
if (total === i-1)
|
||||
break;
|
||||
|
||||
stream.moveBackwardsBy(6);
|
||||
}
|
||||
}
|
||||
|
||||
if (extensionOffset || developerOffset) {
|
||||
if (extensionOffset) {
|
||||
// Size is stored in two bytes hence the maximum is 0xffff.
|
||||
moveBackwardsUntilSize(0xffff, 2);
|
||||
|
||||
// Move to where we think the start of the file is.
|
||||
stream.moveBackwardsBy(extensionOffset);
|
||||
} else if (developerOffset) {
|
||||
// Size is stored in 4 bytes hence the maxiumum is 0xffffffff.
|
||||
moveBackwardsUntilSize(0xffffffff, 4);
|
||||
|
||||
// Size is stored in byte position 6 so have to move back.
|
||||
stream.moveBackwardsBy(6);
|
||||
|
||||
// Move to where we think the start of the file is.
|
||||
stream.moveBackwardsBy(developerOffset);
|
||||
}
|
||||
} else {
|
||||
// Move backwards until size === number of bytes passed.
|
||||
moveBackwardsUntilImageSize();
|
||||
|
||||
// Move backwards over the reaminder of the header + the 5 we borrowed in moveBackwardsUntilImageSize().
|
||||
stream.moveBackwardsBy(0xc+5);
|
||||
}
|
||||
|
||||
return stream.carve(stream.position, offset+0x12);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WAV extractor.
|
||||
*
|
||||
@@ -3048,6 +3179,79 @@ export function extractWAV(bytes, offset) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MP3 extractor.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {Number} offset
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function extractMP3(bytes, offset) {
|
||||
const stream = new Stream(bytes.slice(offset));
|
||||
|
||||
// Constants for flag byte.
|
||||
const bitRateIndexes = ["free", 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, "bad"];
|
||||
|
||||
const samplingRateFrequencyIndex = [44100, 48000, 32000, "reserved"];
|
||||
|
||||
// ID3 tag, move over it.
|
||||
if ((stream.getBytes(3).toString() === [0x49, 0x44, 0x33].toString())) {
|
||||
stream.moveTo(6);
|
||||
const tagSize = (stream.readInt(1) << 21) | (stream.readInt(1) << 14) | (stream.readInt(1) << 7) | stream.readInt(1);
|
||||
stream.moveForwardsBy(tagSize);
|
||||
} else {
|
||||
stream.moveTo(0);
|
||||
}
|
||||
|
||||
// Loop over all the frame headers in the file.
|
||||
while (stream.hasMore()) {
|
||||
|
||||
// If it has an old TAG frame at the end of it, fixed size, 128 bytes.
|
||||
if (stream.getBytes(3) === [0x54, 0x41, 0x47].toString()) {
|
||||
stream.moveForwardsBy(125);
|
||||
break;
|
||||
}
|
||||
|
||||
// If not start of frame.
|
||||
if (stream.getBytes(2).toString() !== [0xff, 0xfb].toString()) {
|
||||
stream.moveBackwardsBy(2);
|
||||
break;
|
||||
}
|
||||
|
||||
// Read flag byte.
|
||||
const flags = stream.readInt(1);
|
||||
|
||||
// Extract frame bit rate from flag byte.
|
||||
const bitRate = bitRateIndexes[flags >> 4];
|
||||
|
||||
// Extract frame sample rate from flag byte.
|
||||
const sampleRate = samplingRateFrequencyIndex[(flags & 0x0f) >> 2];
|
||||
|
||||
// Padding if the frame size is not a multiple of the bitrate.
|
||||
const padding = (flags & 0x02) >> 1;
|
||||
|
||||
// Things that are either not standard or undocumented.
|
||||
if (bitRate === "free" || bitRate === "bad" || sampleRate === "reserved") {
|
||||
stream.moveBackwardsBy(1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Formula: FrameLength = (144 * BitRate / SampleRate ) + Padding
|
||||
const frameSize = Math.floor(((144 * bitRate) / sampleRate) + padding);
|
||||
|
||||
// If the next move goes past the end of the bytestream then extract the entire bytestream.
|
||||
// We assume complete frames in the above formula because there is no field that suggests otherwise.
|
||||
if ((stream.position + frameSize) > stream.length) {
|
||||
stream.moveTo(stream.length);
|
||||
break;
|
||||
} else {
|
||||
stream.moveForwardsBy(frameSize - 3);
|
||||
}
|
||||
}
|
||||
return stream.carve();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FLV extractor.
|
||||
*
|
||||
@@ -3448,6 +3652,37 @@ export function extractXZ(bytes, offset) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DEB extractor.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {Number} offset
|
||||
*/
|
||||
export function extractDEB(bytes, offset) {
|
||||
const stream = new Stream(bytes.slice(offset));
|
||||
|
||||
// Move past !<arch>
|
||||
stream.moveForwardsBy(8);
|
||||
while (stream.hasMore()) {
|
||||
|
||||
// Move to size field.
|
||||
stream.moveForwardsBy(48);
|
||||
let fsize= "";
|
||||
|
||||
// Convert size to a usable number.
|
||||
for (const elem of stream.getBytes(10)) {
|
||||
fsize += String.fromCharCode(elem);
|
||||
}
|
||||
fsize = parseInt(fsize.trim(), 10);
|
||||
|
||||
// Move past `\n
|
||||
stream.moveForwardsBy(2);
|
||||
stream.moveForwardsBy(fsize);
|
||||
}
|
||||
return stream.carve();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ELF extractor.
|
||||
*
|
||||
@@ -3749,3 +3984,158 @@ export function extractEVT(bytes, offset) {
|
||||
stream.moveForwardsBy(eofSize-4);
|
||||
return stream.carve();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DMP extractor.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {Number} offset
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function extractDMP(bytes, offset) {
|
||||
const stream = new Stream(bytes.slice(offset));
|
||||
|
||||
// Move to fileSize field.
|
||||
stream.moveTo(0x70);
|
||||
|
||||
// Multiply number of pages by page size. Plus 1 since the header is a page.
|
||||
stream.moveTo((stream.readInt(4, "le") + 1) * 0x1000);
|
||||
|
||||
return stream.carve();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* PF extractor.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {Number} offset
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function extractPF(bytes, offset) {
|
||||
const stream = new Stream(bytes.slice(offset));
|
||||
|
||||
// Move to file size.
|
||||
stream.moveTo(12);
|
||||
stream.moveTo(stream.readInt(4, "be"));
|
||||
|
||||
return stream.carve();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* PF (Win 10) extractor.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {Number} offset
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function extractPFWin10(bytes, offset) {
|
||||
const stream = new Stream(bytes.slice(offset));
|
||||
|
||||
// Read in file size.
|
||||
stream.moveTo(stream.readInt(4, "be"));
|
||||
|
||||
return stream.carve();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* LNK extractor.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {Number} offset
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function extractLNK(bytes, offset) {
|
||||
const stream = new Stream(bytes.slice(offset));
|
||||
|
||||
// Move to file size field.
|
||||
stream.moveTo(0x34);
|
||||
stream.moveTo(stream.readInt(4, "le"));
|
||||
|
||||
return stream.carve();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* LZOP extractor.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {Number} offset
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function extractLZOP(bytes, offset) {
|
||||
const stream = new Stream(bytes.slice(offset));
|
||||
|
||||
// Flag bits.
|
||||
const F_ADLER32_D = 0x00000001;
|
||||
const F_ADLER32_C = 0x00000002;
|
||||
const F_CRC32_D = 0x00000100;
|
||||
const F_CRC32_C = 0x00000200;
|
||||
const F_H_FILTER = 0x00000800;
|
||||
const F_H_EXTRA_FIELD = 0x00000040;
|
||||
|
||||
let numCheckSumC = 0, numCheckSumD = 0;
|
||||
|
||||
// Move over magic bytes.
|
||||
stream.moveForwardsBy(9);
|
||||
|
||||
const version = stream.readInt(2, "be");
|
||||
|
||||
// Move to flag register offset.
|
||||
stream.moveForwardsBy(6);
|
||||
const flags = stream.readInt(4, "be");
|
||||
|
||||
if (version & F_H_FILTER)
|
||||
stream.moveForwardsBy(4);
|
||||
|
||||
if (flags & F_ADLER32_C)
|
||||
numCheckSumC++;
|
||||
|
||||
if (flags & F_CRC32_C)
|
||||
numCheckSumC++;
|
||||
|
||||
if (flags & F_ADLER32_D)
|
||||
numCheckSumD++;
|
||||
|
||||
if (flags & F_CRC32_D)
|
||||
numCheckSumD++;
|
||||
|
||||
// Move over the mode, mtime_low
|
||||
stream.moveForwardsBy(8);
|
||||
|
||||
if (version >= 0x0940)
|
||||
stream.moveForwardsBy(4);
|
||||
|
||||
const fnameSize = stream.readInt(1, "be");
|
||||
|
||||
// Move forwards by size of file name and the following 4 byte checksum.
|
||||
stream.moveForwardsBy(fnameSize);
|
||||
|
||||
if (flags & F_H_EXTRA_FIELD) {
|
||||
const extraSize = stream.readInt(4, "be");
|
||||
stream.moveForwardsBy(extraSize);
|
||||
}
|
||||
|
||||
// Move past checksum.
|
||||
stream.moveForwardsBy(4);
|
||||
|
||||
while (stream.hasMore()) {
|
||||
const uncompSize = stream.readInt(4, "be");
|
||||
|
||||
// If data has no length, break.
|
||||
if (uncompSize === 0)
|
||||
break;
|
||||
|
||||
const compSize = stream.readInt(4, "be");
|
||||
|
||||
const numCheckSumSkip = (uncompSize === compSize) ? numCheckSumD : numCheckSumD + numCheckSumC;
|
||||
|
||||
// skip forwards by compressed data size and the size of the checksum(s).
|
||||
stream.moveForwardsBy(compSize + (numCheckSumSkip * 4));
|
||||
}
|
||||
return stream.carve();
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export function ipv4CidrRange(cidr, includeNetworkInfo, enumerateAddresses, allo
|
||||
let output = "";
|
||||
|
||||
if (cidrRange < 0 || cidrRange > 31) {
|
||||
return "IPv4 CIDR must be less than 32";
|
||||
throw new OperationError("IPv4 CIDR must be less than 32");
|
||||
}
|
||||
|
||||
const mask = ~(0xFFFFFFFF >>> cidrRange),
|
||||
@@ -64,7 +64,7 @@ export function ipv6CidrRange(cidr, includeNetworkInfo) {
|
||||
cidrRange = parseInt(cidr[cidr.length-1], 10);
|
||||
|
||||
if (cidrRange < 0 || cidrRange > 127) {
|
||||
return "IPv6 CIDR must be less than 128";
|
||||
throw new OperationError("IPv6 CIDR must be less than 128");
|
||||
}
|
||||
|
||||
const ip1 = new Array(8),
|
||||
@@ -211,7 +211,7 @@ export function ipv4ListedRange(match, includeNetworkInfo, enumerateAddresses, a
|
||||
const network = strToIpv4(ipv4CidrList[i].split("/")[0]);
|
||||
const cidrRange = parseInt(ipv4CidrList[i].split("/")[1], 10);
|
||||
if (cidrRange < 0 || cidrRange > 31) {
|
||||
return "IPv4 CIDR must be less than 32";
|
||||
throw new OperationError("IPv4 CIDR must be less than 32");
|
||||
}
|
||||
const mask = ~(0xFFFFFFFF >>> cidrRange),
|
||||
cidrIp1 = network & mask,
|
||||
@@ -254,7 +254,7 @@ export function ipv6ListedRange(match, includeNetworkInfo) {
|
||||
const cidrRange = parseInt(ipv6CidrList[i].split("/")[1], 10);
|
||||
|
||||
if (cidrRange < 0 || cidrRange > 127) {
|
||||
return "IPv6 CIDR must be less than 128";
|
||||
throw new OperationError("IPv6 CIDR must be less than 128");
|
||||
}
|
||||
|
||||
const cidrIp1 = new Array(8),
|
||||
|
||||
@@ -2,7 +2,7 @@ import OperationConfig from "../config/OperationConfig.json";
|
||||
import Utils, { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import Recipe from "../Recipe.mjs";
|
||||
import Dish from "../Dish.mjs";
|
||||
import {detectFileType} from "./FileType.mjs";
|
||||
import {detectFileType, isType} from "./FileType.mjs";
|
||||
import chiSquared from "chi-squared";
|
||||
|
||||
/**
|
||||
@@ -19,31 +19,38 @@ class Magic {
|
||||
* Magic constructor.
|
||||
*
|
||||
* @param {ArrayBuffer} buf
|
||||
* @param {Object[]} [opPatterns]
|
||||
* @param {Object[]} [opCriteria]
|
||||
* @param {Object} [prevOp]
|
||||
*/
|
||||
constructor(buf, opPatterns) {
|
||||
constructor(buf, opCriteria=Magic._generateOpCriteria(), prevOp=null) {
|
||||
this.inputBuffer = new Uint8Array(buf);
|
||||
this.inputStr = Utils.arrayBufferToStr(buf);
|
||||
this.opPatterns = opPatterns || Magic._generateOpPatterns();
|
||||
this.opCriteria = opCriteria;
|
||||
this.prevOp = prevOp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds operations that claim to be able to decode the input based on regular
|
||||
* expression matches.
|
||||
* Finds operations that claim to be able to decode the input based on various criteria.
|
||||
*
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
findMatchingOps() {
|
||||
const matches = [];
|
||||
findMatchingInputOps() {
|
||||
const matches = [],
|
||||
inputEntropy = this.calcEntropy();
|
||||
|
||||
for (let i = 0; i < this.opPatterns.length; i++) {
|
||||
const pattern = this.opPatterns[i],
|
||||
regex = new RegExp(pattern.match, pattern.flags);
|
||||
this.opCriteria.forEach(check => {
|
||||
// If the input doesn't lie in the required entropy range, move on
|
||||
if (check.entropyRange &&
|
||||
(inputEntropy < check.entropyRange[0] ||
|
||||
inputEntropy > check.entropyRange[1]))
|
||||
return;
|
||||
// If the input doesn't match the pattern, move on
|
||||
if (check.pattern &&
|
||||
!check.pattern.test(this.inputStr))
|
||||
return;
|
||||
|
||||
if (regex.test(this.inputStr)) {
|
||||
matches.push(pattern);
|
||||
}
|
||||
}
|
||||
matches.push(check);
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
@@ -185,8 +192,10 @@ class Magic {
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
calcEntropy() {
|
||||
const prob = this._freqDist();
|
||||
calcEntropy(data=this.inputBuffer, standalone=false) {
|
||||
if (!standalone && this.inputEntropy) return this.inputEntropy;
|
||||
|
||||
const prob = this._freqDist(data, standalone);
|
||||
let entropy = 0,
|
||||
p;
|
||||
|
||||
@@ -195,6 +204,8 @@ class Magic {
|
||||
if (p === 0) continue;
|
||||
entropy += p * Math.log(p) / Math.log(2);
|
||||
}
|
||||
|
||||
if (!standalone) this.inputEntropy = -entropy;
|
||||
return -entropy;
|
||||
}
|
||||
|
||||
@@ -264,25 +275,59 @@ class Magic {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the data passes output criteria for an operation check
|
||||
*
|
||||
* @param {ArrayBuffer} data
|
||||
* @param {Object} criteria
|
||||
* @returns {boolean}
|
||||
*/
|
||||
outputCheckPasses(data, criteria) {
|
||||
if (criteria.pattern) {
|
||||
const dataStr = Utils.arrayBufferToStr(data),
|
||||
regex = new RegExp(criteria.pattern, criteria.flags);
|
||||
if (!regex.test(dataStr))
|
||||
return false;
|
||||
}
|
||||
if (criteria.entropyRange) {
|
||||
const dataEntropy = this.calcEntropy(data, true);
|
||||
if (dataEntropy < criteria.entropyRange[0] || dataEntropy > criteria.entropyRange[1])
|
||||
return false;
|
||||
}
|
||||
if (criteria.mime &&
|
||||
!isType(criteria.mime, data))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Speculatively executes matching operations, recording metadata of each result.
|
||||
*
|
||||
* @param {number} [depth=0] - How many levels to try to execute
|
||||
* @param {boolean} [extLang=false] - Extensive language support (false = only check the most
|
||||
* common Internet languages)
|
||||
* common Internet languages)
|
||||
* @param {boolean} [intensive=false] - Run brute-forcing on each branch (significantly affects
|
||||
* performance)
|
||||
* performance)
|
||||
* @param {Object[]} [recipeConfig=[]] - The recipe configuration up to this point
|
||||
* @param {boolean} [useful=false] - Whether the current recipe should be scored highly
|
||||
* @param {string} [crib=null] - The regex crib provided by the user, for filtering the operation output
|
||||
* @param {string} [crib=null] - The regex crib provided by the user, for filtering the operation
|
||||
* output
|
||||
* @returns {Object[]} - A sorted list of the recipes most likely to result in correct decoding
|
||||
*/
|
||||
async speculativeExecution(depth=0, extLang=false, intensive=false, recipeConfig=[], useful=false, crib=null) {
|
||||
async speculativeExecution(
|
||||
depth=0,
|
||||
extLang=false,
|
||||
intensive=false,
|
||||
recipeConfig=[],
|
||||
useful=false,
|
||||
crib=null) {
|
||||
|
||||
// If we have reached the recursion depth, return
|
||||
if (depth < 0) return [];
|
||||
|
||||
// Find any operations that can be run on this data
|
||||
const matchingOps = this.findMatchingOps();
|
||||
|
||||
const matchingOps = this.findMatchingInputOps();
|
||||
let results = [];
|
||||
|
||||
// Record the properties of the current data
|
||||
@@ -308,17 +353,21 @@ class Magic {
|
||||
},
|
||||
output = await this._runRecipe([opConfig]);
|
||||
|
||||
// If the recipe is repeating and returning the same data, do not continue
|
||||
if (prevOp && op.op === prevOp.op && _buffersEqual(output, this.inputBuffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the recipe returned an empty buffer, do not continue
|
||||
if (_buffersEqual(output, new ArrayBuffer())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const magic = new Magic(output, this.opPatterns),
|
||||
// If the recipe is repeating and returning the same data, do not continue
|
||||
if (prevOp && op.op === prevOp.op && _buffersEqual(output, this.inputBuffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the output criteria for this op doesn't match the output, do not continue
|
||||
if (op.output && !this.outputCheckPasses(output, op.output))
|
||||
return;
|
||||
|
||||
const magic = new Magic(output, this.opCriteria, OperationConfig[op.op]),
|
||||
speculativeResults = await magic.speculativeExecution(
|
||||
depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful, crib);
|
||||
|
||||
@@ -330,7 +379,7 @@ class Magic {
|
||||
const bfEncodings = await this.bruteForce();
|
||||
|
||||
await Promise.all(bfEncodings.map(async enc => {
|
||||
const magic = new Magic(enc.data, this.opPatterns),
|
||||
const magic = new Magic(enc.data, this.opCriteria, undefined),
|
||||
bfResults = await magic.speculativeExecution(
|
||||
depth-1, extLang, false, [...recipeConfig, enc.conf], false, crib);
|
||||
|
||||
@@ -345,7 +394,8 @@ class Magic {
|
||||
r.languageScores[0].probability > 0 || // Some kind of language was found
|
||||
r.fileType || // A file was found
|
||||
r.isUTF8 || // UTF-8 was found
|
||||
r.matchingOps.length // A matching op was found
|
||||
r.matchingOps.length || // A matching op was found
|
||||
r.matchesCrib // The crib matches
|
||||
)
|
||||
);
|
||||
|
||||
@@ -376,9 +426,10 @@ class Magic {
|
||||
bScore += b.entropy;
|
||||
|
||||
// A result with no recipe but matching ops suggests there are better options
|
||||
if ((!a.recipe.length && a.matchingOps.length) &&
|
||||
b.recipe.length)
|
||||
if ((!a.recipe.length && a.matchingOps.length) && b.recipe.length)
|
||||
return 1;
|
||||
if ((!b.recipe.length && b.matchingOps.length) && a.recipe.length)
|
||||
return -1;
|
||||
|
||||
return aScore - bScore;
|
||||
});
|
||||
@@ -417,14 +468,16 @@ class Magic {
|
||||
* Calculates the number of times each byte appears in the input as a percentage
|
||||
*
|
||||
* @private
|
||||
* @param {ArrayBuffer} [data]
|
||||
* @param {boolean} [standalone]
|
||||
* @returns {number[]}
|
||||
*/
|
||||
_freqDist() {
|
||||
if (this.freqDist) return this.freqDist;
|
||||
_freqDist(data=this.inputBuffer, standalone=false) {
|
||||
if (!standalone && this.freqDist) return this.freqDist;
|
||||
|
||||
const len = this.inputBuffer.length;
|
||||
const len = data.length,
|
||||
counts = new Array(256).fill(0);
|
||||
let i = len;
|
||||
const counts = new Array(256).fill(0);
|
||||
|
||||
if (!len) {
|
||||
this.freqDist = counts;
|
||||
@@ -432,13 +485,15 @@ class Magic {
|
||||
}
|
||||
|
||||
while (i--) {
|
||||
counts[this.inputBuffer[i]]++;
|
||||
counts[data[i]]++;
|
||||
}
|
||||
|
||||
this.freqDist = counts.map(c => {
|
||||
const result = counts.map(c => {
|
||||
return c / len * 100;
|
||||
});
|
||||
return this.freqDist;
|
||||
|
||||
if (!standalone) this.freqDist = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -447,24 +502,29 @@ class Magic {
|
||||
* @private
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
static _generateOpPatterns() {
|
||||
const opPatterns = [];
|
||||
static _generateOpCriteria() {
|
||||
const opCriteria = [];
|
||||
|
||||
for (const op in OperationConfig) {
|
||||
if (!("patterns" in OperationConfig[op])) continue;
|
||||
if (!("checks" in OperationConfig[op]))
|
||||
continue;
|
||||
|
||||
OperationConfig[op].patterns.forEach(pattern => {
|
||||
opPatterns.push({
|
||||
OperationConfig[op].checks.forEach(check => {
|
||||
// Add to the opCriteria list.
|
||||
// Compile the regex here and cache the compiled version so we
|
||||
// don't have to keep calculating it.
|
||||
opCriteria.push({
|
||||
op: op,
|
||||
match: pattern.match,
|
||||
flags: pattern.flags,
|
||||
args: pattern.args,
|
||||
useful: pattern.useful || false
|
||||
pattern: check.pattern ? new RegExp(check.pattern, check.flags) : null,
|
||||
args: check.args,
|
||||
useful: check.useful,
|
||||
entropyRange: check.entropyRange,
|
||||
output: check.output
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return opPatterns;
|
||||
return opCriteria;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -303,11 +303,13 @@ export default class Stream {
|
||||
/**
|
||||
* Returns a slice of the stream up to the current position.
|
||||
*
|
||||
* @param {number} [start=0]
|
||||
* @param {number} [finish=this.position]
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
carve() {
|
||||
if (this.bitPos > 0) this.position++;
|
||||
return this.bytes.slice(0, this.position);
|
||||
carve(start=0, finish=this.position) {
|
||||
if (this.bitPos > 0) finish++;
|
||||
return this.bytes.slice(start, finish);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,38 @@ class A1Z26CipherDecode extends Operation {
|
||||
value: DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^\\s*([12]?[0-9] )+[12]?[0-9]\\s*$",
|
||||
flags: "",
|
||||
args: ["Space"]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([12]?[0-9],)+[12]?[0-9]\\s*$",
|
||||
flags: "",
|
||||
args: ["Comma"]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([12]?[0-9];)+[12]?[0-9]\\s*$",
|
||||
flags: "",
|
||||
args: ["Semi-colon"]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([12]?[0-9]:)+[12]?[0-9]\\s*$",
|
||||
flags: "",
|
||||
args: ["Colon"]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([12]?[0-9]\\n)+[12]?[0-9]\\s*$",
|
||||
flags: "",
|
||||
args: ["Line feed"]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([12]?[0-9]\\r\\n)+[12]?[0-9]\\s*$",
|
||||
flags: "",
|
||||
args: ["CRLF"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,6 +44,48 @@ class BaconCipherDecode extends Operation {
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^\\s*([01]{5}\\s?)+$",
|
||||
flags: "",
|
||||
args: ["Standard (I=J and U=V)", "0/1", false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([01]{5}\\s?)+$",
|
||||
flags: "",
|
||||
args: ["Standard (I=J and U=V)", "0/1", true]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([AB]{5}\\s?)+$",
|
||||
flags: "",
|
||||
args: ["Standard (I=J and U=V)", "A/B", false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([AB]{5}\\s?)+$",
|
||||
flags: "",
|
||||
args: ["Standard (I=J and U=V)", "A/B", true]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([01]{5}\\s?)+$",
|
||||
flags: "",
|
||||
args: ["Complete", "0/1", false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([01]{5}\\s?)+$",
|
||||
flags: "",
|
||||
args: ["Complete", "0/1", true]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([AB]{5}\\s?)+$",
|
||||
flags: "",
|
||||
args: ["Complete", "A/B", false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*([AB]{5}\\s?)+$",
|
||||
flags: "",
|
||||
args: ["Complete", "A/B", true]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,9 +33,9 @@ class Bzip2Decompress extends Operation {
|
||||
value: false
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
"match": "^\\x42\\x5a\\x68",
|
||||
"pattern": "^\\x42\\x5a\\x68",
|
||||
"flags": "",
|
||||
"args": []
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
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";
|
||||
|
||||
82
src/core/operations/ConvertToNATOAlphabet.mjs
Normal file
82
src/core/operations/ConvertToNATOAlphabet.mjs
Normal file
@@ -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;
|
||||
@@ -24,6 +24,13 @@ class DechunkHTTPResponse extends Operation {
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^[0-9A-F]+\r\n",
|
||||
flags: "i",
|
||||
args: []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,13 @@ class DecodeNetBIOSName extends Operation {
|
||||
"value": 65
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^\\s*\\S{32}$",
|
||||
flags: "",
|
||||
args: [65]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,7 +25,17 @@ class DefangIPAddresses extends Operation {
|
||||
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"
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,22 +44,22 @@ class EscapeUnicodeCharacters extends Operation {
|
||||
"value": true
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "\\\\u(?:[\\da-f]{4,6})",
|
||||
pattern: "\\\\u(?:[\\da-f]{4,6})",
|
||||
flags: "i",
|
||||
args: ["\\u"]
|
||||
},
|
||||
{
|
||||
match: "%u(?:[\\da-f]{4,6})",
|
||||
pattern: "%u(?:[\\da-f]{4,6})",
|
||||
flags: "i",
|
||||
args: ["%u"]
|
||||
},
|
||||
{
|
||||
match: "U\\+(?:[\\da-f]{4,6})",
|
||||
pattern: "U\\+(?:[\\da-f]{4,6})",
|
||||
flags: "i",
|
||||
args: ["U+"]
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@ class FromBCD extends Operation {
|
||||
"value": FORMAT
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^(?:\\d{4} ){3,}\\d{4}$",
|
||||
pattern: "^(?:\\d{4} ){3,}\\d{4}$",
|
||||
flags: "",
|
||||
args: ["8 4 2 1", true, false, "Nibbles"]
|
||||
},
|
||||
|
||||
@@ -36,12 +36,12 @@ class FromBase32 extends Operation {
|
||||
value: true
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^(?:[A-Z2-7]{8})+(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}={1})?$",
|
||||
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]
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -38,14 +38,14 @@ class FromBase58 extends Operation {
|
||||
"value": true
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^[1-9A-HJ-NP-Za-km-z]{20,}$",
|
||||
pattern: "^[1-9A-HJ-NP-Za-km-z]{20,}$",
|
||||
flags: "",
|
||||
args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", false]
|
||||
},
|
||||
{
|
||||
match: "^[1-9A-HJ-NP-Za-km-z]{20,}$",
|
||||
pattern: "^[1-9A-HJ-NP-Za-km-z]{20,}$",
|
||||
flags: "",
|
||||
args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", false]
|
||||
},
|
||||
|
||||
@@ -36,69 +36,69 @@ class FromBase64 extends Operation {
|
||||
value: true
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
|
||||
pattern: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["A-Za-z0-9+/=", true]
|
||||
},
|
||||
{
|
||||
match: "^\\s*[A-Z\\d\\-_]{20,}\\s*$",
|
||||
pattern: "^\\s*[A-Z\\d\\-_]{20,}\\s*$",
|
||||
flags: "i",
|
||||
args: ["A-Za-z0-9-_", true]
|
||||
},
|
||||
{
|
||||
match: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$",
|
||||
pattern: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["A-Za-z0-9+\\-=", true]
|
||||
},
|
||||
{
|
||||
match: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$",
|
||||
pattern: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["./0-9A-Za-z=", true]
|
||||
},
|
||||
{
|
||||
match: "^\\s*[A-Z\\d_.]{20,}\\s*$",
|
||||
pattern: "^\\s*[A-Z\\d_.]{20,}\\s*$",
|
||||
flags: "i",
|
||||
args: ["A-Za-z0-9_.", true]
|
||||
},
|
||||
{
|
||||
match: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$",
|
||||
pattern: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["A-Za-z0-9._-", true]
|
||||
},
|
||||
{
|
||||
match: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
|
||||
pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["0-9a-zA-Z+/=", true]
|
||||
},
|
||||
{
|
||||
match: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
|
||||
pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["0-9A-Za-z+/=", true]
|
||||
},
|
||||
{
|
||||
match: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$",
|
||||
pattern: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$",
|
||||
flags: "",
|
||||
args: [" -_", false]
|
||||
},
|
||||
{
|
||||
match: "^\\s*[A-Z\\d+\\-]{20,}\\s*$",
|
||||
pattern: "^\\s*[A-Z\\d+\\-]{20,}\\s*$",
|
||||
flags: "i",
|
||||
args: ["+\\-0-9A-Za-z", true]
|
||||
},
|
||||
{
|
||||
match: "^\\s*[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\s*$",
|
||||
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]
|
||||
},
|
||||
{
|
||||
match: "^\\s*(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?\\s*$",
|
||||
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]
|
||||
},
|
||||
{
|
||||
match: "^\\s*[A-Z\\d./]{20,}\\s*$",
|
||||
pattern: "^\\s*[A-Z\\d./]{20,}\\s*$",
|
||||
flags: "i",
|
||||
args: ["./0-9A-Za-z", true]
|
||||
},
|
||||
|
||||
@@ -33,39 +33,39 @@ class FromBinary extends Operation {
|
||||
"value": BIN_DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^(?:[01]{8})+$",
|
||||
pattern: "^(?:[01]{8})+$",
|
||||
flags: "",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[01]{8})(?: [01]{8})*$",
|
||||
pattern: "^(?:[01]{8})(?: [01]{8})*$",
|
||||
flags: "",
|
||||
args: ["Space"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[01]{8})(?:,[01]{8})*$",
|
||||
pattern: "^(?:[01]{8})(?:,[01]{8})*$",
|
||||
flags: "",
|
||||
args: ["Comma"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[01]{8})(?:;[01]{8})*$",
|
||||
pattern: "^(?:[01]{8})(?:;[01]{8})*$",
|
||||
flags: "",
|
||||
args: ["Semi-colon"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[01]{8})(?::[01]{8})*$",
|
||||
pattern: "^(?:[01]{8})(?::[01]{8})*$",
|
||||
flags: "",
|
||||
args: ["Colon"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[01]{8})(?:\\n[01]{8})*$",
|
||||
pattern: "^(?:[01]{8})(?:\\n[01]{8})*$",
|
||||
flags: "",
|
||||
args: ["Line feed"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[01]{8})(?:\\r\\n[01]{8})*$",
|
||||
pattern: "^(?:[01]{8})(?:\\r\\n[01]{8})*$",
|
||||
flags: "",
|
||||
args: ["CRLF"]
|
||||
},
|
||||
|
||||
@@ -36,37 +36,37 @@ class FromDecimal extends Operation {
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?: (?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$",
|
||||
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]
|
||||
},
|
||||
{
|
||||
match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:,(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$",
|
||||
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]
|
||||
},
|
||||
{
|
||||
match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:;(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$",
|
||||
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]
|
||||
},
|
||||
{
|
||||
match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?::(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$",
|
||||
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]
|
||||
},
|
||||
{
|
||||
match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:\\n(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$",
|
||||
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]
|
||||
},
|
||||
{
|
||||
match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:\\r\\n(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$",
|
||||
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]
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -25,12 +25,12 @@ class FromHTMLEntity extends Operation {
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "&(?:#\\d{2,3}|#x[\\da-f]{2}|[a-z]{2,6});",
|
||||
pattern: "&(?:#\\d{2,3}|#x[\\da-f]{2}|[a-z]{2,6});",
|
||||
flags: "i",
|
||||
args: []
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -32,49 +32,54 @@ class FromHex extends Operation {
|
||||
value: FROM_HEX_DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^(?:[\\dA-F]{2})+$",
|
||||
pattern: "^(?:[\\dA-F]{2})+$",
|
||||
flags: "i",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
match: "^[\\dA-F]{2}(?: [\\dA-F]{2})*$",
|
||||
pattern: "^[\\dA-F]{2}(?: [\\dA-F]{2})*$",
|
||||
flags: "i",
|
||||
args: ["Space"]
|
||||
},
|
||||
{
|
||||
match: "^[\\dA-F]{2}(?:,[\\dA-F]{2})*$",
|
||||
pattern: "^[\\dA-F]{2}(?:,[\\dA-F]{2})*$",
|
||||
flags: "i",
|
||||
args: ["Comma"]
|
||||
},
|
||||
{
|
||||
match: "^[\\dA-F]{2}(?:;[\\dA-F]{2})*$",
|
||||
pattern: "^[\\dA-F]{2}(?:;[\\dA-F]{2})*$",
|
||||
flags: "i",
|
||||
args: ["Semi-colon"]
|
||||
},
|
||||
{
|
||||
match: "^[\\dA-F]{2}(?::[\\dA-F]{2})*$",
|
||||
pattern: "^[\\dA-F]{2}(?::[\\dA-F]{2})*$",
|
||||
flags: "i",
|
||||
args: ["Colon"]
|
||||
},
|
||||
{
|
||||
match: "^[\\dA-F]{2}(?:\\n[\\dA-F]{2})*$",
|
||||
pattern: "^[\\dA-F]{2}(?:\\n[\\dA-F]{2})*$",
|
||||
flags: "i",
|
||||
args: ["Line feed"]
|
||||
},
|
||||
{
|
||||
match: "^[\\dA-F]{2}(?:\\r\\n[\\dA-F]{2})*$",
|
||||
pattern: "^[\\dA-F]{2}(?:\\r\\n[\\dA-F]{2})*$",
|
||||
flags: "i",
|
||||
args: ["CRLF"]
|
||||
},
|
||||
{
|
||||
match: "^[\\dA-F]{2}(?:0x[\\dA-F]{2})*$",
|
||||
pattern: "^(?:0x[\\dA-F]{2})+$",
|
||||
flags: "i",
|
||||
args: ["0x"]
|
||||
},
|
||||
{
|
||||
match: "^[\\dA-F]{2}(?:\\\\x[\\dA-F]{2})*$",
|
||||
pattern: "^0x[\\dA-F]{2}(?:,0x[\\dA-F]{2})*$",
|
||||
flags: "i",
|
||||
args: ["0x with comma"]
|
||||
},
|
||||
{
|
||||
pattern: "^(?:\\\\x[\\dA-F]{2})+$",
|
||||
flags: "i",
|
||||
args: ["\\x"]
|
||||
}
|
||||
|
||||
@@ -26,6 +26,13 @@ class FromHexContent extends Operation {
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "\\|([\\da-f]{2} ?)+\\|",
|
||||
flags: "i",
|
||||
args: []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,9 +27,9 @@ class FromHexdump extends Operation {
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^(?:(?:[\\dA-F]{4,16}h?:?)?[ \\t]*((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )*[\\dA-F]{4}|(?:[\\dA-F]{2} )*[\\dA-F]{2})[^\\n]*\\n?){2,}$",
|
||||
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: []
|
||||
},
|
||||
|
||||
@@ -37,12 +37,12 @@ class FromMorseCode extends Operation {
|
||||
"value": WORD_DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "(?:^[-. \\n]{5,}$|^[_. \\n]{5,}$|^(?:dash|dot| |\\n){5,}$)",
|
||||
pattern: "(?:^[-. \\n]{5,}$|^[_. \\n]{5,}$|^(?:dash|dot| |\\n){5,}$)",
|
||||
flags: "i",
|
||||
args: ["Space", "Line feed"]
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -32,37 +32,37 @@ class FromOctal extends Operation {
|
||||
"value": DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?: (?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?: (?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
flags: "",
|
||||
args: ["Space"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:,(?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:,(?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
flags: "",
|
||||
args: ["Comma"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:;(?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:;(?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
flags: "",
|
||||
args: ["Semi-colon"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?::(?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?::(?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
flags: "",
|
||||
args: ["Colon"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
flags: "",
|
||||
args: ["Line feed"]
|
||||
},
|
||||
{
|
||||
match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\r\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
pattern: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\r\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$",
|
||||
flags: "",
|
||||
args: ["CRLF"]
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ class FromQuotedPrintable extends Operation {
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^[\\x21-\\x3d\\x3f-\\x7e \\t]{0,76}(?:=[\\da-f]{2}|=\\r?\\n)(?:[\\x21-\\x3d\\x3f-\\x7e \\t]|=[\\da-f]{2}|=\\r?\\n)*$",
|
||||
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: []
|
||||
},
|
||||
|
||||
@@ -33,27 +33,27 @@ class FromUNIXTimestamp extends Operation {
|
||||
"value": UNITS
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^1?\\d{9}$",
|
||||
pattern: "^1?\\d{9}$",
|
||||
flags: "",
|
||||
args: ["Seconds (s)"]
|
||||
},
|
||||
{
|
||||
match: "^1?\\d{12}$",
|
||||
pattern: "^1?\\d{12}$",
|
||||
flags: "",
|
||||
args: ["Milliseconds (ms)"]
|
||||
},
|
||||
{
|
||||
match: "^1?\\d{15}$",
|
||||
pattern: "^1?\\d{15}$",
|
||||
flags: "",
|
||||
args: ["Microseconds (μs)"]
|
||||
},
|
||||
{
|
||||
match: "^1?\\d{18}$",
|
||||
pattern: "^1?\\d{18}$",
|
||||
flags: "",
|
||||
args: ["Nanoseconds (ns)"]
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
184
src/core/operations/GenerateImage.mjs
Normal file
184
src/core/operations/GenerateImage.mjs
Normal file
@@ -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";
|
||||
import {toBase64} from "../lib/Base64";
|
||||
import jimp from "jimp";
|
||||
import {isWorkerEnvironment} from "../Utils";
|
||||
|
||||
/**
|
||||
* 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 `<img src="data:${type};base64,${toBase64(dataArray)}">`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GenerateImage;
|
||||
@@ -27,12 +27,12 @@ class Gunzip extends Operation {
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^\\x1f\\x8b\\x08",
|
||||
pattern: "^\\x1f\\x8b\\x08",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Operation from "../Operation.mjs";
|
||||
import * as esprima from "esprima";
|
||||
import escodegen from "escodegen";
|
||||
import esmangle from "esmangle";
|
||||
import Terser from "terser";
|
||||
|
||||
/**
|
||||
* JavaScript Minify operation
|
||||
@@ -34,22 +33,11 @@ class JavaScriptMinify extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
let result = "";
|
||||
const AST = esprima.parseScript(input),
|
||||
optimisedAST = esmangle.optimize(AST, null),
|
||||
mangledAST = esmangle.mangle(optimisedAST);
|
||||
|
||||
result = escodegen.generate(mangledAST, {
|
||||
format: {
|
||||
renumber: true,
|
||||
hexadecimal: true,
|
||||
escapeless: true,
|
||||
compact: true,
|
||||
semicolons: false,
|
||||
parentheses: false
|
||||
}
|
||||
});
|
||||
return result;
|
||||
const result = Terser.minify(input);
|
||||
if (result.error) {
|
||||
throw new OperationError(`Error minifying JavaScript. (${result.error})`);
|
||||
}
|
||||
return result.code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -293,8 +293,8 @@ class Lorenz extends Operation {
|
||||
chosenSetting.S[3] = this.readLugs(lugs3);
|
||||
chosenSetting.S[4] = this.readLugs(lugs4);
|
||||
chosenSetting.S[5] = this.readLugs(lugs5);
|
||||
chosenSetting.M[1] = this.readLugs(lugm37);
|
||||
chosenSetting.M[2] = this.readLugs(lugm61);
|
||||
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);
|
||||
|
||||
@@ -23,19 +23,19 @@ class LuhnChecksum extends Operation {
|
||||
this.description = "The Luhn algorithm, also known as the modulus 10 or mod 10 algorithm, is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers, IMEI numbers and Canadian Social Insurance Numbers.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Luhn_algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "number";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* Generates the Luhn Checksum from the input.
|
||||
*
|
||||
* @param {string} inputStr
|
||||
* @returns {number}
|
||||
*/
|
||||
run(input, args) {
|
||||
checksum(inputStr) {
|
||||
let even = false;
|
||||
return input.split("").reverse().reduce((acc, elem) => {
|
||||
|
||||
return inputStr.split("").reverse().reduce((acc, elem) => {
|
||||
// Convert element to integer.
|
||||
let temp = parseInt(elem, 10);
|
||||
|
||||
@@ -45,7 +45,6 @@ class LuhnChecksum extends Operation {
|
||||
|
||||
// If element is in an even position
|
||||
if (even) {
|
||||
|
||||
// Double the element and add the quotient and remainder together.
|
||||
temp = 2 * elem;
|
||||
temp = Math.floor(temp/10) + (temp % 10);
|
||||
@@ -53,10 +52,26 @@ class LuhnChecksum extends Operation {
|
||||
|
||||
even = !even;
|
||||
return acc + temp;
|
||||
|
||||
}, 0) % 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (!input) return "";
|
||||
|
||||
const checkSum = this.checksum(input);
|
||||
let checkDigit = this.checksum(input + "0");
|
||||
checkDigit = checkDigit === 0 ? 0 : (10-checkDigit);
|
||||
|
||||
return `Checksum: ${checkSum}
|
||||
Checkdigit: ${checkDigit}
|
||||
Luhn Validated String: ${input + "" + checkDigit}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LuhnChecksum;
|
||||
|
||||
@@ -144,7 +144,7 @@ class MultipleBombe extends Operation {
|
||||
* @param {number} progress - Progress (as a float in the range 0..1)
|
||||
*/
|
||||
updateStatus(nLoops, nStops, progress, start) {
|
||||
const elapsed = new Date().getTime() - 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);
|
||||
@@ -237,7 +237,7 @@ class MultipleBombe extends Operation {
|
||||
const totalRuns = choose(rotors.length, 3) * 6 * fourthRotors.length * reflectors.length;
|
||||
let nRuns = 0;
|
||||
let nStops = 0;
|
||||
const start = new Date().getTime();
|
||||
const start = Date.now();
|
||||
for (const rotor1 of rotors) {
|
||||
for (const rotor2 of rotors) {
|
||||
if (rotor2 === rotor1) {
|
||||
|
||||
47
src/core/operations/ParseObjectIDTimestamp.mjs
Normal file
47
src/core/operations/ParseObjectIDTimestamp.mjs
Normal file
@@ -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;
|
||||
@@ -33,9 +33,9 @@ class ParseQRCode extends Operation {
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
"match": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)",
|
||||
"pattern": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)",
|
||||
"flags": "",
|
||||
"args": [false],
|
||||
"useful": true
|
||||
|
||||
@@ -38,6 +38,13 @@ class ParseSSHHostKey extends Operation {
|
||||
]
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^\\s*([A-F\\d]{2}[,;:]){15,}[A-F\\d]{2}\\s*$",
|
||||
flags: "i",
|
||||
args: ["Hex"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,13 @@ class ParseUNIXFilePermissions extends Operation {
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^\\s*d[rxw-]{9}\\s*$",
|
||||
flags: "",
|
||||
args: []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,13 @@ class ParseUserAgent extends Operation {
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^(User-Agent:|Mozilla\\/)[^\\n\\r]+\\s*$",
|
||||
flags: "i",
|
||||
args: []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,13 +35,11 @@ class ParseX509Certificate extends Operation {
|
||||
"value": ["PEM", "DER Hex", "Base64", "Raw"]
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
"match": "^-+BEGIN CERTIFICATE-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE-+\\r?\\n?$",
|
||||
"pattern": "^-+BEGIN CERTIFICATE-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE-+\\r?\\n?$",
|
||||
"flags": "i",
|
||||
"args": [
|
||||
"PEM"
|
||||
]
|
||||
"args": ["PEM"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -60,6 +60,12 @@ class RawInflate extends Operation {
|
||||
value: false
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
entropyRange: [7.5, 8],
|
||||
args: [0, 0, INFLATE_BUFFER_TYPE, false, false]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -163,7 +163,7 @@ class RegularExpression extends Operation {
|
||||
case "List matches with capture groups":
|
||||
return Utils.escapeHtml(regexList(input, regex, displayTotal, true, true));
|
||||
default:
|
||||
return "Error: Invalid output format";
|
||||
throw new OperationError("Error: Invalid output format");
|
||||
}
|
||||
} catch (err) {
|
||||
throw new OperationError("Invalid regex. Details: " + err.message);
|
||||
|
||||
@@ -35,12 +35,15 @@ class RenderImage extends Operation {
|
||||
"value": ["Raw", "Base64", "Hex"]
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
"match": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)",
|
||||
"flags": "",
|
||||
"args": ["Raw"],
|
||||
"useful": true
|
||||
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"
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -35,6 +35,13 @@ class StripHTMLTags extends Operation {
|
||||
"value": true
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "(</html>|</div>|</body>)",
|
||||
flags: "i",
|
||||
args: [true, true]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,13 @@ class StripHTTPHeaders extends Operation {
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^HTTP(.|\\s)+?(\\r?\\n){2}",
|
||||
flags: "",
|
||||
args: []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,9 +24,9 @@ class URLDecode extends Operation {
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: ".*(?:%[\\da-f]{2}.*){4}",
|
||||
pattern: ".*(?:%[\\da-f]{2}.*){4}",
|
||||
flags: "i",
|
||||
args: []
|
||||
},
|
||||
|
||||
@@ -27,9 +27,9 @@ class Untar extends Operation {
|
||||
this.outputType = "List<File>";
|
||||
this.presentType = "html";
|
||||
this.args = [];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
"match": "^.{257}\\x75\\x73\\x74\\x61\\x72",
|
||||
"pattern": "^.{257}\\x75\\x73\\x74\\x61\\x72",
|
||||
"flags": "",
|
||||
"args": []
|
||||
}
|
||||
|
||||
@@ -40,12 +40,12 @@ class Unzip extends Operation {
|
||||
value: false
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^\\x50\\x4b(?:\\x03|\\x05|\\x07)(?:\\x04|\\x06|\\x08)",
|
||||
pattern: "^\\x50\\x4b(?:\\x03|\\x05|\\x07)(?:\\x04|\\x06|\\x08)",
|
||||
flags: "",
|
||||
args: ["", false]
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -59,9 +59,9 @@ class ZlibInflate extends Operation {
|
||||
value: false
|
||||
}
|
||||
];
|
||||
this.patterns = [
|
||||
this.checks = [
|
||||
{
|
||||
match: "^\\x78(\\x01|\\x9c|\\xda|\\x5e)",
|
||||
pattern: "^\\x78(\\x01|\\x9c|\\xda|\\x5e)",
|
||||
flags: "",
|
||||
args: [0, 0, "Adaptive", false, false]
|
||||
},
|
||||
|
||||
4
src/core/vendor/gost/gostRandom.mjs
vendored
4
src/core/vendor/gost/gostRandom.mjs
vendored
@@ -70,7 +70,7 @@ if (typeof document !== 'undefined') {
|
||||
try {
|
||||
// Mouse move event to fill random array
|
||||
document.addEventListener('mousemove', function (e) {
|
||||
randomRing.set((new Date().getTime() & 255) ^
|
||||
randomRing.set((Date.now() & 255) ^
|
||||
((e.clientX || e.pageX) & 255) ^
|
||||
((e.clientY || e.pageY) & 255));
|
||||
}, false);
|
||||
@@ -80,7 +80,7 @@ if (typeof document !== 'undefined') {
|
||||
try {
|
||||
// Keypress event to fill random array
|
||||
document.addEventListener('keydown', function (e) {
|
||||
randomRing.set((new Date().getTime() & 255) ^
|
||||
randomRing.set((Date.now() & 255) ^
|
||||
(e.keyCode & 255));
|
||||
}, false);
|
||||
} catch (e) {
|
||||
|
||||
@@ -282,11 +282,11 @@ export function help(input) {
|
||||
.map(result => result.hydrated);
|
||||
|
||||
if (matches && matches.length) {
|
||||
console.log(`${matches.length} result${matches.length > 1 ? "s" : ""} found.`);
|
||||
// console.log(`${matches.length} result${matches.length > 1 ? "s" : ""} found.`);
|
||||
return matches;
|
||||
}
|
||||
|
||||
console.log("No results found.");
|
||||
// console.log("No results found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Sitemap from "sitemap";
|
||||
import sm from "sitemap";
|
||||
import OperationConfig from "../../core/config/OperationConfig.json";
|
||||
|
||||
|
||||
@@ -10,24 +10,25 @@ import OperationConfig from "../../core/config/OperationConfig.json";
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
const sitemap = Sitemap.createSitemap({
|
||||
const smStream = new sm.SitemapStream({
|
||||
hostname: "https://gchq.github.io/CyberChef",
|
||||
});
|
||||
|
||||
sitemap.add({
|
||||
smStream.write({
|
||||
url: "/",
|
||||
changefreq: "weekly",
|
||||
priority: 1.0
|
||||
});
|
||||
|
||||
for (const op in OperationConfig) {
|
||||
sitemap.add({
|
||||
smStream.write({
|
||||
url: `/?op=${encodeURIComponent(op)}`,
|
||||
changeFreq: "yearly",
|
||||
priority: 0.5
|
||||
});
|
||||
}
|
||||
smStream.end();
|
||||
|
||||
const xml = sitemap.toString();
|
||||
|
||||
console.log(xml); // eslint-disable-line no-console
|
||||
sm.streamToPromise(smStream).then(
|
||||
buffer => console.log(buffer.toString()) // eslint-disable-line no-console
|
||||
);
|
||||
|
||||
@@ -51,7 +51,6 @@ class RecipeWaiter {
|
||||
}
|
||||
}.bind(this),
|
||||
onSort: function(evt) {
|
||||
this.updateZIndices();
|
||||
if (evt.from.id === "rec-list") {
|
||||
document.dispatchEvent(this.manager.statechange);
|
||||
}
|
||||
@@ -150,19 +149,6 @@ class RecipeWaiter {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the z-index property on each operation to make sure that operations higher in the list
|
||||
* have a higher index, meaning dropdowns are not hidden underneath subsequent operations.
|
||||
*/
|
||||
updateZIndices() {
|
||||
const operations = document.getElementById("rec-list").children;
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
const operation = operations[i];
|
||||
operation.style.zIndex = 100 + operations.length - i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for favourite dragover events.
|
||||
* If the element being dragged is an operation, displays a visual cue so that the user knows it can
|
||||
@@ -480,7 +466,6 @@ class RecipeWaiter {
|
||||
log.debug(`'${e.target.querySelector(".op-title").textContent}' added to recipe`);
|
||||
|
||||
this.triggerArgEvents(e.target);
|
||||
this.updateZIndices();
|
||||
window.dispatchEvent(this.manager.statechange);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ class SeasonalWaiter {
|
||||
// Konami code
|
||||
this.kkeys = [];
|
||||
window.addEventListener("keydown", this.konamiCodeListener.bind(this));
|
||||
|
||||
// CyberChef Challenge
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("43 6f 6e 67 72 61 74 75 6c 61 74 69 6f 6e 73 2c 20 79 6f 75 20 68 61 76 65 20 63 6f 6d 70 6c 65 74 65 64 20 43 79 62 65 72 43 68 65 66 20 63 68 61 6c 6c 65 6e 67 65 20 23 31 21 0a 0a 54 68 69 73 20 63 68 61 6c 6c 65 6e 67 65 20 65 78 70 6c 6f 72 65 64 20 68 65 78 61 64 65 63 69 6d 61 6c 20 65 6e 63 6f 64 69 6e 67 2e 20 54 6f 20 6c 65 61 72 6e 20 6d 6f 72 65 2c 20 76 69 73 69 74 20 77 69 6b 69 70 65 64 69 61 2e 6f 72 67 2f 77 69 6b 69 2f 48 65 78 61 64 65 63 69 6d 61 6c 2e 0a 0a 54 68 65 20 63 6f 64 65 20 66 6f 72 20 74 68 69 73 20 63 68 61 6c 6c 65 6e 67 65 20 69 73 20 39 64 34 63 62 63 65 66 2d 62 65 35 32 2d 34 37 35 31 2d 61 32 62 32 2d 38 33 33 38 65 36 34 30 39 34 31 36 20 28 6b 65 65 70 20 74 68 69 73 20 70 72 69 76 61 74 65 29 2e 0a 0a 54 68 65 20 6e 65 78 74 20 63 68 61 6c 6c 65 6e 67 65 20 63 61 6e 20 62 65 20 66 6f 75 6e 64 20 61 74 20 68 74 74 70 73 3a 2f 2f 70 61 73 74 65 62 69 6e 2e 63 6f 6d 2f 47 53 6e 54 41 6d 6b 56 2e");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class WindowWaiter {
|
||||
* focus is returned.
|
||||
*/
|
||||
windowBlur() {
|
||||
this.windowBlurTime = new Date().getTime();
|
||||
this.windowBlurTime = Date.now();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class WindowWaiter {
|
||||
* a long time and the browser has swapped out all its memory.
|
||||
*/
|
||||
windowFocus() {
|
||||
const unfocusedTime = new Date().getTime() - this.windowBlurTime;
|
||||
const unfocusedTime = Date.now() - this.windowBlurTime;
|
||||
if (unfocusedTime > 60000) {
|
||||
this.app.silentBake();
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ class WorkerWaiter {
|
||||
*/
|
||||
bakingComplete() {
|
||||
this.setBakingStatus(false);
|
||||
let duration = new Date().getTime() - this.bakeStartTime;
|
||||
let duration = Date.now() - this.bakeStartTime;
|
||||
duration = duration.toLocaleString() + "ms";
|
||||
const progress = this.getBakeProgress();
|
||||
|
||||
@@ -489,7 +489,7 @@ class WorkerWaiter {
|
||||
bake(recipeConfig, options, progress, step) {
|
||||
this.setBakingStatus(true);
|
||||
this.manager.recipe.updateBreakpointIndicator(false);
|
||||
this.bakeStartTime = new Date().getTime();
|
||||
this.bakeStartTime = Date.now();
|
||||
this.bakeId++;
|
||||
this.recipeConfig = recipeConfig;
|
||||
this.options = options;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import Chef from "../../src/core/Chef.mjs";
|
||||
import Utils from "../../src/core/Utils.mjs";
|
||||
import cliProgress from "cli-progress";
|
||||
|
||||
/**
|
||||
* Object to store and run the list of tests.
|
||||
@@ -47,68 +49,103 @@ class TestRegister {
|
||||
/**
|
||||
* Runs all the tests in the register.
|
||||
*/
|
||||
runTests () {
|
||||
console.log("Running tests...");
|
||||
return Promise.all(
|
||||
this.tests.map(function(test, i) {
|
||||
const chef = new Chef();
|
||||
async runTests () {
|
||||
const progBar = new cliProgress.SingleBar({
|
||||
format: formatter,
|
||||
stopOnComplete: true
|
||||
}, cliProgress.Presets.shades_classic);
|
||||
const testResults = [];
|
||||
|
||||
return chef.bake(
|
||||
test.input,
|
||||
test.recipeConfig,
|
||||
{},
|
||||
0,
|
||||
false
|
||||
).then(function(result) {
|
||||
const ret = {
|
||||
test: test,
|
||||
status: null,
|
||||
output: null,
|
||||
};
|
||||
console.log("Running operation tests...");
|
||||
progBar.start(this.tests.length, 0, {
|
||||
msg: "Setting up"
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
if (test.expectedError) {
|
||||
ret.status = "passing";
|
||||
} else {
|
||||
ret.status = "erroring";
|
||||
ret.output = result.error.displayStr;
|
||||
}
|
||||
} else {
|
||||
if (test.expectedError) {
|
||||
ret.status = "failing";
|
||||
ret.output = "Expected an error but did not receive one.";
|
||||
} else if (result.result === test.expectedOutput) {
|
||||
ret.status = "passing";
|
||||
} else if ("expectedMatch" in test && test.expectedMatch.test(result.result)) {
|
||||
ret.status = "passing";
|
||||
} else {
|
||||
ret.status = "failing";
|
||||
const expected = test.expectedOutput ? test.expectedOutput :
|
||||
test.expectedMatch ? test.expectedMatch.toString() : "unknown";
|
||||
ret.output = [
|
||||
"Expected",
|
||||
"\t" + expected.replace(/\n/g, "\n\t"),
|
||||
"Received",
|
||||
"\t" + result.result.replace(/\n/g, "\n\t"),
|
||||
].join("\n");
|
||||
}
|
||||
}
|
||||
for (const test of this.tests) {
|
||||
progBar.update(testResults.length, {
|
||||
msg: test.name
|
||||
});
|
||||
|
||||
return ret;
|
||||
});
|
||||
})
|
||||
);
|
||||
const chef = new Chef();
|
||||
const result = await chef.bake(
|
||||
test.input,
|
||||
test.recipeConfig,
|
||||
{},
|
||||
0,
|
||||
false
|
||||
);
|
||||
|
||||
const ret = {
|
||||
test: test,
|
||||
status: null,
|
||||
output: null,
|
||||
duration: result.duration
|
||||
};
|
||||
|
||||
if (result.error) {
|
||||
if (test.expectedError) {
|
||||
ret.status = "passing";
|
||||
} else {
|
||||
ret.status = "erroring";
|
||||
ret.output = result.error.displayStr;
|
||||
}
|
||||
} else {
|
||||
if (test.expectedError) {
|
||||
ret.status = "failing";
|
||||
ret.output = "Expected an error but did not receive one.";
|
||||
} else if (result.result === test.expectedOutput) {
|
||||
ret.status = "passing";
|
||||
} else if ("expectedMatch" in test && test.expectedMatch.test(result.result)) {
|
||||
ret.status = "passing";
|
||||
} else if ("unexpectedMatch" in test && !test.unexpectedMatch.test(result.result)) {
|
||||
ret.status = "passing";
|
||||
} else {
|
||||
ret.status = "failing";
|
||||
const expected = test.expectedOutput ? test.expectedOutput :
|
||||
test.expectedMatch ? test.expectedMatch.toString() :
|
||||
test.unexpectedMatch ? "to not find " + test.unexpectedMatch.toString() :
|
||||
"unknown";
|
||||
ret.output = [
|
||||
"Expected",
|
||||
"\t" + expected.replace(/\n/g, "\n\t"),
|
||||
"Received",
|
||||
"\t" + result.result.replace(/\n/g, "\n\t"),
|
||||
].join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
testResults.push(ret);
|
||||
progBar.increment();
|
||||
}
|
||||
|
||||
return testResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all api related tests and wrap results in report format
|
||||
*/
|
||||
runApiTests() {
|
||||
return Promise.all(this.apiTests.map(async function(test, i) {
|
||||
async runApiTests() {
|
||||
const progBar = new cliProgress.SingleBar({
|
||||
format: formatter,
|
||||
stopOnComplete: true
|
||||
}, cliProgress.Presets.shades_classic);
|
||||
const testResults = [];
|
||||
|
||||
console.log("Running Node API tests...");
|
||||
progBar.start(this.apiTests.length, 0, {
|
||||
msg: "Setting up"
|
||||
});
|
||||
|
||||
global.TESTING = true;
|
||||
for (const test of this.apiTests) {
|
||||
progBar.update(testResults.length, {
|
||||
msg: test.name
|
||||
});
|
||||
|
||||
const result = {
|
||||
test: test,
|
||||
status: null,
|
||||
output: null,
|
||||
output: null
|
||||
};
|
||||
try {
|
||||
await test.run();
|
||||
@@ -117,10 +154,37 @@ class TestRegister {
|
||||
result.status = "erroring";
|
||||
result.output = e.message;
|
||||
}
|
||||
return result;
|
||||
}));
|
||||
|
||||
testResults.push(result);
|
||||
progBar.increment();
|
||||
}
|
||||
|
||||
return testResults;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formatter for the progress bar
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Object} params
|
||||
* @param {Object} payload
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatter(options, params, payload) {
|
||||
const bar = options.barCompleteString.substr(0, Math.round(params.progress * options.barsize)) +
|
||||
options.barIncompleteString.substr(0, Math.round((1-params.progress) * options.barsize));
|
||||
|
||||
const percentage = Math.floor(params.progress * 100),
|
||||
duration = Math.floor((Date.now() - params.startTime) / 1000);
|
||||
|
||||
let testName = payload.msg ? payload.msg : "";
|
||||
if (params.value >= params.total) testName = "Tests completed";
|
||||
testName = Utils.truncate(testName, 25).padEnd(25, " ");
|
||||
|
||||
return `${testName} ${bar} ${params.value}/${params.total} | ${percentage}% | Duration: ${duration}s`;
|
||||
}
|
||||
|
||||
// Export an instance to make a singleton
|
||||
export default new TestRegister();
|
||||
|
||||
@@ -33,6 +33,10 @@ function handleTestResult(testStatus, testResult) {
|
||||
testStatus.allTestsPassing = testStatus.allTestsPassing && testResult.status === "passing";
|
||||
testStatus.counts[testResult.status] = (testStatus.counts[testResult.status] || 0) + 1;
|
||||
testStatus.counts.total += 1;
|
||||
|
||||
if (testResult.duration > 2000) {
|
||||
console.log(`'${testResult.test.name}' took ${(testResult.duration / 1000).toFixed(1)}s to complete`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,8 +46,6 @@ function handleTestResult(testStatus, testResult) {
|
||||
* @param {Object[]} results - results from TestRegister
|
||||
*/
|
||||
export function logTestReport(testStatus, results) {
|
||||
console.log("Tests completed.");
|
||||
|
||||
results.forEach(r => handleTestResult(testStatus, r));
|
||||
|
||||
console.log();
|
||||
@@ -80,8 +82,9 @@ export function logTestReport(testStatus, results) {
|
||||
* Fail if the process takes longer than 60 seconds.
|
||||
*/
|
||||
export function setLongTestFailure() {
|
||||
const timeLimit = 120;
|
||||
setTimeout(function() {
|
||||
console.log("Tests took longer than 60 seconds to run, returning.");
|
||||
console.log(`Tests took longer than ${timeLimit} seconds to run, returning.`);
|
||||
process.exit(1);
|
||||
}, 60 * 1000);
|
||||
}, timeLimit * 1000);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ setLongTestFailure();
|
||||
|
||||
const logOpsTestReport = logTestReport.bind(null, testStatus);
|
||||
|
||||
TestRegister.runApiTests()
|
||||
.then(logOpsTestReport);
|
||||
|
||||
(async function() {
|
||||
const results = await TestRegister.runApiTests();
|
||||
logOpsTestReport(results);
|
||||
})();
|
||||
|
||||
@@ -588,7 +588,7 @@ Password: 034148`;
|
||||
const result = await chef.generatePGPKeyPair("Back To the Drawing Board", {
|
||||
keyType: "ECC-256",
|
||||
});
|
||||
assert.strictEqual(result.toString().length, 2005);
|
||||
assert.strictEqual(result.toString().length, 2007);
|
||||
}),
|
||||
|
||||
it("Generate UUID", () => {
|
||||
|
||||
@@ -100,6 +100,7 @@ import "./tests/Lorenz.mjs";
|
||||
import "./tests/LuhnChecksum.mjs";
|
||||
import "./tests/CipherSaber2.mjs";
|
||||
import "./tests/Colossus.mjs";
|
||||
import "./tests/ParseObjectIDTimestamp.mjs";
|
||||
|
||||
|
||||
// Cannot test operations that use the File type yet
|
||||
@@ -116,5 +117,7 @@ setLongTestFailure();
|
||||
|
||||
const logOpsTestReport = logTestReport.bind(null, testStatus);
|
||||
|
||||
TestRegister.runTests()
|
||||
.then(logOpsTestReport);
|
||||
(async function() {
|
||||
const results = await TestRegister.runTests();
|
||||
logOpsTestReport(results);
|
||||
})();
|
||||
|
||||
43
tests/operations/samples/Images.mjs
Normal file
43
tests/operations/samples/Images.mjs
Normal file
File diff suppressed because one or more lines are too long
32
tests/operations/tests/ConvertToNATOAlphabet.mjs
Normal file
32
tests/operations/tests/ConvertToNATOAlphabet.mjs
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @author MarvinJWendt [git@marvinjwendt.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Convert to NATO alphabet: nothing",
|
||||
input: "",
|
||||
expectedOutput: "",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert to NATO alphabet",
|
||||
args: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Convert to NATO alphabet: full alphabet with numbers",
|
||||
input: "abcdefghijklmnopqrstuvwxyz0123456789,/.",
|
||||
expectedOutput: "Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu Zero One Two Three Four Five Six Seven Eight Nine Comma Fraction bar Full stop ",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert to NATO alphabet",
|
||||
args: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
@@ -92,6 +92,19 @@ TestRegister.addTests([
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "0x with Comma to Ascii",
|
||||
input: "0x74,0x65,0x73,0x74,0x20,0x73,0x74,0x72,0x69,0x6e,0x67",
|
||||
expectedOutput: "test string",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "From Hex",
|
||||
"args": [
|
||||
"0x with comma"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -11,7 +11,29 @@ TestRegister.addTests([
|
||||
{
|
||||
name: "Luhn Checksum on standard data",
|
||||
input: "35641709012469",
|
||||
expectedOutput: "7",
|
||||
expectedOutput: "Checksum: 7\nCheckdigit: 0\nLuhn Validated String: 356417090124690",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Luhn Checksum",
|
||||
args: []
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Luhn Checksum on standard data 2",
|
||||
input: "896101950123440000",
|
||||
expectedOutput: "Checksum: 5\nCheckdigit: 1\nLuhn Validated String: 8961019501234400001",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Luhn Checksum",
|
||||
args: []
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Luhn Checksum on standard data 3",
|
||||
input: "35726908971331",
|
||||
expectedOutput: "Checksum: 6\nCheckdigit: 7\nLuhn Validated String: 357269089713317",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Luhn Checksum",
|
||||
@@ -33,7 +55,7 @@ TestRegister.addTests([
|
||||
{
|
||||
name: "Luhn Checksum on empty data",
|
||||
input: "",
|
||||
expectedOutput: "0",
|
||||
expectedOutput: "",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Luhn Checksum",
|
||||
|
||||
File diff suppressed because one or more lines are too long
24
tests/operations/tests/ParseObjectIDTimestamp.mjs
Normal file
24
tests/operations/tests/ParseObjectIDTimestamp.mjs
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Parse ObjectID timestamp tests
|
||||
*
|
||||
* @author dmfj [dominic@dmfj.io]
|
||||
*
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Parse ISO timestamp from ObjectId",
|
||||
input: "000000000000000000000000",
|
||||
expectedOutput: "1970-01-01T00:00:00.000Z",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Parse ObjectID timestamp",
|
||||
args: [],
|
||||
}
|
||||
],
|
||||
}
|
||||
]);
|
||||
Reference in New Issue
Block a user