mirror of
https://github.com/gchq/CyberChef
synced 2025-12-05 23:53:27 +00:00
Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2d30bf668 | ||
|
|
dcb59edb79 | ||
|
|
84d31c1d59 | ||
|
|
58d41f4458 | ||
|
|
360effb839 | ||
|
|
d923c99975 | ||
|
|
2b538061e9 | ||
|
|
9fa7edffbf | ||
|
|
24a47445f6 | ||
|
|
68278267e1 | ||
|
|
8148c1a8a8 | ||
|
|
ce61bcc078 | ||
|
|
49e2b05a11 | ||
|
|
481f2a4717 | ||
|
|
c01c076561 | ||
|
|
2391e08ac1 | ||
|
|
a7a2fe243a | ||
|
|
b5c655dd70 | ||
|
|
d84a61d108 | ||
|
|
13abbd2c5d | ||
|
|
d22eac9f35 | ||
|
|
d71aa8d7e2 | ||
|
|
7a4e0301d2 | ||
|
|
42826e542d | ||
|
|
42d1c9403c | ||
|
|
ab43635583 | ||
|
|
b8ecd83bfd | ||
|
|
0db0ced1ab | ||
|
|
3d20833d42 | ||
|
|
7d09ba5669 | ||
|
|
310ff30278 | ||
|
|
821bc9405c | ||
|
|
d54d66cffc | ||
|
|
d2b4c40357 | ||
|
|
f48af97ddc | ||
|
|
f1264d6310 | ||
|
|
4dc5a1499a | ||
|
|
58a8af20a6 | ||
|
|
75a58f465c | ||
|
|
613cbaa556 | ||
|
|
0d0a634255 | ||
|
|
105090db60 | ||
|
|
3e9c75f735 | ||
|
|
d42075072b | ||
|
|
6a099f0813 | ||
|
|
9af5e40071 | ||
|
|
4bf2a29070 | ||
|
|
069d0e48c1 | ||
|
|
c8cb2692dd | ||
|
|
74a22bcf9c | ||
|
|
8b44927cb6 | ||
|
|
3209c94622 | ||
|
|
6f8a5ea1be | ||
|
|
69837837b0 | ||
|
|
03d8bf2836 | ||
|
|
eca2c142f3 | ||
|
|
715f7bbbc2 | ||
|
|
291ebd5c12 | ||
|
|
ba04cac7ac | ||
|
|
acb8c0b5af | ||
|
|
0c14bacea7 | ||
|
|
4cabb849f3 | ||
|
|
445a85798b | ||
|
|
55775f48e9 | ||
|
|
4bd923dc06 | ||
|
|
439654ed7f | ||
|
|
69797e58cb | ||
|
|
d1961ca3fa | ||
|
|
2e9b1e079c | ||
|
|
3dfaaf4c25 | ||
|
|
fcc39a0397 | ||
|
|
0602f457ce | ||
|
|
d00b0f4c0e | ||
|
|
5e68959c03 | ||
|
|
ad4451a757 | ||
|
|
4d8127a7d9 | ||
|
|
cd2c8078c8 | ||
|
|
ee360521bb | ||
|
|
04b0b8c723 | ||
|
|
b3ac8d0835 | ||
|
|
1a88a0164c | ||
|
|
8b77ad7748 | ||
|
|
8d1f668fc5 | ||
|
|
68fbbb64db | ||
|
|
8bba4b2973 | ||
|
|
2307325af8 | ||
|
|
4e57b4be88 | ||
|
|
abdd70c6fa | ||
|
|
c077b22410 | ||
|
|
9787ab04cd | ||
|
|
79d3c90026 | ||
|
|
c2068b343b | ||
|
|
9e63e40dab | ||
|
|
6424839731 | ||
|
|
863a525625 | ||
|
|
f82a727e24 | ||
|
|
995fcab071 | ||
|
|
c5270d75a1 | ||
|
|
324c409ff1 | ||
|
|
1db8e6dddc | ||
|
|
c49a770c59 | ||
|
|
dd9ba4d250 | ||
|
|
0e601d5b5f | ||
|
|
ebb632e888 | ||
|
|
4db2335107 | ||
|
|
26a2fb6662 | ||
|
|
4c1521a98e | ||
|
|
df8abb099c | ||
|
|
fe1332f18e | ||
|
|
cb9ab7a2c9 | ||
|
|
3a6b2875d5 | ||
|
|
766de7e6fa | ||
|
|
13439e100e | ||
|
|
5ac469b174 | ||
|
|
8ac5b48493 | ||
|
|
1a827ef44f | ||
|
|
0f0e346a02 | ||
|
|
2a6db47aeb | ||
|
|
19b7957523 | ||
|
|
0d2cb02f97 | ||
|
|
7d8d80ca2c | ||
|
|
0449c46b38 | ||
|
|
cd0c86e0d6 | ||
|
|
a56f92cdee | ||
|
|
c82971f8db | ||
|
|
cb2c376c63 | ||
|
|
bc00fa0694 | ||
|
|
c86007da71 | ||
|
|
1bf513ca74 | ||
|
|
29411c903f | ||
|
|
017dde364c | ||
|
|
c123d7370a | ||
|
|
76f1e5e8f3 | ||
|
|
4e466c7886 | ||
|
|
d469fb9c58 | ||
|
|
4c285bce57 | ||
|
|
050ab03448 | ||
|
|
40acf751a8 | ||
|
|
126ad585c0 | ||
|
|
1d04b649e0 | ||
|
|
b750006cf0 | ||
|
|
3c16b839b6 | ||
|
|
32aea6b86c | ||
|
|
688c2d0df5 | ||
|
|
ede75530d0 | ||
|
|
3ae225ac59 | ||
|
|
fd07b89028 | ||
|
|
0cea56dc62 | ||
|
|
bb44268c30 | ||
|
|
19b3dcf1c2 | ||
|
|
71e0a4e0ce | ||
|
|
7f2e879e24 | ||
|
|
840e44deac | ||
|
|
f7707faece | ||
|
|
b631e3fef6 | ||
|
|
b0fb9db4b8 | ||
|
|
c7e9115994 | ||
|
|
f2d115ee4d | ||
|
|
0198f05112 | ||
|
|
729307336e | ||
|
|
f4f9b5c91c | ||
|
|
f355fe3447 | ||
|
|
321718d43a | ||
|
|
a1b161493c | ||
|
|
5acee80463 | ||
|
|
e6932401ad | ||
|
|
7a4eff0f5c | ||
|
|
8b533e9893 | ||
|
|
02b92c7977 | ||
|
|
e386863bdb | ||
|
|
16be7cb28a | ||
|
|
454ef0076b | ||
|
|
18693d2471 | ||
|
|
5a9583c970 | ||
|
|
0046f7e3d7 | ||
|
|
8d3836cb16 | ||
|
|
9829491c4c | ||
|
|
e6fb0be1d0 | ||
|
|
d02124550b | ||
|
|
6aa9d2b492 |
@@ -9,6 +9,6 @@ trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[{package.json,.travis.yml}]
|
||||
[{package.json,.travis.yml,nightwatch.json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
"ecmaFeatures": {
|
||||
"impliedStrict": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
@@ -87,10 +89,20 @@
|
||||
"no-var": "error",
|
||||
"prefer-const": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "tests/**/*",
|
||||
"rules": {
|
||||
"no-unused-expressions": "off",
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globals": {
|
||||
"$": false,
|
||||
"jQuery": false,
|
||||
"log": false,
|
||||
"app": false,
|
||||
|
||||
"COMPILE_TIME": false,
|
||||
"COMPILE_MSG": false,
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,4 +9,5 @@ docs/*
|
||||
src/core/config/modules/*
|
||||
src/core/config/OperationConfig.json
|
||||
src/core/operations/index.mjs
|
||||
tests/browser/output/*
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- node
|
||||
addons:
|
||||
chrome: stable
|
||||
install: npm install
|
||||
before_script:
|
||||
- npm install -g grunt
|
||||
- export NODE_OPTIONS=--max_old_space_size=2048
|
||||
script:
|
||||
- grunt lint
|
||||
- grunt test
|
||||
- grunt docs
|
||||
- grunt node
|
||||
- grunt prod --msg="$COMPILE_MSG"
|
||||
- xvfb-run --server-args="-screen 0 1200x800x24" grunt testui
|
||||
before_deploy:
|
||||
- grunt exec:sitemap
|
||||
- grunt copy:ghPages
|
||||
|
||||
42
CHANGELOG.md
42
CHANGELOG.md
@@ -2,6 +2,30 @@
|
||||
All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
|
||||
|
||||
|
||||
### [8.24.0] - 2019-02-08
|
||||
- 'DNS over HTTPS' operation added [@h345983745] | [#489]
|
||||
|
||||
### [8.23.1] - 2019-01-18
|
||||
- 'Convert co-ordinate format' operation added [@j433866] | [#476]
|
||||
|
||||
### [8.23.0] - 2019-01-18
|
||||
- 'YARA Rules' operation added [@artemisbot] | [#468]
|
||||
|
||||
### [8.22.0] - 2019-01-10
|
||||
- 'Subsection' operation added [@j433866] | [#467]
|
||||
|
||||
### [8.21.0] - 2019-01-10
|
||||
- 'To Case Insensitive Regex' and 'From Case Insensitive Regex' operations added [@masq] | [#461]
|
||||
|
||||
### [8.20.0] - 2019-01-09
|
||||
- 'Generate Lorem Ipsum' operation added [@klaxon1] | [#455]
|
||||
|
||||
### [8.19.0] - 2018-12-30
|
||||
- UI test suite added to confirm that the app loads correctly in a reasonable time and that various operations from each module can be run [@n1474335] | [#458]
|
||||
|
||||
### [8.18.0] - 2018-12-26
|
||||
- 'Split Colour Channels' operation added [@artemisbot] | [#449]
|
||||
|
||||
### [8.17.0] - 2018-12-25
|
||||
- 'Generate QR Code' and 'Parse QR Code' operations added [@j433866] | [#448]
|
||||
|
||||
@@ -82,6 +106,14 @@ All major and minor version changes will be documented in this file. Details of
|
||||
|
||||
|
||||
|
||||
[8.24.0]: https://github.com/gchq/CyberChef/releases/tag/v8.24.0
|
||||
[8.23.1]: https://github.com/gchq/CyberChef/releases/tag/v8.23.1
|
||||
[8.23.0]: https://github.com/gchq/CyberChef/releases/tag/v8.23.0
|
||||
[8.22.0]: https://github.com/gchq/CyberChef/releases/tag/v8.22.0
|
||||
[8.21.0]: https://github.com/gchq/CyberChef/releases/tag/v8.21.0
|
||||
[8.20.0]: https://github.com/gchq/CyberChef/releases/tag/v8.20.0
|
||||
[8.19.0]: https://github.com/gchq/CyberChef/releases/tag/v8.19.0
|
||||
[8.18.0]: https://github.com/gchq/CyberChef/releases/tag/v8.18.0
|
||||
[8.17.0]: https://github.com/gchq/CyberChef/releases/tag/v8.17.0
|
||||
[8.16.0]: https://github.com/gchq/CyberChef/releases/tag/v8.16.0
|
||||
[8.15.0]: https://github.com/gchq/CyberChef/releases/tag/v8.15.0
|
||||
@@ -109,6 +141,7 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[@d98762625]: https://github.com/d98762625
|
||||
[@j433866]: https://github.com/j433866
|
||||
[@GCHQ77703]: https://github.com/GCHQ77703
|
||||
[@h345983745]: https://github.com/h345983745
|
||||
[@artemisbot]: https://github.com/artemisbot
|
||||
[@picapi]: https://github.com/picapi
|
||||
[@Dachande663]: https://github.com/Dachande663
|
||||
@@ -122,6 +155,7 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[@tcode2k16]: https://github.com/tcode2k16
|
||||
[@Cynser]: https://github.com/Cynser
|
||||
[@anthony-arnold]: https://github.com/anthony-arnold
|
||||
[@masq]: https://github.com/masq
|
||||
|
||||
[#95]: https://github.com/gchq/CyberChef/pull/299
|
||||
[#173]: https://github.com/gchq/CyberChef/pull/173
|
||||
@@ -150,3 +184,11 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[#443]: https://github.com/gchq/CyberChef/pull/443
|
||||
[#446]: https://github.com/gchq/CyberChef/pull/446
|
||||
[#448]: https://github.com/gchq/CyberChef/pull/448
|
||||
[#449]: https://github.com/gchq/CyberChef/pull/449
|
||||
[#455]: https://github.com/gchq/CyberChef/pull/455
|
||||
[#458]: https://github.com/gchq/CyberChef/pull/458
|
||||
[#461]: https://github.com/gchq/CyberChef/pull/461
|
||||
[#467]: https://github.com/gchq/CyberChef/pull/467
|
||||
[#468]: https://github.com/gchq/CyberChef/pull/468
|
||||
[#476]: https://github.com/gchq/CyberChef/pull/476
|
||||
[#489]: https://github.com/gchq/CyberChef/pull/489
|
||||
|
||||
121
Gruntfile.js
121
Gruntfile.js
@@ -30,8 +30,12 @@ module.exports = function (grunt) {
|
||||
["clean:node", "clean:config", "exec:generateConfig", "webpack:node", "chmod:build"]);
|
||||
|
||||
grunt.registerTask("test",
|
||||
"A task which runs all the tests in test/tests.",
|
||||
["exec:generateConfig", "exec:tests"]);
|
||||
"A task which runs all the operation tests in the tests directory.",
|
||||
["exec:generateConfig", "exec:opTests"]);
|
||||
|
||||
grunt.registerTask("testui",
|
||||
"A task which runs all the UI tests in the tests directory. The prod task must already have been run.",
|
||||
["connect:prod", "exec:browserTests"]);
|
||||
|
||||
grunt.registerTask("docs",
|
||||
"Compiles documentation in the /docs directory.",
|
||||
@@ -67,6 +71,7 @@ module.exports = function (grunt) {
|
||||
grunt.loadNpmTasks("grunt-exec");
|
||||
grunt.loadNpmTasks("grunt-accessibility");
|
||||
grunt.loadNpmTasks("grunt-concurrent");
|
||||
grunt.loadNpmTasks("grunt-contrib-connect");
|
||||
|
||||
|
||||
// Project configuration
|
||||
@@ -144,11 +149,11 @@ module.exports = function (grunt) {
|
||||
options: {
|
||||
configFile: "./.eslintrc.json"
|
||||
},
|
||||
configs: ["*.js"],
|
||||
configs: ["*.{js,mjs}"],
|
||||
core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
|
||||
web: ["src/web/**/*.{js,mjs}"],
|
||||
node: ["src/node/**/*.{js,mjs}"],
|
||||
tests: ["test/**/*.{js,mjs}"],
|
||||
tests: ["tests/**/*.{js,mjs}"],
|
||||
},
|
||||
jsdoc: {
|
||||
options: {
|
||||
@@ -180,42 +185,45 @@ module.exports = function (grunt) {
|
||||
},
|
||||
webpack: {
|
||||
options: webpackConfig,
|
||||
web: {
|
||||
mode: "production",
|
||||
target: "web",
|
||||
entry: Object.assign({
|
||||
main: "./src/web/index.js",
|
||||
sitemap: "./src/web/static/sitemap.js"
|
||||
}, moduleEntryPoints),
|
||||
output: {
|
||||
path: __dirname + "/build/prod"
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"./config/modules/OpModules": "./config/modules/Default"
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(BUILD_CONSTANTS),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "index.html",
|
||||
template: "./src/web/html/index.html",
|
||||
chunks: ["main"],
|
||||
compileTime: compileTime,
|
||||
version: pkg.version,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true
|
||||
web: () => {
|
||||
return {
|
||||
mode: "production",
|
||||
target: "web",
|
||||
entry: Object.assign({
|
||||
main: "./src/web/index.js",
|
||||
sitemap: "./src/web/static/sitemap.js"
|
||||
}, moduleEntryPoints),
|
||||
output: {
|
||||
path: __dirname + "/build/prod",
|
||||
globalObject: "this"
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"./config/modules/OpModules": "./config/modules/Default"
|
||||
}
|
||||
}),
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: "static",
|
||||
reportFilename: "BundleAnalyzerReport.html",
|
||||
openAnalyzer: false
|
||||
}),
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(BUILD_CONSTANTS),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "index.html",
|
||||
template: "./src/web/html/index.html",
|
||||
chunks: ["main"],
|
||||
compileTime: compileTime,
|
||||
version: pkg.version,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true
|
||||
}
|
||||
}),
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: "static",
|
||||
reportFilename: "BundleAnalyzerReport.html",
|
||||
openAnalyzer: false
|
||||
}),
|
||||
]
|
||||
};
|
||||
},
|
||||
webInline: {
|
||||
mode: "production",
|
||||
@@ -244,19 +252,6 @@ module.exports = function (grunt) {
|
||||
}),
|
||||
]
|
||||
},
|
||||
tests: {
|
||||
mode: "development",
|
||||
target: "node",
|
||||
entry: "./test/index.mjs",
|
||||
externals: [NodeExternals()],
|
||||
output: {
|
||||
filename: "index.js",
|
||||
path: __dirname + "/build/test"
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(BUILD_CONSTANTS)
|
||||
]
|
||||
},
|
||||
node: {
|
||||
mode: "production",
|
||||
target: "node",
|
||||
@@ -305,6 +300,9 @@ module.exports = function (grunt) {
|
||||
"./config/modules/OpModules": "./config/modules/Default"
|
||||
}
|
||||
},
|
||||
output: {
|
||||
globalObject: "this",
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(BUILD_CONSTANTS),
|
||||
new HtmlWebpackPlugin({
|
||||
@@ -318,6 +316,14 @@ module.exports = function (grunt) {
|
||||
}
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
prod: {
|
||||
options: {
|
||||
port: 8000,
|
||||
base: "build/prod/"
|
||||
}
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
ghPages: {
|
||||
options: {
|
||||
@@ -389,16 +395,17 @@ module.exports = function (grunt) {
|
||||
generateConfig: {
|
||||
command: [
|
||||
"echo '\n--- Regenerating config files. ---'",
|
||||
"mkdir -p src/core/config/modules",
|
||||
"echo 'export default {};\n' > src/core/config/modules/OpModules.mjs",
|
||||
"echo '[]\n' > src/core/config/OperationConfig.json",
|
||||
"echo [] > src/core/config/OperationConfig.json",
|
||||
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateOpsIndex.mjs",
|
||||
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateConfig.mjs",
|
||||
"echo '--- Config scripts finished. ---\n'"
|
||||
].join(";")
|
||||
},
|
||||
tests: {
|
||||
command: "node --experimental-modules --no-warnings --no-deprecation test/index.mjs"
|
||||
opTests: {
|
||||
command: "node --experimental-modules --no-warnings --no-deprecation tests/operations/index.mjs"
|
||||
},
|
||||
browserTests: {
|
||||
command: "./node_modules/.bin/nightwatch --env prod,inline"
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ module.exports = function(api) {
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
"babel-plugin-syntax-dynamic-import",
|
||||
["babel-plugin-transform-builtin-extend", {
|
||||
"globals": ["Error"]
|
||||
}]
|
||||
|
||||
34
nightwatch.json
Normal file
34
nightwatch.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"src_folders": ["tests/browser"],
|
||||
"output_folder": "tests/browser/output",
|
||||
|
||||
"test_settings": {
|
||||
|
||||
"default": {
|
||||
"launch_url": "http://localhost:8080",
|
||||
"webdriver": {
|
||||
"start_process": true,
|
||||
"server_path": "./node_modules/.bin/chromedriver",
|
||||
"port": 9515,
|
||||
"log_path": false
|
||||
},
|
||||
"desiredCapabilities": {
|
||||
"browserName": "chrome"
|
||||
}
|
||||
},
|
||||
|
||||
"dev": {
|
||||
"launch_url": "http://localhost:8080"
|
||||
},
|
||||
|
||||
"prod": {
|
||||
"launch_url": "http://localhost:8000/index.html"
|
||||
},
|
||||
|
||||
"inline": {
|
||||
"launch_url": "http://localhost:8000/cyberchef.htm"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
2462
package-lock.json
generated
2462
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
58
package.json
58
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cyberchef",
|
||||
"version": "8.17.1",
|
||||
"version": "8.25.0",
|
||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||
"author": "n1474335 <n1474335@gmail.com>",
|
||||
"homepage": "https://gchq.github.io/CyberChef",
|
||||
@@ -30,22 +30,25 @@
|
||||
"main": "build/node/CyberChef.js",
|
||||
"bugs": "https://github.com/gchq/CyberChef/issues",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.5",
|
||||
"@babel/preset-env": "^7.1.5",
|
||||
"autoprefixer": "^9.3.1",
|
||||
"@babel/core": "^7.2.2",
|
||||
"@babel/preset-env": "^7.2.3",
|
||||
"autoprefixer": "^9.4.3",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.4",
|
||||
"bootstrap": "^4.1.3",
|
||||
"colors": "^1.3.2",
|
||||
"css-loader": "^1.0.1",
|
||||
"eslint": "^5.8.0",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"bootstrap": "^4.2.1",
|
||||
"chromedriver": "^2.45.0",
|
||||
"colors": "^1.3.3",
|
||||
"css-loader": "^2.1.0",
|
||||
"eslint": "^5.12.1",
|
||||
"exports-loader": "^0.7.0",
|
||||
"extract-text-webpack-plugin": "^4.0.0-alpha0",
|
||||
"file-loader": "^2.0.0",
|
||||
"file-loader": "^3.0.1",
|
||||
"grunt": "^1.0.3",
|
||||
"grunt-accessibility": "~6.0.0",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-concurrent": "^2.3.1",
|
||||
"grunt-contrib-clean": "~2.0.0",
|
||||
"grunt-contrib-connect": "^2.0.0",
|
||||
"grunt-contrib-copy": "~1.0.0",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-eslint": "^21.0.0",
|
||||
@@ -56,7 +59,9 @@
|
||||
"imports-loader": "^0.8.0",
|
||||
"ink-docstrap": "^1.3.2",
|
||||
"jsdoc-babel": "^0.5.0",
|
||||
"node-sass": "^4.10.0",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"nightwatch": "^1.0.18",
|
||||
"node-sass": "^4.11.0",
|
||||
"postcss-css-variables": "^0.11.0",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
@@ -66,9 +71,9 @@
|
||||
"style-loader": "^0.23.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"web-resource-inliner": "^4.2.1",
|
||||
"webpack": "^4.25.1",
|
||||
"webpack": "^4.28.3",
|
||||
"webpack-bundle-analyzer": "^3.0.3",
|
||||
"webpack-dev-server": "^3.1.10",
|
||||
"webpack-dev-server": "^3.1.14",
|
||||
"webpack-node-externals": "^1.7.2",
|
||||
"worker-loader": "^2.0.0"
|
||||
},
|
||||
@@ -77,10 +82,10 @@
|
||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bignumber.js": "^8.0.1",
|
||||
"bignumber.js": "^8.0.2",
|
||||
"bootstrap-colorpicker": "^2.5.3",
|
||||
"bootstrap-material-design": "^4.1.1",
|
||||
"bson": "^3.0.2",
|
||||
"bson": "^4.0.1",
|
||||
"chi-squared": "^1.1.0",
|
||||
"crypto-api": "^0.8.3",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
@@ -91,48 +96,51 @@
|
||||
"esmangle": "^1.0.1",
|
||||
"esprima": "^4.0.1",
|
||||
"exif-parser": "^0.1.12",
|
||||
"file-saver": "^2.0.0-rc.4",
|
||||
"file-saver": "^2.0.0",
|
||||
"geodesy": "^1.1.3",
|
||||
"highlight.js": "^9.13.1",
|
||||
"jimp": "^0.6.0",
|
||||
"jquery": "^3.3.1",
|
||||
"js-crc": "^0.2.0",
|
||||
"js-sha3": "^0.8.0",
|
||||
"jsesc": "^2.5.1",
|
||||
"jsesc": "^2.5.2",
|
||||
"jsonpath": "^1.0.0",
|
||||
"jsonwebtoken": "^8.3.0",
|
||||
"jsonwebtoken": "^8.4.0",
|
||||
"jsqr": "^1.1.1",
|
||||
"jsrsasign": "8.0.12",
|
||||
"kbpgp": "^2.0.82",
|
||||
"libyara-wasm": "0.0.12",
|
||||
"lodash": "^4.17.11",
|
||||
"loglevel": "^1.6.1",
|
||||
"loglevel-message-prefix": "^3.0.0",
|
||||
"moment": "^2.22.2",
|
||||
"moment": "^2.23.0",
|
||||
"moment-timezone": "^0.5.23",
|
||||
"ngeohash": "^0.6.0",
|
||||
"ngeohash": "^0.6.3",
|
||||
"node-forge": "^0.7.6",
|
||||
"node-md6": "^0.1.0",
|
||||
"notepack.io": "^2.1.3",
|
||||
"notepack.io": "^2.2.0",
|
||||
"nwmatcher": "^1.4.4",
|
||||
"otp": "^0.1.3",
|
||||
"popper.js": "^1.14.4",
|
||||
"popper.js": "^1.14.6",
|
||||
"qr-image": "^3.2.0",
|
||||
"scryptsy": "^2.0.0",
|
||||
"snackbarjs": "^1.1.0",
|
||||
"sortablejs": "^1.7.0",
|
||||
"split.js": "^1.5.9",
|
||||
"sortablejs": "^1.8.0-rc1",
|
||||
"split.js": "^1.5.10",
|
||||
"ssdeep.js": "0.0.2",
|
||||
"ua-parser-js": "^0.7.19",
|
||||
"utf8": "^3.0.0",
|
||||
"vkbeautify": "^0.99.3",
|
||||
"xmldom": "^0.1.27",
|
||||
"xpath": "0.0.27",
|
||||
"xregexp": "^4.2.0",
|
||||
"xregexp": "^4.2.4",
|
||||
"zlibjs": "^0.3.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "grunt dev",
|
||||
"build": "grunt prod",
|
||||
"test": "grunt test",
|
||||
"testui": "grunt testui",
|
||||
"docs": "grunt docs",
|
||||
"lint": "grunt lint",
|
||||
"newop": "node --experimental-modules src/core/config/scripts/newOperation.mjs"
|
||||
|
||||
@@ -157,9 +157,9 @@ class Chef {
|
||||
* @param {number} pos.end - The end offset.
|
||||
* @returns {Object}
|
||||
*/
|
||||
calculateHighlights(recipeConfig, direction, pos) {
|
||||
async calculateHighlights(recipeConfig, direction, pos) {
|
||||
const recipe = new Recipe(recipeConfig);
|
||||
const highlights = recipe.generateHighlightList();
|
||||
const highlights = await recipe.generateHighlightList();
|
||||
|
||||
if (!highlights) return false;
|
||||
|
||||
|
||||
@@ -157,8 +157,8 @@ async function getDishAs(data) {
|
||||
* @param {number} pos.start - The start offset.
|
||||
* @param {number} pos.end - The end offset.
|
||||
*/
|
||||
function calculateHighlights(recipeConfig, direction, pos) {
|
||||
pos = self.chef.calculateHighlights(recipeConfig, direction, pos);
|
||||
async function calculateHighlights(recipeConfig, direction, pos) {
|
||||
pos = await self.chef.calculateHighlights(recipeConfig, direction, pos);
|
||||
|
||||
self.postMessage({
|
||||
action: "highlightsCalculated",
|
||||
|
||||
@@ -168,7 +168,7 @@ class Dish {
|
||||
this.value = Array.prototype.slice.call(new Uint8Array(this.value));
|
||||
break;
|
||||
case Dish.BIG_NUMBER:
|
||||
this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : [];
|
||||
this.value = BigNumber.isBigNumber(this.value) ? Utils.strToByteArray(this.value.toFixed()) : [];
|
||||
break;
|
||||
case Dish.JSON:
|
||||
this.value = this.value ? Utils.strToByteArray(JSON.stringify(this.value, null, 4)) : [];
|
||||
@@ -265,7 +265,7 @@ class Dish {
|
||||
case Dish.ARRAY_BUFFER:
|
||||
return this.value instanceof ArrayBuffer;
|
||||
case Dish.BIG_NUMBER:
|
||||
return this.value instanceof BigNumber;
|
||||
return BigNumber.isBigNumber(this.value);
|
||||
case Dish.JSON:
|
||||
// All values can be serialised in some manner, so we return true in all cases
|
||||
return true;
|
||||
|
||||
@@ -23,6 +23,7 @@ class Ingredient {
|
||||
this._value = null;
|
||||
this.disabled = false;
|
||||
this.hint = "";
|
||||
this.rows = 0;
|
||||
this.toggleValues = [];
|
||||
this.target = null;
|
||||
this.defaultIndex = 0;
|
||||
@@ -45,6 +46,7 @@ class Ingredient {
|
||||
this.defaultValue = ingredientConfig.value;
|
||||
this.disabled = !!ingredientConfig.disabled;
|
||||
this.hint = ingredientConfig.hint || false;
|
||||
this.rows = ingredientConfig.rows || false;
|
||||
this.toggleValues = ingredientConfig.toggleValues;
|
||||
this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null;
|
||||
this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0;
|
||||
@@ -95,6 +97,7 @@ class Ingredient {
|
||||
case "binaryString":
|
||||
case "binaryShortString":
|
||||
case "editableOption":
|
||||
case "editableOptionShort":
|
||||
return Utils.parseEscapedChars(data);
|
||||
case "byteArray":
|
||||
if (typeof data == "string") {
|
||||
|
||||
@@ -23,6 +23,7 @@ class Operation {
|
||||
this._breakpoint = false;
|
||||
this._disabled = false;
|
||||
this._flowControl = false;
|
||||
this._manualBake = false;
|
||||
this._ingList = [];
|
||||
|
||||
// Public fields
|
||||
@@ -179,6 +180,7 @@ class Operation {
|
||||
|
||||
if (ing.toggleValues) conf.toggleValues = ing.toggleValues;
|
||||
if (ing.hint) conf.hint = ing.hint;
|
||||
if (ing.rows) conf.rows = ing.rows;
|
||||
if (ing.disabled) conf.disabled = ing.disabled;
|
||||
if (ing.target) conf.target = ing.target;
|
||||
if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex;
|
||||
@@ -281,6 +283,7 @@ class Operation {
|
||||
return this._flowControl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation is a flowcontrol op.
|
||||
*
|
||||
@@ -290,6 +293,26 @@ class Operation {
|
||||
this._flowControl = !!value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation should not trigger AutoBake.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get manualBake() {
|
||||
return this._manualBake;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation should trigger AutoBake.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set manualBake(value) {
|
||||
this._manualBake = !!value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Operation;
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OpModules from "./config/modules/OpModules";
|
||||
import OperationConfig from "./config/OperationConfig.json";
|
||||
import OperationError from "./errors/OperationError";
|
||||
import Operation from "./Operation";
|
||||
import DishError from "./errors/DishError";
|
||||
import log from "loglevel";
|
||||
|
||||
// Cache container for modules
|
||||
let modules = null;
|
||||
|
||||
/**
|
||||
* The Recipe controls a list of Operations and the Dish they operate on.
|
||||
*/
|
||||
@@ -36,16 +39,43 @@ class Recipe {
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
_parseConfig(recipeConfig) {
|
||||
for (let c = 0; c < recipeConfig.length; c++) {
|
||||
const operationName = recipeConfig[c].op;
|
||||
const opConf = OperationConfig[operationName];
|
||||
const opObj = OpModules[opConf.module][operationName];
|
||||
const operation = new opObj();
|
||||
operation.ingValues = recipeConfig[c].args;
|
||||
operation.breakpoint = recipeConfig[c].breakpoint;
|
||||
operation.disabled = recipeConfig[c].disabled;
|
||||
this.addOperation(operation);
|
||||
recipeConfig.forEach(c => {
|
||||
this.opList.push({
|
||||
name: c.op,
|
||||
module: OperationConfig[c.op].module,
|
||||
ingValues: c.args,
|
||||
breakpoint: c.breakpoint,
|
||||
disabled: c.disabled,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populate elements of opList with operation instances.
|
||||
* Dynamic import here removes top-level cyclic dependency issue.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
async _hydrateOpList() {
|
||||
if (!modules) {
|
||||
// Using Webpack Magic Comments to force the dynamic import to be included in the main chunk
|
||||
// https://webpack.js.org/api/module-methods/
|
||||
modules = await import(/* webpackMode: "eager" */ "./config/modules/OpModules");
|
||||
modules = modules.default;
|
||||
}
|
||||
|
||||
this.opList = this.opList.map(o => {
|
||||
if (o instanceof Operation) {
|
||||
return o;
|
||||
} else {
|
||||
const op = new modules[o.module][o.name]();
|
||||
op.ingValues = o.ingValues;
|
||||
op.breakpoint = o.breakpoint;
|
||||
op.disabled = o.disabled;
|
||||
return op;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +85,10 @@ class Recipe {
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
get config() {
|
||||
return this.opList.map(op => op.config);
|
||||
return this.opList.map(op => ({
|
||||
op: op.name,
|
||||
args: op.ingValues,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +108,19 @@ class Recipe {
|
||||
* @param {Operation[]} operations
|
||||
*/
|
||||
addOperations(operations) {
|
||||
this.opList = this.opList.concat(operations);
|
||||
operations.forEach(o => {
|
||||
if (o instanceof Operation) {
|
||||
this.opList.push(o);
|
||||
} else {
|
||||
this.opList.push({
|
||||
name: o.name,
|
||||
module: o.module,
|
||||
ingValues: o.args,
|
||||
breakpoint: o.breakpoint,
|
||||
disabled: o.disabled,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +153,7 @@ class Recipe {
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if there is an Flow Control Operation in this Recipe.
|
||||
* Returns true if there is a Flow Control Operation in this Recipe.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
@@ -137,6 +182,8 @@ class Recipe {
|
||||
|
||||
if (startFrom === 0) this.lastRunOp = null;
|
||||
|
||||
await this._hydrateOpList();
|
||||
|
||||
log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
|
||||
|
||||
for (let i = startFrom; i < this.opList.length; i++) {
|
||||
@@ -254,7 +301,8 @@ class Recipe {
|
||||
* @returns {function} highlights[].b
|
||||
* @returns {Object[]} highlights[].args
|
||||
*/
|
||||
generateHighlightList() {
|
||||
async generateHighlightList() {
|
||||
await this._hydrateOpList();
|
||||
const highlights = [];
|
||||
|
||||
for (let i = 0; i < this.opList.length; i++) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import utf8 from "utf8";
|
||||
import {fromBase64} from "./lib/Base64";
|
||||
import {fromBase64, toBase64} from "./lib/Base64";
|
||||
import {fromHex} from "./lib/Hex";
|
||||
import {fromDecimal} from "./lib/Decimal";
|
||||
import {fromBinary} from "./lib/Binary";
|
||||
@@ -817,12 +817,24 @@ class Utils {
|
||||
return html;
|
||||
};
|
||||
|
||||
const formatContent = function (buff, type) {
|
||||
if (type.startsWith("image")) {
|
||||
let dataURI = "data:";
|
||||
dataURI += type + ";";
|
||||
dataURI += "base64," + toBase64(buff);
|
||||
return "<img style='max-width: 100%;' src='" + dataURI + "'>";
|
||||
} else {
|
||||
return `<pre>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</pre>`;
|
||||
}
|
||||
};
|
||||
|
||||
const formatFile = async function(file, i) {
|
||||
const buff = await Utils.readFile(file);
|
||||
const blob = new Blob(
|
||||
[buff],
|
||||
{type: "octet/stream"}
|
||||
{type: file.type || "octet/stream"}
|
||||
);
|
||||
const blobURL = URL.createObjectURL(blob);
|
||||
|
||||
const html = `<div class='card' style='white-space: normal;'>
|
||||
<div class='card-header' id='heading${i}'>
|
||||
@@ -837,16 +849,25 @@ class Utils {
|
||||
<span class='float-right' style="margin-top: -3px">
|
||||
${file.size.toLocaleString()} bytes
|
||||
<a title="Download ${Utils.escapeHtml(file.name)}"
|
||||
href='${URL.createObjectURL(blob)}'
|
||||
download='${Utils.escapeHtml(file.name)}'>
|
||||
href="${blobURL}"
|
||||
download="${Utils.escapeHtml(file.name)}"
|
||||
data-toggle="tooltip">
|
||||
<i class="material-icons" style="vertical-align: bottom">save</i>
|
||||
</a>
|
||||
<a title="Move to input"
|
||||
href="#"
|
||||
blob-url="${blobURL}"
|
||||
file-name="${Utils.escapeHtml(file.name)}"
|
||||
class="extract-file"
|
||||
data-toggle="tooltip">
|
||||
<i class="material-icons" style="vertical-align: bottom">open_in_browser</i>
|
||||
</a>
|
||||
</span>
|
||||
</h6>
|
||||
</div>
|
||||
<div id='collapse${i}' class='collapse' aria-labelledby='heading${i}' data-parent="#files">
|
||||
<div class='card-body'>
|
||||
<pre>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</pre>
|
||||
${formatContent(buff, file.type)}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -1152,6 +1173,21 @@ String.prototype.count = function(chr) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for self.sendStatusMessage to handle different environments.
|
||||
*
|
||||
* @param {string} msg
|
||||
*/
|
||||
export function sendStatusMessage(msg) {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage(msg);
|
||||
else if (ENVIRONMENT_IS_WEB())
|
||||
app.alert(msg, 10000);
|
||||
else if (ENVIRONMENT_IS_NODE())
|
||||
log.debug(msg);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Polyfills
|
||||
*/
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
"name": "Networking",
|
||||
"ops": [
|
||||
"HTTP request",
|
||||
"DNS over HTTPS",
|
||||
"Strip HTTP headers",
|
||||
"Dechunk HTTP response",
|
||||
"Parse User Agent",
|
||||
@@ -189,6 +190,8 @@
|
||||
"Remove null bytes",
|
||||
"To Upper case",
|
||||
"To Lower case",
|
||||
"To Case Insensitive Regex",
|
||||
"From Case Insensitive Regex",
|
||||
"Add line numbers",
|
||||
"Remove line numbers",
|
||||
"To Table",
|
||||
@@ -213,6 +216,7 @@
|
||||
"Convert mass",
|
||||
"Convert speed",
|
||||
"Convert data units",
|
||||
"Convert co-ordinate format",
|
||||
"Parse UNIX file permissions",
|
||||
"Swap endianness",
|
||||
"Parse colour code",
|
||||
@@ -250,7 +254,8 @@
|
||||
"XPath expression",
|
||||
"JPath expression",
|
||||
"CSS selector",
|
||||
"Extract EXIF"
|
||||
"Extract EXIF",
|
||||
"Extract Files"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -304,9 +309,7 @@
|
||||
"Adler-32 Checksum",
|
||||
"CRC-16 Checksum",
|
||||
"CRC-32 Checksum",
|
||||
"TCP/IP Checksum",
|
||||
"To Geohash",
|
||||
"From Geohash"
|
||||
"TCP/IP Checksum"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -346,10 +349,19 @@
|
||||
"ops": [
|
||||
"Detect File Type",
|
||||
"Scan for Embedded Files",
|
||||
"Extract Files",
|
||||
"Remove EXIF",
|
||||
"Extract EXIF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Multimedia",
|
||||
"ops": [
|
||||
"Render Image",
|
||||
"Play Media",
|
||||
"Remove EXIF",
|
||||
"Extract EXIF",
|
||||
"Render Image",
|
||||
"Play Media"
|
||||
"Split Colour Channels"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -366,6 +378,7 @@
|
||||
"Generate QR Code",
|
||||
"Parse QR Code",
|
||||
"Haversine distance",
|
||||
"Generate Lorem Ipsum",
|
||||
"Numberwang",
|
||||
"XKCD Random Number"
|
||||
]
|
||||
@@ -375,6 +388,7 @@
|
||||
"ops": [
|
||||
"Magic",
|
||||
"Fork",
|
||||
"Subsection",
|
||||
"Merge",
|
||||
"Register",
|
||||
"Label",
|
||||
|
||||
@@ -41,6 +41,7 @@ for (const opObj in Ops) {
|
||||
inputType: op.inputType,
|
||||
outputType: op.presentType,
|
||||
flowControl: op.flowControl,
|
||||
manualBake: op.manualBake,
|
||||
args: op.args
|
||||
};
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ export default ${moduleName};
|
||||
console.log(`\nNext steps:
|
||||
1. Add your operation to ${colors.green("src/core/config/Categories.json")}
|
||||
2. Write your operation code.
|
||||
3. Write tests in ${colors.green("test/tests/operations/")}
|
||||
3. Write tests in ${colors.green("tests/operations/tests/")}
|
||||
4. Run ${colors.cyan("npm run lint")} and ${colors.cyan("npm run test")}
|
||||
5. Submit a Pull Request to get your operation added to the official CyberChef repository.`);
|
||||
|
||||
|
||||
0
src/core/lib/BCD.mjs
Executable file → Normal file
0
src/core/lib/BCD.mjs
Executable file → Normal file
0
src/core/lib/Base58.mjs
Executable file → Normal file
0
src/core/lib/Base58.mjs
Executable file → Normal file
0
src/core/lib/Base64.mjs
Executable file → Normal file
0
src/core/lib/Base64.mjs
Executable file → Normal file
0
src/core/lib/CanvasComponents.mjs
Executable file → Normal file
0
src/core/lib/CanvasComponents.mjs
Executable file → Normal file
655
src/core/lib/ConvertCoordinates.mjs
Normal file
655
src/core/lib/ConvertCoordinates.mjs
Normal file
@@ -0,0 +1,655 @@
|
||||
/**
|
||||
* Co-ordinate conversion resources.
|
||||
*
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import geohash from "ngeohash";
|
||||
import geodesy from "geodesy";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* Co-ordinate formats
|
||||
*/
|
||||
export const FORMATS = [
|
||||
"Degrees Minutes Seconds",
|
||||
"Degrees Decimal Minutes",
|
||||
"Decimal Degrees",
|
||||
"Geohash",
|
||||
"Military Grid Reference System",
|
||||
"Ordnance Survey National Grid",
|
||||
"Universal Transverse Mercator"
|
||||
];
|
||||
|
||||
/**
|
||||
* Formats that should be passed to the conversion module as-is
|
||||
*/
|
||||
const NO_CHANGE = [
|
||||
"Geohash",
|
||||
"Military Grid Reference System",
|
||||
"Ordnance Survey National Grid",
|
||||
"Universal Transverse Mercator",
|
||||
];
|
||||
|
||||
/**
|
||||
* Convert a given latitude and longitude into a different format.
|
||||
*
|
||||
* @param {string} input - Input string to be converted
|
||||
* @param {string} inFormat - Format of the input coordinates
|
||||
* @param {string} inDelim - The delimiter splitting the lat/long of the input
|
||||
* @param {string} outFormat - Format to convert to
|
||||
* @param {string} outDelim - The delimiter to separate the output with
|
||||
* @param {string} includeDir - Whether or not to include the compass direction in the output
|
||||
* @param {number} precision - Precision of the result
|
||||
* @returns {string} A formatted string of the converted co-ordinates
|
||||
*/
|
||||
export function convertCoordinates (input, inFormat, inDelim, outFormat, outDelim, includeDir, precision) {
|
||||
let isPair = false,
|
||||
split,
|
||||
latlon,
|
||||
convLat,
|
||||
convLon,
|
||||
conv,
|
||||
hash,
|
||||
utm,
|
||||
mgrs,
|
||||
osng,
|
||||
splitLat,
|
||||
splitLong,
|
||||
lat,
|
||||
lon;
|
||||
|
||||
// Can't have a precision less than 0!
|
||||
if (precision < 0) {
|
||||
precision = 0;
|
||||
}
|
||||
|
||||
if (inDelim === "Auto") {
|
||||
// Try to detect a delimiter in the input.
|
||||
inDelim = findDelim(input);
|
||||
if (inDelim === null) {
|
||||
throw new OperationError("Unable to detect the input delimiter automatically.");
|
||||
}
|
||||
} else if (!inDelim.includes("Direction")) {
|
||||
// Convert the delimiter argument value to the actual character
|
||||
inDelim = realDelim(inDelim);
|
||||
}
|
||||
|
||||
if (inFormat === "Auto") {
|
||||
// Try to detect the format of the input data
|
||||
inFormat = findFormat(input, inDelim);
|
||||
if (inFormat === null) {
|
||||
throw new OperationError("Unable to detect the input format automatically.");
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the output delimiter argument to the real character
|
||||
outDelim = realDelim(outDelim);
|
||||
|
||||
if (!NO_CHANGE.includes(inFormat)) {
|
||||
if (inDelim.includes("Direction")) {
|
||||
// Split on directions
|
||||
split = input.split(/[NnEeSsWw]/g);
|
||||
if (split[0] === "") {
|
||||
// Remove first element if direction preceding
|
||||
split = split.slice(1);
|
||||
}
|
||||
} else {
|
||||
split = input.split(inDelim);
|
||||
}
|
||||
// Replace any co-ordinate symbols with spaces so we can split on them later
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
split[i] = split[i].replace(/[°˝´'"]/g, " ");
|
||||
}
|
||||
if (split.length > 1) {
|
||||
isPair = true;
|
||||
}
|
||||
} else {
|
||||
// Remove any delimiters from the input
|
||||
input = input.replace(inDelim, "");
|
||||
isPair = true;
|
||||
}
|
||||
|
||||
// Conversions from the input format into a geodesy latlon object
|
||||
switch (inFormat) {
|
||||
case "Geohash":
|
||||
hash = geohash.decode(input.replace(/[^A-Za-z0-9]/g, ""));
|
||||
latlon = new geodesy.LatLonEllipsoidal(hash.latitude, hash.longitude);
|
||||
break;
|
||||
case "Military Grid Reference System":
|
||||
utm = geodesy.Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, "")).toUtm();
|
||||
latlon = utm.toLatLonE();
|
||||
break;
|
||||
case "Ordnance Survey National Grid":
|
||||
osng = geodesy.OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, ""));
|
||||
latlon = geodesy.OsGridRef.osGridToLatLon(osng);
|
||||
break;
|
||||
case "Universal Transverse Mercator":
|
||||
// Geodesy needs a space between the first 2 digits and the next letter
|
||||
if (/^[\d]{2}[A-Za-z]/.test(input)) {
|
||||
input = input.slice(0, 2) + " " + input.slice(2);
|
||||
}
|
||||
utm = geodesy.Utm.parse(input);
|
||||
latlon = utm.toLatLonE();
|
||||
break;
|
||||
case "Degrees Minutes Seconds":
|
||||
if (isPair) {
|
||||
// Split up the lat/long into degrees / minutes / seconds values
|
||||
splitLat = splitInput(split[0]);
|
||||
splitLong = splitInput(split[1]);
|
||||
|
||||
if (splitLat.length >= 3 && splitLong.length >= 3) {
|
||||
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2], 10);
|
||||
lon = convDMSToDD(splitLong[0], splitLong[1], splitLong[2], 10);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees);
|
||||
} else {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
|
||||
}
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(split[0]);
|
||||
if (splitLat.length >= 3) {
|
||||
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2]);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
|
||||
} else {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "Degrees Decimal Minutes":
|
||||
if (isPair) {
|
||||
splitLat = splitInput(split[0]);
|
||||
splitLong = splitInput(split[1]);
|
||||
if (splitLat.length !== 2 || splitLong.length !== 2) {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
|
||||
}
|
||||
// Convert to decimal degrees, and then convert to a geodesy object
|
||||
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
|
||||
lon = convDDMToDD(splitLong[0], splitLong[1], 10);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees);
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(input);
|
||||
if (splitLat.length !== 2) {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
|
||||
}
|
||||
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
|
||||
}
|
||||
break;
|
||||
case "Decimal Degrees":
|
||||
if (isPair) {
|
||||
splitLat = splitInput(split[0]);
|
||||
splitLong = splitInput(split[1]);
|
||||
if (splitLat.length !== 1 || splitLong.length !== 1) {
|
||||
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
|
||||
}
|
||||
latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLong[0]);
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(split[0]);
|
||||
if (splitLat.length !== 1) {
|
||||
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
|
||||
}
|
||||
latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLat[0]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown input format '${inFormat}'`);
|
||||
}
|
||||
|
||||
// Everything is now a geodesy latlon object
|
||||
// These store the latitude and longitude as decimal
|
||||
if (inFormat.includes("Degrees")) {
|
||||
// If the input string contains directions, we need to check if they're S or W.
|
||||
// If either of the directions are, we should make the decimal value negative
|
||||
const dirs = input.toUpperCase().match(/[NESW]/g);
|
||||
if (dirs && dirs.length >= 1) {
|
||||
// Make positive lat/lon values with S/W directions into negative values
|
||||
if (dirs[0] === "S" || dirs[0] === "W" && latlon.lat > 0) {
|
||||
latlon.lat = -latlon.lat;
|
||||
}
|
||||
if (dirs.length >= 2) {
|
||||
if (dirs[1] === "S" || dirs[1] === "W" && latlon.lon > 0) {
|
||||
latlon.lon = -latlon.lon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find the compass directions of the lat and long
|
||||
const [latDir, longDir] = findDirs(latlon.lat + "," + latlon.lon, ",");
|
||||
|
||||
// Output conversions for each output format
|
||||
switch (outFormat) {
|
||||
case "Decimal Degrees":
|
||||
// We could use the built in latlon.toString(),
|
||||
// but this makes adjusting the output harder
|
||||
lat = convDDToDD(latlon.lat, precision);
|
||||
lon = convDDToDD(latlon.lon, precision);
|
||||
convLat = lat.string;
|
||||
convLon = lon.string;
|
||||
break;
|
||||
case "Degrees Decimal Minutes":
|
||||
lat = convDDToDDM(latlon.lat, precision);
|
||||
lon = convDDToDDM(latlon.lon, precision);
|
||||
convLat = lat.string;
|
||||
convLon = lon.string;
|
||||
break;
|
||||
case "Degrees Minutes Seconds":
|
||||
lat = convDDToDMS(latlon.lat, precision);
|
||||
lon = convDDToDMS(latlon.lon, precision);
|
||||
convLat = lat.string;
|
||||
convLon = lon.string;
|
||||
break;
|
||||
case "Geohash":
|
||||
convLat = geohash.encode(latlon.lat, latlon.lon, precision);
|
||||
break;
|
||||
case "Military Grid Reference System":
|
||||
utm = latlon.toUtm();
|
||||
mgrs = utm.toMgrs();
|
||||
// MGRS wants a precision that's an even number between 2 and 10
|
||||
if (precision % 2 !== 0) {
|
||||
precision = precision + 1;
|
||||
}
|
||||
if (precision > 10) {
|
||||
precision = 10;
|
||||
}
|
||||
convLat = mgrs.toString(precision);
|
||||
break;
|
||||
case "Ordnance Survey National Grid":
|
||||
osng = geodesy.OsGridRef.latLonToOsGrid(latlon);
|
||||
if (osng.toString() === "") {
|
||||
throw new OperationError("Could not convert co-ordinates to OS National Grid. Are the co-ordinates in range?");
|
||||
}
|
||||
// OSNG wants a precision that's an even number between 2 and 10
|
||||
if (precision % 2 !== 0) {
|
||||
precision = precision + 1;
|
||||
}
|
||||
if (precision > 10) {
|
||||
precision = 10;
|
||||
}
|
||||
convLat = osng.toString(precision);
|
||||
break;
|
||||
case "Universal Transverse Mercator":
|
||||
utm = latlon.toUtm();
|
||||
convLat = utm.toString(precision);
|
||||
break;
|
||||
}
|
||||
|
||||
if (convLat === undefined) {
|
||||
throw new OperationError("Error converting co-ordinates.");
|
||||
}
|
||||
|
||||
if (outFormat.includes("Degrees")) {
|
||||
// Format DD/DDM/DMS for output
|
||||
// If we're outputting a compass direction, remove the negative sign
|
||||
if (latDir === "S" && includeDir !== "None") {
|
||||
convLat = convLat.replace("-", "");
|
||||
}
|
||||
if (longDir === "W" && includeDir !== "None") {
|
||||
convLon = convLon.replace("-", "");
|
||||
}
|
||||
|
||||
let outConv = "";
|
||||
if (includeDir === "Before") {
|
||||
outConv += latDir + " ";
|
||||
}
|
||||
|
||||
outConv += convLat;
|
||||
if (includeDir === "After") {
|
||||
outConv += " " + latDir;
|
||||
}
|
||||
outConv += outDelim;
|
||||
if (isPair) {
|
||||
if (includeDir === "Before") {
|
||||
outConv += longDir + " ";
|
||||
}
|
||||
outConv += convLon;
|
||||
if (includeDir === "After") {
|
||||
outConv += " " + longDir;
|
||||
}
|
||||
outConv += outDelim;
|
||||
}
|
||||
conv = outConv;
|
||||
} else {
|
||||
conv = convLat + outDelim;
|
||||
}
|
||||
|
||||
return conv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split up the input using a space or degrees signs, and sanitise the result
|
||||
*
|
||||
* @param {string} input - The input data to be split
|
||||
* @returns {number[]} An array of the different items in the string, stored as floats
|
||||
*/
|
||||
function splitInput (input){
|
||||
const split = [];
|
||||
|
||||
input.split(/\s+/).forEach(item => {
|
||||
// Remove any character that isn't a digit, decimal point or negative sign
|
||||
item = item.replace(/[^0-9.-]/g, "");
|
||||
if (item.length > 0){
|
||||
// Turn the item into a float
|
||||
split.push(parseFloat(item));
|
||||
}
|
||||
});
|
||||
return split;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Degrees Minutes Seconds to Decimal Degrees
|
||||
*
|
||||
* @param {number} degrees - The degrees of the input co-ordinates
|
||||
* @param {number} minutes - The minutes of the input co-ordinates
|
||||
* @param {number} seconds - The seconds of the input co-ordinates
|
||||
* @param {number} precision - The precision the result should be rounded to
|
||||
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDMSToDD (degrees, minutes, seconds, precision){
|
||||
const absDegrees = Math.abs(degrees);
|
||||
let conv = absDegrees + (minutes / 60) + (seconds / 3600);
|
||||
let outString = round(conv, precision) + "°";
|
||||
if (isNegativeZero(degrees) || degrees < 0) {
|
||||
conv = -conv;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
return {
|
||||
"degrees": conv,
|
||||
"string": outString
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees Minutes to Decimal Degrees
|
||||
*
|
||||
* @param {number} degrees - The input degrees to be converted
|
||||
* @param {number} minutes - The input minutes to be converted
|
||||
* @param {number} precision - The precision which the result should be rounded to
|
||||
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDMToDD (degrees, minutes, precision) {
|
||||
const absDegrees = Math.abs(degrees);
|
||||
let conv = absDegrees + minutes / 60;
|
||||
let outString = round(conv, precision) + "°";
|
||||
if (isNegativeZero(degrees) || degrees < 0) {
|
||||
conv = -conv;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
return {
|
||||
"degrees": conv,
|
||||
"string": outString
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees to Decimal Degrees
|
||||
*
|
||||
* Doesn't affect the input, just puts it into an object
|
||||
* @param {number} degrees - The input degrees to be converted
|
||||
* @param {number} precision - The precision which the result should be rounded to
|
||||
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDToDD (degrees, precision) {
|
||||
return {
|
||||
"degrees": degrees,
|
||||
"string": round(degrees, precision) + "°"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees to Degrees Minutes Seconds
|
||||
*
|
||||
* @param {number} decDegrees - The input data to be converted
|
||||
* @param {number} precision - The precision which the result should be rounded to
|
||||
* @returns {{string: string, degrees: number, minutes: number, seconds: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes, .seconds), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDToDMS (decDegrees, precision) {
|
||||
const absDegrees = Math.abs(decDegrees);
|
||||
let degrees = Math.floor(absDegrees);
|
||||
const minutes = Math.floor(60 * (absDegrees - degrees)),
|
||||
seconds = round(3600 * (absDegrees - degrees) - 60 * minutes, precision);
|
||||
let outString = degrees + "° " + minutes + "' " + seconds + "\"";
|
||||
if (isNegativeZero(decDegrees) || decDegrees < 0) {
|
||||
degrees = -degrees;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
return {
|
||||
"degrees": degrees,
|
||||
"minutes": minutes,
|
||||
"seconds": seconds,
|
||||
"string": outString
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees to Degrees Decimal Minutes
|
||||
*
|
||||
* @param {number} decDegrees - The input degrees to be converted
|
||||
* @param {number} precision - The precision the input data should be rounded to
|
||||
* @returns {{string: string, degrees: number, minutes: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDToDDM (decDegrees, precision) {
|
||||
const absDegrees = Math.abs(decDegrees);
|
||||
let degrees = Math.floor(absDegrees);
|
||||
const minutes = absDegrees - degrees,
|
||||
decMinutes = round(minutes * 60, precision);
|
||||
let outString = degrees + "° " + decMinutes + "'";
|
||||
if (decDegrees < 0 || isNegativeZero(decDegrees)) {
|
||||
degrees = -degrees;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
|
||||
return {
|
||||
"degrees": degrees,
|
||||
"minutes": decMinutes,
|
||||
"string": outString,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the compass directions in an input string
|
||||
*
|
||||
* @param {string} input - The input co-ordinates containing the direction
|
||||
* @param {string} delim - The delimiter separating latitide and longitude
|
||||
* @returns {string[]} String array containing the latitude and longitude directions
|
||||
*/
|
||||
export function findDirs(input, delim) {
|
||||
const upperInput = input.toUpperCase();
|
||||
const dirExp = new RegExp(/[NESW]/g);
|
||||
|
||||
const dirs = upperInput.match(dirExp);
|
||||
|
||||
if (dirs) {
|
||||
// If there's actually compass directions
|
||||
// in the input, use these to work out the direction
|
||||
if (dirs.length <= 2 && dirs.length >= 1) {
|
||||
return dirs.length === 2 ? [dirs[0], dirs[1]] : [dirs[0], ""];
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing was returned, so guess the directions
|
||||
let lat = upperInput,
|
||||
long,
|
||||
latDir = "",
|
||||
longDir = "";
|
||||
if (!delim.includes("Direction")) {
|
||||
if (upperInput.includes(delim)) {
|
||||
const split = upperInput.split(delim);
|
||||
if (split.length >= 1) {
|
||||
if (split[0] !== "") {
|
||||
lat = split[0];
|
||||
}
|
||||
if (split.length >= 2 && split[1] !== "") {
|
||||
long = split[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const split = upperInput.split(dirExp);
|
||||
if (split.length > 1) {
|
||||
lat = split[0] === "" ? split[1] : split[0];
|
||||
if (split.length > 2 && split[2] !== "") {
|
||||
long = split[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lat) {
|
||||
lat = parseFloat(lat);
|
||||
latDir = lat < 0 ? "S" : "N";
|
||||
}
|
||||
|
||||
if (long) {
|
||||
long = parseFloat(long);
|
||||
longDir = long < 0 ? "W" : "E";
|
||||
}
|
||||
|
||||
return [latDir, longDir];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the co-ordinate format of the input data
|
||||
*
|
||||
* @param {string} input - The input data whose format we need to detect
|
||||
* @param {string} delim - The delimiter separating the data in input
|
||||
* @returns {string} The input format
|
||||
*/
|
||||
export function findFormat (input, delim) {
|
||||
let testData;
|
||||
const mgrsPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]{1}\s?[A-HJ-NP-Z][A-HJ-NP-V]\s?[0-9\s]+/),
|
||||
osngPattern = new RegExp(/^[A-HJ-Z]{2}\s+[0-9\s]+$/),
|
||||
geohashPattern = new RegExp(/^[0123456789BCDEFGHJKMNPQRSTUVWXYZ]+$/),
|
||||
utmPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]\s[0-9.]+\s?[0-9.]+$/),
|
||||
degPattern = new RegExp(/[°'"]/g);
|
||||
|
||||
input = input.trim();
|
||||
|
||||
if (delim !== null && delim.includes("Direction")) {
|
||||
const split = input.split(/[NnEeSsWw]/);
|
||||
if (split.length > 1) {
|
||||
testData = split[0] === "" ? split[1] : split[0];
|
||||
}
|
||||
} else if (delim !== null && delim !== "") {
|
||||
if (input.includes(delim)) {
|
||||
const split = input.split(delim);
|
||||
if (split.length > 1) {
|
||||
testData = split[0] === "" ? split[1] : split[0];
|
||||
}
|
||||
} else {
|
||||
testData = input;
|
||||
}
|
||||
}
|
||||
|
||||
// Test non-degrees formats
|
||||
if (!degPattern.test(input)) {
|
||||
const filteredInput = input.toUpperCase().replace(delim, "");
|
||||
|
||||
if (utmPattern.test(filteredInput)) {
|
||||
return "Universal Transverse Mercator";
|
||||
}
|
||||
if (mgrsPattern.test(filteredInput)) {
|
||||
return "Military Grid Reference System";
|
||||
}
|
||||
if (osngPattern.test(filteredInput)) {
|
||||
return "Ordnance Survey National Grid";
|
||||
}
|
||||
if (geohashPattern.test(filteredInput)) {
|
||||
return "Geohash";
|
||||
}
|
||||
}
|
||||
|
||||
// Test DMS/DDM/DD formats
|
||||
if (testData !== undefined) {
|
||||
const split = splitInput(testData);
|
||||
switch (split.length){
|
||||
case 3:
|
||||
return "Degrees Minutes Seconds";
|
||||
case 2:
|
||||
return "Degrees Decimal Minutes";
|
||||
case 1:
|
||||
return "Decimal Degrees";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically find the delimeter type from the given input
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string} Delimiter type
|
||||
*/
|
||||
export function findDelim (input) {
|
||||
input = input.trim();
|
||||
const delims = [",", ";", ":"];
|
||||
const testDir = input.match(/[NnEeSsWw]/g);
|
||||
if (testDir !== null && testDir.length > 0 && testDir.length < 3) {
|
||||
// Possibly contains a direction
|
||||
const splitInput = input.split(/[NnEeSsWw]/);
|
||||
if (splitInput.length <= 3 && splitInput.length > 0) {
|
||||
// If there's 3 splits (one should be empty), then assume we have directions
|
||||
if (splitInput[0] === "") {
|
||||
return "Direction Preceding";
|
||||
} else if (splitInput[splitInput.length - 1] === "") {
|
||||
return "Direction Following";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through the standard delimiters, and try to find them in the input
|
||||
for (let i = 0; i < delims.length; i++) {
|
||||
const delim = delims[i];
|
||||
if (input.includes(delim)) {
|
||||
const splitInput = input.split(delim);
|
||||
if (splitInput.length <= 3 && splitInput.length > 0) {
|
||||
// Don't want to try and convert more than 2 co-ordinates
|
||||
return delim;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the real string for a delimiter name.
|
||||
*
|
||||
* @param {string} delim The delimiter to be matched
|
||||
* @returns {string}
|
||||
*/
|
||||
export function realDelim (delim) {
|
||||
return {
|
||||
"Auto": "Auto",
|
||||
"Space": " ",
|
||||
"\\n": "\n",
|
||||
"Comma": ",",
|
||||
"Semi-colon": ";",
|
||||
"Colon": ":"
|
||||
}[delim];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a zero is negative
|
||||
*
|
||||
* @param {number} zero
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isNegativeZero(zero) {
|
||||
return zero === 0 && (1/zero < 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds a number to a specified number of decimal places
|
||||
*
|
||||
* @param {number} input - The number to be rounded
|
||||
* @param {precision} precision - The number of decimal places the number should be rounded to
|
||||
* @returns {number}
|
||||
*/
|
||||
function round(input, precision) {
|
||||
precision = Math.pow(10, precision);
|
||||
return Math.round(input * precision) / precision;
|
||||
}
|
||||
1727
src/core/lib/FileSignatures.mjs
Normal file
1727
src/core/lib/FileSignatures.mjs
Normal file
File diff suppressed because it is too large
Load Diff
263
src/core/lib/FileType.mjs
Normal file
263
src/core/lib/FileType.mjs
Normal file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* File type functions
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*
|
||||
*/
|
||||
import {FILE_SIGNATURES} from "./FileSignatures";
|
||||
import {sendStatusMessage} from "../Utils";
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether a signature matches a buffer.
|
||||
*
|
||||
* @param {Object|Object[]} sig - A dictionary of offsets with values assigned to them.
|
||||
* These values can be numbers for static checks, arrays of potential valid matches,
|
||||
* or bespoke functions to check the validity of the buffer value at that offset.
|
||||
* @param {Uint8Array} buf
|
||||
* @param {number} [offset=0] Where in the buffer to start searching from
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function signatureMatches(sig, buf, offset=0) {
|
||||
// Using a length check seems to be more performant than `sig instanceof Array`
|
||||
if (sig.length) {
|
||||
// sig is an Array - return true if any of them match
|
||||
// The following `reduce` method is nice, but performance matters here, so we
|
||||
// opt for a faster, if less elegant, for loop.
|
||||
// return sig.reduce((acc, s) => acc || bytesMatch(s, buf, offset), false);
|
||||
for (let i = 0; i < sig.length; i++) {
|
||||
if (bytesMatch(sig[i], buf, offset)) return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return bytesMatch(sig, buf, offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether a set of bytes match the given buffer.
|
||||
*
|
||||
* @param {Object} sig - A dictionary of offsets with values assigned to them.
|
||||
* These values can be numbers for static checks, arrays of potential valid matches,
|
||||
* or bespoke functions to check the validity of the buffer value at that offset.
|
||||
* @param {Uint8Array} buf
|
||||
* @param {number} [offset=0] Where in the buffer to start searching from
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function bytesMatch(sig, buf, offset=0) {
|
||||
for (const sigoffset in sig) {
|
||||
const pos = parseInt(sigoffset, 10) + offset;
|
||||
switch (typeof sig[sigoffset]) {
|
||||
case "number": // Static check
|
||||
if (buf[pos] !== sig[sigoffset])
|
||||
return false;
|
||||
break;
|
||||
case "object": // Array of options
|
||||
if (sig[sigoffset].indexOf(buf[pos]) < 0)
|
||||
return false;
|
||||
break;
|
||||
case "function": // More complex calculation
|
||||
if (!sig[sigoffset](buf[pos]))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unrecognised signature type at offset ${sigoffset}`);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a buffer, detects magic byte sequences at specific positions and returns the
|
||||
* extension and mime type.
|
||||
*
|
||||
* @param {Uint8Array} buf
|
||||
* @param {string[]} [categories=All] - Which categories of file to look for
|
||||
* @returns {Object[]} types
|
||||
* @returns {string} type.name - Name of file type
|
||||
* @returns {string} type.ext - File extension
|
||||
* @returns {string} type.mime - Mime type
|
||||
* @returns {string} [type.desc] - Description
|
||||
*/
|
||||
export function detectFileType(buf, categories=Object.keys(FILE_SIGNATURES)) {
|
||||
if (!(buf && buf.length > 1)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const matchingFiles = [];
|
||||
const signatures = {};
|
||||
|
||||
for (const cat in FILE_SIGNATURES) {
|
||||
if (categories.includes(cat)) {
|
||||
signatures[cat] = FILE_SIGNATURES[cat];
|
||||
}
|
||||
}
|
||||
|
||||
for (const cat in signatures) {
|
||||
const category = signatures[cat];
|
||||
|
||||
category.forEach(filetype => {
|
||||
if (signatureMatches(filetype.signature, buf)) {
|
||||
matchingFiles.push(filetype);
|
||||
}
|
||||
});
|
||||
}
|
||||
return matchingFiles;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a buffer, searches for magic byte sequences at all possible positions and returns
|
||||
* the extensions and mime types.
|
||||
*
|
||||
* @param {Uint8Array} buf
|
||||
* @param {string[]} [categories=All] - Which categories of file to look for
|
||||
* @returns {Object[]} foundFiles
|
||||
* @returns {number} foundFiles.offset - The position in the buffer at which this file was found
|
||||
* @returns {Object} foundFiles.fileDetails
|
||||
* @returns {string} foundFiles.fileDetails.name - Name of file type
|
||||
* @returns {string} foundFiles.fileDetails.ext - File extension
|
||||
* @returns {string} foundFiles.fileDetails.mime - Mime type
|
||||
* @returns {string} [foundFiles.fileDetails.desc] - Description
|
||||
*/
|
||||
export function scanForFileTypes(buf, categories=Object.keys(FILE_SIGNATURES)) {
|
||||
if (!(buf && buf.length > 1)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const foundFiles = [];
|
||||
const signatures = {};
|
||||
|
||||
for (const cat in FILE_SIGNATURES) {
|
||||
if (categories.includes(cat)) {
|
||||
signatures[cat] = FILE_SIGNATURES[cat];
|
||||
}
|
||||
}
|
||||
|
||||
for (const cat in signatures) {
|
||||
const category = signatures[cat];
|
||||
|
||||
for (let i = 0; i < category.length; i++) {
|
||||
const filetype = category[i];
|
||||
const sigs = filetype.signature.length ? filetype.signature : [filetype.signature];
|
||||
|
||||
sigs.forEach(sig => {
|
||||
let pos = 0;
|
||||
while ((pos = locatePotentialSig(buf, sig, pos)) >= 0) {
|
||||
if (bytesMatch(sig, buf, pos)) {
|
||||
sendStatusMessage(`Found potential signature for ${filetype.name} at pos ${pos}`);
|
||||
foundFiles.push({
|
||||
offset: pos,
|
||||
fileDetails: filetype
|
||||
});
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Return found files in order of increasing offset
|
||||
return foundFiles.sort((a, b) => {
|
||||
return a.offset - b.offset;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fastcheck function to quickly scan the buffer for the first byte in a signature.
|
||||
*
|
||||
* @param {Uint8Array} buf - The buffer to search
|
||||
* @param {Object} sig - A single signature object (Not an array of signatures)
|
||||
* @param {number} offset - Where to start search from
|
||||
* @returs {number} The position of the match or -1 if one cannot be found.
|
||||
*/
|
||||
function locatePotentialSig(buf, sig, offset) {
|
||||
// Find values for first key and value in sig
|
||||
const k = parseInt(Object.keys(sig)[0], 10);
|
||||
const v = Object.values(sig)[0];
|
||||
switch (typeof v) {
|
||||
case "number":
|
||||
return buf.indexOf(v, offset + k) - k;
|
||||
case "object":
|
||||
for (let i = offset + k; i < buf.length; i++) {
|
||||
if (v.indexOf(buf[i]) >= 0) return i - k;
|
||||
}
|
||||
return -1;
|
||||
case "function":
|
||||
for (let i = offset + k; i < buf.length; i++) {
|
||||
if (v(buf[i])) return i - k;
|
||||
}
|
||||
return -1;
|
||||
default:
|
||||
throw new Error("Unrecognised signature type");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detects whether the given buffer is a file of the type specified.
|
||||
*
|
||||
* @param {string|RegExp} type
|
||||
* @param {Uint8Array} buf
|
||||
* @returns {string|false} The mime type or false if the type does not match
|
||||
*/
|
||||
export function isType(type, buf) {
|
||||
const types = detectFileType(buf);
|
||||
|
||||
if (!(types && types.length)) return false;
|
||||
|
||||
if (typeof type === "string") {
|
||||
return types.reduce((acc, t) => {
|
||||
const mime = t.mime.startsWith(type) ? t.mime : false;
|
||||
return acc || mime;
|
||||
}, false);
|
||||
} else if (type instanceof RegExp) {
|
||||
return types.reduce((acc, t) => {
|
||||
const mime = type.test(t.mime) ? t.mime : false;
|
||||
return acc || mime;
|
||||
}, false);
|
||||
} else {
|
||||
throw new Error("Invalid type input.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detects whether the given buffer contains an image file.
|
||||
*
|
||||
* @param {Uint8Array} buf
|
||||
* @returns {string|false} The mime type or false if the type does not match
|
||||
*/
|
||||
export function isImage(buf) {
|
||||
return isType("image", buf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to extract a file from a data stream given its offset and extractor function.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {Object} fileDetail
|
||||
* @param {string} fileDetail.mime
|
||||
* @param {string} fileDetail.extension
|
||||
* @param {Function} fileDetail.extractor
|
||||
* @param {number} offset
|
||||
* @returns {File}
|
||||
*/
|
||||
export function extractFile(bytes, fileDetail, offset) {
|
||||
if (fileDetail.extractor) {
|
||||
sendStatusMessage(`Attempting to extract ${fileDetail.name} at pos ${offset}...`);
|
||||
const fileData = fileDetail.extractor(bytes, offset);
|
||||
const ext = fileDetail.extension.split(",")[0];
|
||||
return new File([fileData], `extracted_at_0x${offset.toString(16)}.${ext}`, {
|
||||
type: fileDetail.mime
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`No extraction algorithm available for "${fileDetail.mime}" files`);
|
||||
}
|
||||
230
src/core/lib/LoremIpsum.mjs
Normal file
230
src/core/lib/LoremIpsum.mjs
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* Lorem Ipsum generator.
|
||||
*
|
||||
* @author Klaxon [klaxon@veyr.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate lorem ipsum paragraphs.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {string}
|
||||
*/
|
||||
export function GenerateParagraphs(length=3) {
|
||||
const paragraphs = [];
|
||||
while (paragraphs.length < length) {
|
||||
const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV);
|
||||
const sentences = [];
|
||||
while (sentences.length < paragraphLength) {
|
||||
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
|
||||
const sentence = getWords(sentenceLength);
|
||||
sentences.push(formatSentence(sentence));
|
||||
}
|
||||
paragraphs.push(formatParagraph(sentences));
|
||||
}
|
||||
paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -2);
|
||||
paragraphs[0] = replaceStart(paragraphs[0]);
|
||||
return paragraphs.join("");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate lorem ipsum sentences.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {string}
|
||||
*/
|
||||
export function GenerateSentences(length=3) {
|
||||
const sentences = [];
|
||||
while (sentences.length < length) {
|
||||
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
|
||||
const sentence = getWords(sentenceLength);
|
||||
sentences.push(formatSentence(sentence));
|
||||
}
|
||||
const paragraphs = sentencesToParagraphs(sentences);
|
||||
return paragraphs.join("");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate lorem ipsum words.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {string}
|
||||
*/
|
||||
export function GenerateWords(length=3) {
|
||||
const words = getWords(length);
|
||||
const sentences = wordsToSentences(words);
|
||||
const paragraphs = sentencesToParagraphs(sentences);
|
||||
return paragraphs.join("");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate lorem ipsum bytes.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {string}
|
||||
*/
|
||||
export function GenerateBytes(length=3) {
|
||||
const str = GenerateWords(length/3);
|
||||
return str.slice(0, length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get array of randomly selected words from the lorem ipsum wordList.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {string[]}
|
||||
* @private
|
||||
*/
|
||||
function getWords(length=3) {
|
||||
const words = [];
|
||||
let word;
|
||||
let previousWord;
|
||||
while (words.length < length){
|
||||
do {
|
||||
word = wordList[Math.floor(Math.random() * wordList.length)];
|
||||
} while (previousWord === word);
|
||||
words.push(word);
|
||||
previousWord = word;
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert an array of words into an array of sentences
|
||||
*
|
||||
* @param {string[]} words
|
||||
* @returns {string[]}
|
||||
* @private
|
||||
*/
|
||||
function wordsToSentences(words) {
|
||||
const sentences = [];
|
||||
while (words.length > 0) {
|
||||
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
|
||||
if (sentenceLength <= words.length) {
|
||||
sentences.push(formatSentence(words.splice(0, sentenceLength)));
|
||||
} else {
|
||||
sentences.push(formatSentence(words.splice(0, words.length)));
|
||||
}
|
||||
}
|
||||
return sentences;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert an array of sentences into an array of paragraphs
|
||||
*
|
||||
* @param {string[]} sentences
|
||||
* @returns {string[]}
|
||||
* @private
|
||||
*/
|
||||
function sentencesToParagraphs(sentences) {
|
||||
const paragraphs = [];
|
||||
while (sentences.length > 0) {
|
||||
const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV);
|
||||
paragraphs.push(formatParagraph(sentences.splice(0, paragraphLength)));
|
||||
}
|
||||
paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -1);
|
||||
paragraphs[0] = replaceStart(paragraphs[0]);
|
||||
return paragraphs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format an array of words into a sentence.
|
||||
*
|
||||
* @param {string[]} words
|
||||
* @returns {string}
|
||||
* @private
|
||||
*/
|
||||
function formatSentence(words) {
|
||||
// 0.35 chance of a comma being added randomly to the sentence.
|
||||
if (Math.random() < PROBABILITY_OF_A_COMMA) {
|
||||
const pos = Math.round(Math.random()*(words.length-1));
|
||||
words[pos] +=",";
|
||||
}
|
||||
let sentence = words.join(" ");
|
||||
sentence = sentence.charAt(0).toUpperCase() + sentence.slice(1);
|
||||
sentence += ".";
|
||||
return sentence;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format an array of sentences into a paragraph.
|
||||
*
|
||||
* @param {string[]} sentences
|
||||
* @returns {string}
|
||||
* @private
|
||||
*/
|
||||
function formatParagraph(sentences) {
|
||||
let paragraph = sentences.join(" ");
|
||||
paragraph += "\n\n";
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a random number based on a mean and standard deviation.
|
||||
*
|
||||
* @param {number} mean
|
||||
* @param {number} stdDev
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
function getRandomLength(mean, stdDev) {
|
||||
let length;
|
||||
do {
|
||||
length = Math.round((Math.random()*2-1)+(Math.random()*2-1)+(Math.random()*2-1)*stdDev+mean);
|
||||
} while (length <= 0);
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace first 5 words with "Lorem ipsum dolor sit amet"
|
||||
*
|
||||
* @param {string[]} str
|
||||
* @returns {string[]}
|
||||
* @private
|
||||
*/
|
||||
function replaceStart(str) {
|
||||
let words = str.split(" ");
|
||||
if (words.length > 5) {
|
||||
words.splice(0, 5, "Lorem", "ipsum", "dolor", "sit", "amet");
|
||||
return words.join(" ");
|
||||
} else {
|
||||
const lorem = ["Lorem", "ipsum", "dolor", "sit", "amet"];
|
||||
words = lorem.slice(0, words.length);
|
||||
str = words.join(" ");
|
||||
str += ".";
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const SENTENCE_LENGTH_MEAN = 15;
|
||||
const SENTENCE_LENGTH_STD_DEV = 9;
|
||||
const PARAGRAPH_LENGTH_MEAN = 5;
|
||||
const PARAGRAPH_LENGTH_STD_DEV = 2;
|
||||
const PROBABILITY_OF_A_COMMA = 0.35;
|
||||
|
||||
const wordList = [
|
||||
"ad", "adipisicing", "aliqua", "aliquip", "amet", "anim",
|
||||
"aute", "cillum", "commodo", "consectetur", "consequat", "culpa",
|
||||
"cupidatat", "deserunt", "do", "dolor", "dolore", "duis",
|
||||
"ea", "eiusmod", "elit", "enim", "esse", "est",
|
||||
"et", "eu", "ex", "excepteur", "exercitation", "fugiat",
|
||||
"id", "in", "incididunt", "ipsum", "irure", "labore",
|
||||
"laboris", "laborum", "Lorem", "magna", "minim", "mollit",
|
||||
"nisi", "non", "nostrud", "nulla", "occaecat", "officia",
|
||||
"pariatur", "proident", "qui", "quis", "reprehenderit", "sint",
|
||||
"sit", "sunt", "tempor", "ullamco", "ut", "velit",
|
||||
"veniam", "voluptate",
|
||||
];
|
||||
@@ -2,6 +2,7 @@ import OperationConfig from "../config/OperationConfig.json";
|
||||
import Utils from "../Utils";
|
||||
import Recipe from "../Recipe";
|
||||
import Dish from "../Dish";
|
||||
import {detectFileType} from "./FileType";
|
||||
import chiSquared from "chi-squared";
|
||||
|
||||
/**
|
||||
@@ -92,7 +93,14 @@ class Magic {
|
||||
* @returns {string} [type.desc] - Description
|
||||
*/
|
||||
detectFileType() {
|
||||
return Magic.magicFileType(this.inputBuffer);
|
||||
const fileType = detectFileType(this.inputBuffer);
|
||||
|
||||
if (!fileType.length) return null;
|
||||
return {
|
||||
ext: fileType[0].extension,
|
||||
mime: fileType[0].mime,
|
||||
desc: fileType[0].description
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -785,452 +793,9 @@ class Magic {
|
||||
}[code];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a buffer, detects magic byte sequences at specific positions and returns the
|
||||
* extension and mime type.
|
||||
*
|
||||
* @param {Uint8Array} buf
|
||||
* @returns {Object} type
|
||||
* @returns {string} type.ext - File extension
|
||||
* @returns {string} type.mime - Mime type
|
||||
* @returns {string} [type.desc] - Description
|
||||
*/
|
||||
static magicFileType(buf) {
|
||||
if (!(buf && buf.length > 1)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (buf[0] === 0xFF && buf[1] === 0xD8 && buf[2] === 0xFF) {
|
||||
return {
|
||||
ext: "jpg",
|
||||
mime: "image/jpeg"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4E && buf[3] === 0x47) {
|
||||
return {
|
||||
ext: "png",
|
||||
mime: "image/png"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) {
|
||||
return {
|
||||
ext: "gif",
|
||||
mime: "image/gif"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) {
|
||||
return {
|
||||
ext: "webp",
|
||||
mime: "image/webp"
|
||||
};
|
||||
}
|
||||
|
||||
// needs to be before `tif` check
|
||||
if (((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) && buf[8] === 0x43 && buf[9] === 0x52) {
|
||||
return {
|
||||
ext: "cr2",
|
||||
mime: "image/x-canon-cr2"
|
||||
};
|
||||
}
|
||||
|
||||
if ((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) {
|
||||
return {
|
||||
ext: "tif",
|
||||
mime: "image/tiff"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x42 && buf[1] === 0x4D) {
|
||||
return {
|
||||
ext: "bmp",
|
||||
mime: "image/bmp"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0xBC) {
|
||||
return {
|
||||
ext: "jxr",
|
||||
mime: "image/vnd.ms-photo"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x38 && buf[1] === 0x42 && buf[2] === 0x50 && buf[3] === 0x53) {
|
||||
return {
|
||||
ext: "psd",
|
||||
mime: "image/vnd.adobe.photoshop"
|
||||
};
|
||||
}
|
||||
|
||||
// needs to be before `zip` check
|
||||
if (buf[0] === 0x50 && buf[1] === 0x4B && buf[2] === 0x3 && buf[3] === 0x4 && buf[30] === 0x6D && buf[31] === 0x69 && buf[32] === 0x6D && buf[33] === 0x65 && buf[34] === 0x74 && buf[35] === 0x79 && buf[36] === 0x70 && buf[37] === 0x65 && buf[38] === 0x61 && buf[39] === 0x70 && buf[40] === 0x70 && buf[41] === 0x6C && buf[42] === 0x69 && buf[43] === 0x63 && buf[44] === 0x61 && buf[45] === 0x74 && buf[46] === 0x69 && buf[47] === 0x6F && buf[48] === 0x6E && buf[49] === 0x2F && buf[50] === 0x65 && buf[51] === 0x70 && buf[52] === 0x75 && buf[53] === 0x62 && buf[54] === 0x2B && buf[55] === 0x7A && buf[56] === 0x69 && buf[57] === 0x70) {
|
||||
return {
|
||||
ext: "epub",
|
||||
mime: "application/epub+zip"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x50 && buf[1] === 0x4B && (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) && (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)) {
|
||||
return {
|
||||
ext: "zip",
|
||||
mime: "application/zip"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[257] === 0x75 && buf[258] === 0x73 && buf[259] === 0x74 && buf[260] === 0x61 && buf[261] === 0x72) {
|
||||
return {
|
||||
ext: "tar",
|
||||
mime: "application/x-tar"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x52 && buf[1] === 0x61 && buf[2] === 0x72 && buf[3] === 0x21 && buf[4] === 0x1A && buf[5] === 0x7 && (buf[6] === 0x0 || buf[6] === 0x1)) {
|
||||
return {
|
||||
ext: "rar",
|
||||
mime: "application/x-rar-compressed"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x1F && buf[1] === 0x8B && buf[2] === 0x8) {
|
||||
return {
|
||||
ext: "gz",
|
||||
mime: "application/gzip"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x42 && buf[1] === 0x5A && buf[2] === 0x68) {
|
||||
return {
|
||||
ext: "bz2",
|
||||
mime: "application/x-bzip2"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x37 && buf[1] === 0x7A && buf[2] === 0xBC && buf[3] === 0xAF && buf[4] === 0x27 && buf[5] === 0x1C) {
|
||||
return {
|
||||
ext: "7z",
|
||||
mime: "application/x-7z-compressed"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x78 && buf[1] === 0x01) {
|
||||
return {
|
||||
ext: "dmg, zlib",
|
||||
mime: "application/x-apple-diskimage, application/x-deflate"
|
||||
};
|
||||
}
|
||||
|
||||
if ((buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && (buf[3] === 0x18 || buf[3] === 0x20) && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) || (buf[0] === 0x33 && buf[1] === 0x67 && buf[2] === 0x70 && buf[3] === 0x35) || (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x1C && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x6D && buf[9] === 0x70 && buf[10] === 0x34 && buf[11] === 0x32 && buf[16] === 0x6D && buf[17] === 0x70 && buf[18] === 0x34 && buf[19] === 0x31 && buf[20] === 0x6D && buf[21] === 0x70 && buf[22] === 0x34 && buf[23] === 0x32 && buf[24] === 0x69 && buf[25] === 0x73 && buf[26] === 0x6F && buf[27] === 0x6D)) {
|
||||
return {
|
||||
ext: "mp4",
|
||||
mime: "video/mp4"
|
||||
};
|
||||
}
|
||||
|
||||
if ((buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x1C && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x4D && buf[9] === 0x34 && buf[10] === 0x56)) {
|
||||
return {
|
||||
ext: "m4v",
|
||||
mime: "video/x-m4v"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x4D && buf[1] === 0x54 && buf[2] === 0x68 && buf[3] === 0x64) {
|
||||
return {
|
||||
ext: "mid",
|
||||
mime: "audio/midi"
|
||||
};
|
||||
}
|
||||
|
||||
// needs to be before the `webm` check
|
||||
if (buf[31] === 0x6D && buf[32] === 0x61 && buf[33] === 0x74 && buf[34] === 0x72 && buf[35] === 0x6f && buf[36] === 0x73 && buf[37] === 0x6B && buf[38] === 0x61) {
|
||||
return {
|
||||
ext: "mkv",
|
||||
mime: "video/x-matroska"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x1A && buf[1] === 0x45 && buf[2] === 0xDF && buf[3] === 0xA3) {
|
||||
return {
|
||||
ext: "webm",
|
||||
mime: "video/webm"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x14 && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) {
|
||||
return {
|
||||
ext: "mov",
|
||||
mime: "video/quicktime"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x41 && buf[9] === 0x56 && buf[10] === 0x49) {
|
||||
return {
|
||||
ext: "avi",
|
||||
mime: "video/x-msvideo"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x30 && buf[1] === 0x26 && buf[2] === 0xB2 && buf[3] === 0x75 && buf[4] === 0x8E && buf[5] === 0x66 && buf[6] === 0xCF && buf[7] === 0x11 && buf[8] === 0xA6 && buf[9] === 0xD9) {
|
||||
return {
|
||||
ext: "wmv",
|
||||
mime: "video/x-ms-wmv"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x1 && buf[3].toString(16)[0] === "b") {
|
||||
return {
|
||||
ext: "mpg",
|
||||
mime: "video/mpeg"
|
||||
};
|
||||
}
|
||||
|
||||
if ((buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) || (buf[0] === 0xFF && buf[1] === 0xfb)) {
|
||||
return {
|
||||
ext: "mp3",
|
||||
mime: "audio/mpeg"
|
||||
};
|
||||
}
|
||||
|
||||
if ((buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x4D && buf[9] === 0x34 && buf[10] === 0x41) || (buf[0] === 0x4D && buf[1] === 0x34 && buf[2] === 0x41 && buf[3] === 0x20)) {
|
||||
return {
|
||||
ext: "m4a",
|
||||
mime: "audio/m4a"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x4F && buf[1] === 0x67 && buf[2] === 0x67 && buf[3] === 0x53) {
|
||||
return {
|
||||
ext: "ogg",
|
||||
mime: "audio/ogg"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x66 && buf[1] === 0x4C && buf[2] === 0x61 && buf[3] === 0x43) {
|
||||
return {
|
||||
ext: "flac",
|
||||
mime: "audio/x-flac"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45) {
|
||||
return {
|
||||
ext: "wav",
|
||||
mime: "audio/x-wav"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x23 && buf[1] === 0x21 && buf[2] === 0x41 && buf[3] === 0x4D && buf[4] === 0x52 && buf[5] === 0x0A) {
|
||||
return {
|
||||
ext: "amr",
|
||||
mime: "audio/amr"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x25 && buf[1] === 0x50 && buf[2] === 0x44 && buf[3] === 0x46) {
|
||||
return {
|
||||
ext: "pdf",
|
||||
mime: "application/pdf"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x4D && buf[1] === 0x5A) {
|
||||
return {
|
||||
ext: "exe",
|
||||
mime: "application/x-msdownload"
|
||||
};
|
||||
}
|
||||
|
||||
if ((buf[0] === 0x43 || buf[0] === 0x46) && buf[1] === 0x57 && buf[2] === 0x53) {
|
||||
return {
|
||||
ext: "swf",
|
||||
mime: "application/x-shockwave-flash"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x7B && buf[1] === 0x5C && buf[2] === 0x72 && buf[3] === 0x74 && buf[4] === 0x66) {
|
||||
return {
|
||||
ext: "rtf",
|
||||
mime: "application/rtf"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x77 && buf[1] === 0x4F && buf[2] === 0x46 && buf[3] === 0x46 && buf[4] === 0x00 && buf[5] === 0x01 && buf[6] === 0x00 && buf[7] === 0x00) {
|
||||
return {
|
||||
ext: "woff",
|
||||
mime: "application/font-woff"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x77 && buf[1] === 0x4F && buf[2] === 0x46 && buf[3] === 0x32 && buf[4] === 0x00 && buf[5] === 0x01 && buf[6] === 0x00 && buf[7] === 0x00) {
|
||||
return {
|
||||
ext: "woff2",
|
||||
mime: "application/font-woff"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[34] === 0x4C && buf[35] === 0x50 && ((buf[8] === 0x02 && buf[9] === 0x00 && buf[10] === 0x01) || (buf[8] === 0x01 && buf[9] === 0x00 && buf[10] === 0x00) || (buf[8] === 0x02 && buf[9] === 0x00 && buf[10] === 0x02))) {
|
||||
return {
|
||||
ext: "eot",
|
||||
mime: "application/octet-stream"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x00 && buf[1] === 0x01 && buf[2] === 0x00 && buf[3] === 0x00 && buf[4] === 0x00) {
|
||||
return {
|
||||
ext: "ttf",
|
||||
mime: "application/font-sfnt"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x4F && buf[1] === 0x54 && buf[2] === 0x54 && buf[3] === 0x4F && buf[4] === 0x00) {
|
||||
return {
|
||||
ext: "otf",
|
||||
mime: "application/font-sfnt"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x00 && buf[1] === 0x00 && buf[2] === 0x01 && buf[3] === 0x00) {
|
||||
return {
|
||||
ext: "ico",
|
||||
mime: "image/x-icon"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x46 && buf[1] === 0x4C && buf[2] === 0x56 && buf[3] === 0x01) {
|
||||
return {
|
||||
ext: "flv",
|
||||
mime: "video/x-flv"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x25 && buf[1] === 0x21) {
|
||||
return {
|
||||
ext: "ps",
|
||||
mime: "application/postscript"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0xFD && buf[1] === 0x37 && buf[2] === 0x7A && buf[3] === 0x58 && buf[4] === 0x5A && buf[5] === 0x00) {
|
||||
return {
|
||||
ext: "xz",
|
||||
mime: "application/x-xz"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x53 && buf[1] === 0x51 && buf[2] === 0x4C && buf[3] === 0x69) {
|
||||
return {
|
||||
ext: "sqlite",
|
||||
mime: "application/x-sqlite3"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Added by n1474335 [n1474335@gmail.com] from here on
|
||||
*
|
||||
*/
|
||||
if ((buf[0] === 0x1F && buf[1] === 0x9D) || (buf[0] === 0x1F && buf[1] === 0xA0)) {
|
||||
return {
|
||||
ext: "z, tar.z",
|
||||
mime: "application/x-gtar"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x7F && buf[1] === 0x45 && buf[2] === 0x4C && buf[3] === 0x46) {
|
||||
return {
|
||||
ext: "none, axf, bin, elf, o, prx, puff, so",
|
||||
mime: "application/x-executable",
|
||||
desc: "Executable and Linkable Format file. No standard file extension."
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0xCA && buf[1] === 0xFE && buf[2] === 0xBA && buf[3] === 0xBE) {
|
||||
return {
|
||||
ext: "class",
|
||||
mime: "application/java-vm"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) {
|
||||
return {
|
||||
ext: "txt",
|
||||
mime: "text/plain",
|
||||
desc: "UTF-8 encoded Unicode byte order mark detected, commonly but not exclusively seen in text files."
|
||||
};
|
||||
}
|
||||
|
||||
// Must be before Little-endian UTF-16 BOM
|
||||
if (buf[0] === 0xFF && buf[1] === 0xFE && buf[2] === 0x00 && buf[3] === 0x00) {
|
||||
return {
|
||||
ext: "UTF32LE",
|
||||
mime: "charset/utf32le",
|
||||
desc: "Little-endian UTF-32 encoded Unicode byte order mark detected."
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0xFF && buf[1] === 0xFE) {
|
||||
return {
|
||||
ext: "UTF16LE",
|
||||
mime: "charset/utf16le",
|
||||
desc: "Little-endian UTF-16 encoded Unicode byte order mark detected."
|
||||
};
|
||||
}
|
||||
|
||||
if ((buf[0x8001] === 0x43 && buf[0x8002] === 0x44 && buf[0x8003] === 0x30 && buf[0x8004] === 0x30 && buf[0x8005] === 0x31) ||
|
||||
(buf[0x8801] === 0x43 && buf[0x8802] === 0x44 && buf[0x8803] === 0x30 && buf[0x8804] === 0x30 && buf[0x8805] === 0x31) ||
|
||||
(buf[0x9001] === 0x43 && buf[0x9002] === 0x44 && buf[0x9003] === 0x30 && buf[0x9004] === 0x30 && buf[0x9005] === 0x31)) {
|
||||
return {
|
||||
ext: "iso",
|
||||
mime: "application/octet-stream",
|
||||
desc: "ISO 9660 CD/DVD image file"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0xD0 && buf[1] === 0xCF && buf[2] === 0x11 && buf[3] === 0xE0 && buf[4] === 0xA1 && buf[5] === 0xB1 && buf[6] === 0x1A && buf[7] === 0xE1) {
|
||||
return {
|
||||
ext: "doc, xls, ppt",
|
||||
mime: "application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint",
|
||||
desc: "Microsoft Office documents"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x64 && buf[1] === 0x65 && buf[2] === 0x78 && buf[3] === 0x0A && buf[4] === 0x30 && buf[5] === 0x33 && buf[6] === 0x35 && buf[7] === 0x00) {
|
||||
return {
|
||||
ext: "dex",
|
||||
mime: "application/octet-stream",
|
||||
desc: "Dalvik Executable (Android)"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x4B && buf[1] === 0x44 && buf[2] === 0x4D) {
|
||||
return {
|
||||
ext: "vmdk",
|
||||
mime: "application/vmdk, application/x-virtualbox-vmdk"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x43 && buf[1] === 0x72 && buf[2] === 0x32 && buf[3] === 0x34) {
|
||||
return {
|
||||
ext: "crx",
|
||||
mime: "application/crx",
|
||||
desc: "Google Chrome extension or packaged app"
|
||||
};
|
||||
}
|
||||
|
||||
if (buf[0] === 0x78 && (buf[1] === 0x01 || buf[1] === 0x9C || buf[1] === 0xDA || buf[1] === 0x5e)) {
|
||||
return {
|
||||
ext: "zlib",
|
||||
mime: "application/x-deflate"
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Byte frequencies of various languages generated from Wikipedia dumps taken in late 2017 and early 2018.
|
||||
* The Chi-Squared test cannot accept expected values of 0, so 0.0001 has been used to account for bytes
|
||||
|
||||
263
src/core/lib/Stream.mjs
Normal file
263
src/core/lib/Stream.mjs
Normal file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* Stream class for parsing binary protocols.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @author tlwr [toby@toby.codes]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Stream can be used to traverse a binary blob, interpreting sections of it
|
||||
* as various data types.
|
||||
*/
|
||||
export default class Stream {
|
||||
|
||||
/**
|
||||
* Stream constructor.
|
||||
*
|
||||
* @param {Uint8Array} input
|
||||
*/
|
||||
constructor(input) {
|
||||
this.bytes = input;
|
||||
this.length = this.bytes.length;
|
||||
this.position = 0;
|
||||
this.bitPos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a number of bytes from the current position.
|
||||
*
|
||||
* @param {number} numBytes
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
getBytes(numBytes) {
|
||||
if (this.position > this.length) return undefined;
|
||||
|
||||
const newPosition = this.position + numBytes;
|
||||
const bytes = this.bytes.slice(this.position, newPosition);
|
||||
this.position = newPosition;
|
||||
this.bitPos = 0;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpret the following bytes as a string, stopping at the next null byte or
|
||||
* the supplied limit.
|
||||
*
|
||||
* @param {number} numBytes
|
||||
* @returns {string}
|
||||
*/
|
||||
readString(numBytes) {
|
||||
if (this.position > this.length) return undefined;
|
||||
|
||||
let result = "";
|
||||
for (let i = this.position; i < this.position + numBytes; i++) {
|
||||
const currentByte = this.bytes[i];
|
||||
if (currentByte === 0) break;
|
||||
result += String.fromCharCode(currentByte);
|
||||
}
|
||||
this.position += numBytes;
|
||||
this.bitPos = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpret the following bytes as an integer in big or little endian.
|
||||
*
|
||||
* @param {number} numBytes
|
||||
* @param {string} [endianness="be"]
|
||||
* @returns {number}
|
||||
*/
|
||||
readInt(numBytes, endianness="be") {
|
||||
if (this.position > this.length) return undefined;
|
||||
|
||||
let val = 0;
|
||||
if (endianness === "be") {
|
||||
for (let i = this.position; i < this.position + numBytes; i++) {
|
||||
val = val << 8;
|
||||
val |= this.bytes[i];
|
||||
}
|
||||
} else {
|
||||
for (let i = this.position + numBytes - 1; i >= this.position; i--) {
|
||||
val = val << 8;
|
||||
val |= this.bytes[i];
|
||||
}
|
||||
}
|
||||
this.position += numBytes;
|
||||
this.bitPos = 0;
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a number of bits from the buffer.
|
||||
*
|
||||
* @TODO Add endianness
|
||||
*
|
||||
* @param {number} numBits
|
||||
* @returns {number}
|
||||
*/
|
||||
readBits(numBits) {
|
||||
if (this.position > this.length) return undefined;
|
||||
|
||||
let bitBuf = 0,
|
||||
bitBufLen = 0;
|
||||
|
||||
// Add remaining bits from current byte
|
||||
bitBuf = (this.bytes[this.position++] & bitMask(this.bitPos)) >>> this.bitPos;
|
||||
bitBufLen = 8 - this.bitPos;
|
||||
this.bitPos = 0;
|
||||
|
||||
// Not enough bits yet
|
||||
while (bitBufLen < numBits) {
|
||||
bitBuf |= this.bytes[this.position++] << bitBufLen;
|
||||
bitBufLen += 8;
|
||||
}
|
||||
|
||||
// Reverse back to numBits
|
||||
if (bitBufLen > numBits) {
|
||||
const excess = bitBufLen - numBits;
|
||||
bitBuf &= (1 << numBits) - 1;
|
||||
bitBufLen -= excess;
|
||||
this.position--;
|
||||
this.bitPos = 8 - excess;
|
||||
}
|
||||
|
||||
return bitBuf;
|
||||
|
||||
/**
|
||||
* Calculates the bit mask based on the current bit position.
|
||||
*
|
||||
* @param {number} bitPos
|
||||
* @returns {number} The bit mask
|
||||
*/
|
||||
function bitMask(bitPos) {
|
||||
return 256 - (1 << bitPos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the stream until we reach the specified byte or sequence of bytes.
|
||||
*
|
||||
* @param {number|List<number>} val
|
||||
*/
|
||||
continueUntil(val) {
|
||||
if (this.position > this.length) return;
|
||||
|
||||
this.bitPos = 0;
|
||||
|
||||
if (typeof val === "number") {
|
||||
while (++this.position < this.length && this.bytes[this.position] !== val) {
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// val is an array
|
||||
let found = false;
|
||||
while (!found && this.position < this.length) {
|
||||
while (++this.position < this.length && this.bytes[this.position] !== val[0]) {
|
||||
continue;
|
||||
}
|
||||
found = true;
|
||||
for (let i = 1; i < val.length; i++) {
|
||||
if (this.position + i > this.length || this.bytes[this.position + i] !== val[i])
|
||||
found = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the next byte if it matches the supplied value.
|
||||
*
|
||||
* @param {number} val
|
||||
*/
|
||||
consumeIf(val) {
|
||||
if (this.bytes[this.position] === val) {
|
||||
this.position++;
|
||||
this.bitPos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forwards through the stream by the specified number of bytes.
|
||||
*
|
||||
* @param {number} numBytes
|
||||
*/
|
||||
moveForwardsBy(numBytes) {
|
||||
const pos = this.position + numBytes;
|
||||
if (pos < 0 || pos > this.length)
|
||||
throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
|
||||
this.position = pos;
|
||||
this.bitPos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move backwards through the stream by the specified number of bytes.
|
||||
*
|
||||
* @param {number} numBytes
|
||||
*/
|
||||
moveBackwardsBy(numBytes) {
|
||||
const pos = this.position - numBytes;
|
||||
if (pos < 0 || pos > this.length)
|
||||
throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
|
||||
this.position = pos;
|
||||
this.bitPos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move backwards through the strem by the specified number of bits.
|
||||
*
|
||||
* @param {number} numBits
|
||||
*/
|
||||
moveBackwardsByBits(numBits) {
|
||||
if (numBits <= this.bitPos) {
|
||||
this.bitPos -= numBits;
|
||||
} else {
|
||||
if (this.bitPos > 0) {
|
||||
numBits -= this.bitPos;
|
||||
this.bitPos = 0;
|
||||
}
|
||||
|
||||
while (numBits > 0) {
|
||||
this.moveBackwardsBy(1);
|
||||
this.bitPos = 8;
|
||||
this.moveBackwardsByBits(numBits);
|
||||
numBits -= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to a specified position in the stream.
|
||||
*
|
||||
* @param {number} pos
|
||||
*/
|
||||
moveTo(pos) {
|
||||
if (pos < 0 || pos > this.length)
|
||||
throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
|
||||
this.position = pos;
|
||||
this.bitPos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are more bytes left in the stream.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasMore() {
|
||||
return this.position < this.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a slice of the stream up to the current position.
|
||||
*
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
carve() {
|
||||
if (this.bitPos > 0) this.position++;
|
||||
return this.bytes.slice(0, this.position);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import bsonjs from "bson";
|
||||
import bson from "bson";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
@@ -36,8 +36,6 @@ class BSONDeserialise extends Operation {
|
||||
run(input, args) {
|
||||
if (!input.byteLength) return "";
|
||||
|
||||
const bson = new bsonjs();
|
||||
|
||||
try {
|
||||
const data = bson.deserialize(new Buffer(input));
|
||||
return JSON.stringify(data, null, 2);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import bsonjs from "bson";
|
||||
import bson from "bson";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
@@ -36,8 +36,6 @@ class BSONSerialise extends Operation {
|
||||
run(input, args) {
|
||||
if (!input) return new ArrayBuffer();
|
||||
|
||||
const bson = new bsonjs();
|
||||
|
||||
try {
|
||||
const data = JSON.parse(input);
|
||||
return bson.serialize(data).buffer;
|
||||
|
||||
95
src/core/operations/ConvertCoordinateFormat.mjs
Normal file
95
src/core/operations/ConvertCoordinateFormat.mjs
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates";
|
||||
|
||||
/**
|
||||
* Convert co-ordinate format operation
|
||||
*/
|
||||
class ConvertCoordinateFormat extends Operation {
|
||||
|
||||
/**
|
||||
* ConvertCoordinateFormat constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Convert co-ordinate format";
|
||||
this.module = "Hashing";
|
||||
this.description = "Converts geographical coordinates between different formats.<br><br>Supported formats:<ul><li>Degrees Minutes Seconds (DMS)</li><li>Degrees Decimal Minutes (DDM)</li><li>Decimal Degrees (DD)</li><li>Geohash</li><li>Military Grid Reference System (MGRS)</li><li>Ordnance Survey National Grid (OSNG)</li><li>Universal Transverse Mercator (UTM)</li></ul><br>The operation can try to detect the input co-ordinate format and delimiter automatically, but this may not always work correctly.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Geographic_coordinate_conversion";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Input Format",
|
||||
"type": "option",
|
||||
"value": ["Auto"].concat(FORMATS)
|
||||
},
|
||||
{
|
||||
"name": "Input Delimiter",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"Auto",
|
||||
"Direction Preceding",
|
||||
"Direction Following",
|
||||
"\\n",
|
||||
"Comma",
|
||||
"Semi-colon",
|
||||
"Colon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Output Format",
|
||||
"type": "option",
|
||||
"value": FORMATS
|
||||
},
|
||||
{
|
||||
"name": "Output Delimiter",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"Space",
|
||||
"\\n",
|
||||
"Comma",
|
||||
"Semi-colon",
|
||||
"Colon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Include Compass Directions",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"None",
|
||||
"Before",
|
||||
"After"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Precision",
|
||||
"type": "number",
|
||||
"value": 3
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (input.replace(/[\s+]/g, "") !== "") {
|
||||
const [inFormat, inDelim, outFormat, outDelim, incDirection, precision] = args;
|
||||
const result = convertCoordinates(input, inFormat, inDelim, outFormat, outDelim, incDirection, precision);
|
||||
return result;
|
||||
} else {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ConvertCoordinateFormat;
|
||||
125
src/core/operations/DNSOverHTTPS.mjs
Normal file
125
src/core/operations/DNSOverHTTPS.mjs
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @author h345983745
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* DNS over HTTPS operation
|
||||
*/
|
||||
class DNSOverHTTPS extends Operation {
|
||||
|
||||
/**
|
||||
* DNSOverHTTPS constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "DNS over HTTPS";
|
||||
this.module = "Default";
|
||||
this.description = [
|
||||
"Takes a single domain name and performs a DNS lookup using DNS over HTTPS.",
|
||||
"<br><br>",
|
||||
"By default, <a href='https://developers.cloudflare.com/1.1.1.1/dns-over-https/'>Cloudflare</a> and <a href='https://developers.google.com/speed/public-dns/docs/dns-over-https'>Google</a> DNS over HTTPS services are supported.",
|
||||
"<br><br>",
|
||||
"Can be used with any service that supports the GET parameters <code>name</code> and <code>type</code>."
|
||||
].join("\n");
|
||||
this.infoURL = "https://wikipedia.org/wiki/DNS_over_HTTPS";
|
||||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
this.manualBake = true;
|
||||
this.args = [
|
||||
{
|
||||
name: "Resolver",
|
||||
type: "editableOption",
|
||||
value: [
|
||||
{
|
||||
name: "Google",
|
||||
value: "https://dns.google.com/resolve"
|
||||
},
|
||||
{
|
||||
name: "Cloudflare",
|
||||
value: "https://cloudflare-dns.com/dns-query"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Request Type",
|
||||
type: "option",
|
||||
value: [
|
||||
"A",
|
||||
"AAAA",
|
||||
"TXT",
|
||||
"MX",
|
||||
"DNSKEY",
|
||||
"NS"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Answer Data Only",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Validate DNSSEC",
|
||||
type: "boolean",
|
||||
value: true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {JSON}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [resolver, requestType, justAnswer, DNSSEC] = args;
|
||||
let url = URL;
|
||||
try {
|
||||
url = new URL(resolver);
|
||||
} catch (error) {
|
||||
throw new OperationError(error.toString() +
|
||||
"\n\nThis error could be caused by one of the following:\n" +
|
||||
" - An invalid Resolver URL\n");
|
||||
}
|
||||
const params = {name: input, type: requestType, cd: DNSSEC};
|
||||
|
||||
url.search = new URLSearchParams(params);
|
||||
|
||||
return fetch(url, {headers: {"accept": "application/dns-json"}}).then(response => {
|
||||
return response.json();
|
||||
}).then(data => {
|
||||
if (justAnswer) {
|
||||
return extractData(data.Answer);
|
||||
}
|
||||
return data;
|
||||
}).catch(e => {
|
||||
throw new OperationError(`Error making request to ${url}\n${e.toString()}`);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an array of just data from a DNS Answer section
|
||||
*
|
||||
* @private
|
||||
* @param {JSON} data
|
||||
* @returns {JSON}
|
||||
*/
|
||||
function extractData(data) {
|
||||
if (typeof(data) == "undefined"){
|
||||
return [];
|
||||
} else {
|
||||
const dataValues = [];
|
||||
data.forEach(element => {
|
||||
dataValues.push(element.data);
|
||||
});
|
||||
return dataValues;
|
||||
}
|
||||
}
|
||||
|
||||
export default DNSOverHTTPS;
|
||||
@@ -5,7 +5,8 @@
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Magic from "../lib/Magic";
|
||||
import {detectFileType} from "../lib/FileType";
|
||||
import {FILE_SIGNATURES} from "../lib/FileSignatures";
|
||||
|
||||
/**
|
||||
* Detect File Type operation
|
||||
@@ -24,7 +25,13 @@ class DetectFileType extends Operation {
|
||||
this.infoURL = "https://wikipedia.org/wiki/List_of_file_signatures";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.args = Object.keys(FILE_SIGNATURES).map(cat => {
|
||||
return {
|
||||
name: cat,
|
||||
type: "boolean",
|
||||
value: true
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,17 +41,27 @@ class DetectFileType extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const data = new Uint8Array(input),
|
||||
type = Magic.magicFileType(data);
|
||||
categories = [];
|
||||
|
||||
if (!type) {
|
||||
args.forEach((cat, i) => {
|
||||
if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]);
|
||||
});
|
||||
|
||||
const types = detectFileType(data, categories);
|
||||
|
||||
if (!types.length) {
|
||||
return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?";
|
||||
} else {
|
||||
let output = "File extension: " + type.ext + "\n" +
|
||||
"MIME type: " + type.mime;
|
||||
let output = "";
|
||||
|
||||
if (type.desc && type.desc.length) {
|
||||
output += "\nDescription: " + type.desc;
|
||||
}
|
||||
types.forEach(type => {
|
||||
output += "File extension: " + type.extension + "\n" +
|
||||
"MIME type: " + type.mime + "\n";
|
||||
|
||||
if (type.description && type.description.length) {
|
||||
output += "\nDescription: " + type.description + "\n";
|
||||
}
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class Divide extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const val = div(createNumArray(input, args[0]));
|
||||
return val instanceof BigNumber ? val : new BigNumber(NaN);
|
||||
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
100
src/core/operations/ExtractFiles.mjs
Normal file
100
src/core/operations/ExtractFiles.mjs
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import Utils from "../Utils";
|
||||
import {scanForFileTypes, extractFile} from "../lib/FileType";
|
||||
import {FILE_SIGNATURES} from "../lib/FileSignatures";
|
||||
|
||||
/**
|
||||
* Extract Files operation
|
||||
*/
|
||||
class ExtractFiles extends Operation {
|
||||
|
||||
/**
|
||||
* ExtractFiles constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Extract Files";
|
||||
this.module = "Default";
|
||||
this.description = "TODO";
|
||||
this.infoURL = "https://forensicswiki.org/wiki/File_Carving";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "List<File>";
|
||||
this.presentType = "html";
|
||||
this.args = Object.keys(FILE_SIGNATURES).map(cat => {
|
||||
return {
|
||||
name: cat,
|
||||
type: "boolean",
|
||||
value: cat === "Miscellaneous" ? false : true
|
||||
};
|
||||
}).concat([
|
||||
{
|
||||
name: "Ignore failed extractions",
|
||||
type: "boolean",
|
||||
value: "true"
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {List<File>}
|
||||
*/
|
||||
run(input, args) {
|
||||
const bytes = new Uint8Array(input),
|
||||
categories = [],
|
||||
ignoreFailedExtractions = args.pop(1);
|
||||
|
||||
args.forEach((cat, i) => {
|
||||
if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]);
|
||||
});
|
||||
|
||||
// Scan for embedded files
|
||||
const detectedFiles = scanForFileTypes(bytes, categories);
|
||||
|
||||
// Extract each file that we support
|
||||
const files = [];
|
||||
const errors = [];
|
||||
detectedFiles.forEach(detectedFile => {
|
||||
try {
|
||||
files.push(extractFile(bytes, detectedFile.fileDetails, detectedFile.offset));
|
||||
} catch (err) {
|
||||
if (!ignoreFailedExtractions && err.message.indexOf("No extraction algorithm available") < 0) {
|
||||
errors.push(
|
||||
`Error while attempting to extract ${detectedFile.fileDetails.name} ` +
|
||||
`at offset ${detectedFile.offset}:\n` +
|
||||
`${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new OperationError(errors.join("\n\n"));
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Displays the files in HTML for web apps.
|
||||
*
|
||||
* @param {File[]} files
|
||||
* @returns {html}
|
||||
*/
|
||||
async present(files) {
|
||||
return await Utils.displayFilesAsHTML(files);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ExtractFiles;
|
||||
@@ -89,7 +89,7 @@ class Fork extends Operation {
|
||||
// Run recipe over each tranche
|
||||
for (i = 0; i < inputs.length; i++) {
|
||||
// Baseline ing values for each tranche so that registers are reset
|
||||
subOpList.forEach((op, i) => {
|
||||
recipe.opList.forEach((op, i) => {
|
||||
op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));
|
||||
});
|
||||
|
||||
|
||||
39
src/core/operations/FromCaseInsensitiveRegex.mjs
Normal file
39
src/core/operations/FromCaseInsensitiveRegex.mjs
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @author masq [github.cyberchef@masq.cc]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
|
||||
/**
|
||||
* From Case Insensitive Regex operation
|
||||
*/
|
||||
class FromCaseInsensitiveRegex extends Operation {
|
||||
|
||||
/**
|
||||
* FromCaseInsensitiveRegex constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "From Case Insensitive Regex";
|
||||
this.module = "Default";
|
||||
this.description = "Converts a case-insensitive regex string to a case sensitive regex string (no guarantee on it being the proper original casing) in case the i flag wasn't available at the time but now is, or you need it to be case-sensitive again.<br><br>e.g. <code>[mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .*</code> becomes <code>Mozilla/[0-9].[0-9] .*</code>";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Regular_expression";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return input.replace(/\[[a-z]{2}\]/ig, m => m[1].toUpperCase() === m[2].toUpperCase() ? m[1] : m);
|
||||
}
|
||||
}
|
||||
|
||||
export default FromCaseInsensitiveRegex;
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* @author gchq77703 []
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import geohash from "ngeohash";
|
||||
|
||||
/**
|
||||
* From Geohash operation
|
||||
*/
|
||||
class FromGeohash extends Operation {
|
||||
|
||||
/**
|
||||
* FromGeohash constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "From Geohash";
|
||||
this.module = "Crypto";
|
||||
this.description = "Converts Geohash strings into Lat/Long coordinates. For example, <code>ww8p1r4t8</code> becomes <code>37.8324,112.5584</code>.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Geohash";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return input.split("\n").map(line => {
|
||||
const coords = geohash.decode(line);
|
||||
return [coords.latitude, coords.longitude].join(",");
|
||||
}).join("\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default FromGeohash;
|
||||
70
src/core/operations/GenerateLoremIpsum.mjs
Normal file
70
src/core/operations/GenerateLoremIpsum.mjs
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @author klaxon [klaxon@veyr.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum";
|
||||
|
||||
/**
|
||||
* Generate Lorem Ipsum operation
|
||||
*/
|
||||
class GenerateLoremIpsum extends Operation {
|
||||
|
||||
/**
|
||||
* GenerateLoremIpsum constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Generate Lorem Ipsum";
|
||||
this.module = "Default";
|
||||
this.description = "Generate varying length lorem ipsum placeholder text.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Lorem_ipsum";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Length",
|
||||
"type": "number",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"name": "Length in",
|
||||
"type": "option",
|
||||
"value": ["Paragraphs", "Sentences", "Words", "Bytes"]
|
||||
}
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [length, lengthType] = args;
|
||||
if (length < 1){
|
||||
throw new OperationError("Length must be greater than 0");
|
||||
}
|
||||
switch (lengthType) {
|
||||
case "Paragraphs":
|
||||
return GenerateParagraphs(length);
|
||||
case "Sentences":
|
||||
return GenerateSentences(length);
|
||||
case "Words":
|
||||
return GenerateWords(length);
|
||||
case "Bytes":
|
||||
return GenerateBytes(length);
|
||||
default:
|
||||
throw new OperationError("Invalid length type");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GenerateLoremIpsum;
|
||||
@@ -8,7 +8,7 @@ import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import qr from "qr-image";
|
||||
import { toBase64 } from "../lib/Base64";
|
||||
import Magic from "../lib/Magic";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import Utils from "../Utils";
|
||||
|
||||
/**
|
||||
@@ -100,9 +100,9 @@ class GenerateQRCode extends Operation {
|
||||
|
||||
if (format === "PNG") {
|
||||
let dataURI = "data:";
|
||||
const type = Magic.magicFileType(data);
|
||||
if (type && type.mime.indexOf("image") === 0){
|
||||
dataURI += type.mime + ";";
|
||||
const mime = isImage(data);
|
||||
if (mime){
|
||||
dataURI += mime + ";";
|
||||
} else {
|
||||
throw new OperationError("Invalid PNG file generated by QR image");
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class Mean extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const val = mean(createNumArray(input, args[0]));
|
||||
return val instanceof BigNumber ? val : new BigNumber(NaN);
|
||||
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class Median extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const val = median(createNumArray(input, args[0]));
|
||||
return val instanceof BigNumber ? val : new BigNumber(NaN);
|
||||
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class Multiply extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const val = multi(createNumArray(input, args[0]));
|
||||
return val instanceof BigNumber ? val : new BigNumber(NaN);
|
||||
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import Magic from "../lib/Magic";
|
||||
import { isImage } from "../lib/FileType";
|
||||
import jsqr from "jsqr";
|
||||
import jimp from "jimp";
|
||||
|
||||
@@ -42,64 +42,61 @@ class ParseQRCode extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const type = Magic.magicFileType(input);
|
||||
const [normalise] = args;
|
||||
|
||||
// Make sure that the input is an image
|
||||
if (type && type.mime.indexOf("image") === 0) {
|
||||
let image = input;
|
||||
if (!isImage(input)) throw new OperationError("Invalid file type.");
|
||||
|
||||
if (normalise) {
|
||||
// Process the image to be easier to read by jsqr
|
||||
// Disables the alpha channel
|
||||
// Sets the image default background to white
|
||||
// Normalises the image colours
|
||||
// Makes the image greyscale
|
||||
// Converts image to a JPEG
|
||||
image = await new Promise((resolve, reject) => {
|
||||
jimp.read(Buffer.from(input))
|
||||
.then(image => {
|
||||
image
|
||||
.rgba(false)
|
||||
.background(0xFFFFFFFF)
|
||||
.normalize()
|
||||
.greyscale()
|
||||
.getBuffer(jimp.MIME_JPEG, (error, result) => {
|
||||
resolve(result);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
reject(new OperationError("Error reading the image file."));
|
||||
});
|
||||
});
|
||||
}
|
||||
let image = input;
|
||||
|
||||
if (image instanceof OperationError) {
|
||||
throw image;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
jimp.read(Buffer.from(image))
|
||||
if (normalise) {
|
||||
// Process the image to be easier to read by jsqr
|
||||
// Disables the alpha channel
|
||||
// Sets the image default background to white
|
||||
// Normalises the image colours
|
||||
// Makes the image greyscale
|
||||
// Converts image to a JPEG
|
||||
image = await new Promise((resolve, reject) => {
|
||||
jimp.read(Buffer.from(input))
|
||||
.then(image => {
|
||||
if (image.bitmap != null) {
|
||||
const qrData = jsqr(image.bitmap.data, image.getWidth(), image.getHeight());
|
||||
if (qrData != null) {
|
||||
resolve(qrData.data);
|
||||
} else {
|
||||
reject(new OperationError("Couldn't read a QR code from the image."));
|
||||
}
|
||||
} else {
|
||||
reject(new OperationError("Error reading the image file."));
|
||||
}
|
||||
image
|
||||
.rgba(false)
|
||||
.background(0xFFFFFFFF)
|
||||
.normalize()
|
||||
.greyscale()
|
||||
.getBuffer(jimp.MIME_JPEG, (error, result) => {
|
||||
resolve(result);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
reject(new OperationError("Error reading the image file."));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
throw new OperationError("Invalid file type.");
|
||||
}
|
||||
|
||||
if (image instanceof OperationError) {
|
||||
throw image;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
jimp.read(Buffer.from(image))
|
||||
.then(image => {
|
||||
if (image.bitmap != null) {
|
||||
const qrData = jsqr(image.bitmap.data, image.getWidth(), image.getHeight());
|
||||
if (qrData != null) {
|
||||
resolve(qrData.data);
|
||||
} else {
|
||||
reject(new OperationError("Couldn't read a QR code from the image."));
|
||||
}
|
||||
} else {
|
||||
reject(new OperationError("Error reading the image file."));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
reject(new OperationError("Error reading the image file."));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -181,8 +181,8 @@ class ParseX509Certificate extends Operation {
|
||||
Serial number: ${new r.BigInteger(sn, 16).toString()} (0x${sn})
|
||||
Algorithm ID: ${cert.getSignatureAlgorithmField()}
|
||||
Validity
|
||||
Not Before: ${nbDate} (dd-mm-yy hh:mm:ss) (${cert.getNotBefore()})
|
||||
Not After: ${naDate} (dd-mm-yy hh:mm:ss) (${cert.getNotAfter()})
|
||||
Not Before: ${nbDate} (dd-mm-yyyy hh:mm:ss) (${cert.getNotBefore()})
|
||||
Not After: ${naDate} (dd-mm-yyyy hh:mm:ss) (${cert.getNotAfter()})
|
||||
Issuer
|
||||
${issuerStr}
|
||||
Subject
|
||||
@@ -206,12 +206,15 @@ ${extensions}`;
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatDate (dateStr) {
|
||||
return dateStr[4] + dateStr[5] + "/" +
|
||||
dateStr[2] + dateStr[3] + "/" +
|
||||
dateStr[0] + dateStr[1] + " " +
|
||||
dateStr[6] + dateStr[7] + ":" +
|
||||
if (dateStr.length === 13) { // UTC Time
|
||||
dateStr = (dateStr[0] < "5" ? "20" : "19") + dateStr;
|
||||
}
|
||||
return dateStr[6] + dateStr[7] + "/" +
|
||||
dateStr[4] + dateStr[5] + "/" +
|
||||
dateStr[0] + dateStr[1] + dateStr[2] + dateStr[3] + " " +
|
||||
dateStr[8] + dateStr[9] + ":" +
|
||||
dateStr[10] + dateStr[11];
|
||||
dateStr[10] + dateStr[11] + ":" +
|
||||
dateStr[12] + dateStr[13];
|
||||
}
|
||||
|
||||
export default ParseX509Certificate;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { fromHex } from "../lib/Hex";
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import Utils from "../Utils";
|
||||
import Magic from "../lib/Magic";
|
||||
import { isType, detectFileType } from "../lib/FileType";
|
||||
|
||||
/**
|
||||
* PlayMedia operation
|
||||
@@ -66,8 +66,7 @@ class PlayMedia extends Operation {
|
||||
|
||||
|
||||
// Determine file type
|
||||
const type = Magic.magicFileType(input);
|
||||
if (!(type && /^audio|video/.test(type.mime))) {
|
||||
if (!isType(/^(audio|video)/, input)) {
|
||||
throw new OperationError("Invalid or unrecognised file type");
|
||||
}
|
||||
|
||||
@@ -84,15 +83,15 @@ class PlayMedia extends Operation {
|
||||
async present(data) {
|
||||
if (!data.length) return "";
|
||||
|
||||
const type = Magic.magicFileType(data);
|
||||
const matches = /^audio|video/.exec(type.mime);
|
||||
const types = detectFileType(data);
|
||||
const matches = /^audio|video/.exec(types[0].mime);
|
||||
if (!matches) {
|
||||
throw new OperationError("Invalid file type");
|
||||
}
|
||||
const dataURI = `data:${type.mime};base64,${toBase64(data)}`;
|
||||
const dataURI = `data:${types[0].mime};base64,${toBase64(data)}`;
|
||||
const element = matches[0];
|
||||
|
||||
let html = `<${element} src='${dataURI}' type='${type.mime}' controls>`;
|
||||
let html = `<${element} src='${dataURI}' type='${types[0].mime}' controls>`;
|
||||
html += "<p>Unsupported media type.</p>";
|
||||
html += `</${element}>`;
|
||||
return html;
|
||||
|
||||
@@ -228,40 +228,29 @@ function regexList (input, regex, displayTotal, matches, captureGroups) {
|
||||
function regexHighlight (input, regex, displayTotal) {
|
||||
let output = "",
|
||||
title = "",
|
||||
m,
|
||||
hl = 1,
|
||||
i = 0,
|
||||
total = 0;
|
||||
|
||||
while ((m = regex.exec(input))) {
|
||||
// Moves pointer when an empty string is matched (prevents infinite loop)
|
||||
if (m.index === regex.lastIndex) {
|
||||
regex.lastIndex++;
|
||||
}
|
||||
output = input.replace(regex, (match, ...args) => {
|
||||
args.pop(); // Throw away full string
|
||||
const offset = args.pop(),
|
||||
groups = args;
|
||||
|
||||
// Add up to match
|
||||
output += Utils.escapeHtml(input.slice(i, m.index));
|
||||
|
||||
title = `Offset: ${m.index}\n`;
|
||||
if (m.length > 1) {
|
||||
title = `Offset: ${offset}\n`;
|
||||
if (groups.length) {
|
||||
title += "Groups:\n";
|
||||
for (let n = 1; n < m.length; ++n) {
|
||||
title += `\t${n}: ${m[n]}\n`;
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
title += `\t${i+1}: ${Utils.escapeHtml(groups[i] || "")}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add match with highlighting
|
||||
output += "<span class='hl"+hl+"' title='"+title+"'>" + Utils.escapeHtml(m[0]) + "</span>";
|
||||
|
||||
// Switch highlight
|
||||
hl = hl === 1 ? 2 : 1;
|
||||
|
||||
i = regex.lastIndex;
|
||||
total++;
|
||||
}
|
||||
|
||||
// Add all after final match
|
||||
output += Utils.escapeHtml(input.slice(i, input.length));
|
||||
return `<span class='hl${hl}' title='${title}'>${Utils.escapeHtml(match)}</span>`;
|
||||
});
|
||||
|
||||
if (displayTotal)
|
||||
output = "Total found: " + total + "\n\n" + output;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { fromHex } from "../lib/Hex";
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import Utils from "../Utils";
|
||||
import Magic from "../lib/Magic";
|
||||
import {isImage} from "../lib/FileType";
|
||||
|
||||
/**
|
||||
* Render Image operation
|
||||
@@ -72,8 +72,7 @@ class RenderImage extends Operation {
|
||||
}
|
||||
|
||||
// Determine file type
|
||||
const type = Magic.magicFileType(input);
|
||||
if (!(type && type.mime.indexOf("image") === 0)) {
|
||||
if (!isImage(input)) {
|
||||
throw new OperationError("Invalid file type");
|
||||
}
|
||||
|
||||
@@ -92,9 +91,9 @@ class RenderImage extends Operation {
|
||||
let dataURI = "data:";
|
||||
|
||||
// Determine file type
|
||||
const type = Magic.magicFileType(data);
|
||||
if (type && type.mime.indexOf("image") === 0) {
|
||||
dataURI += type.mime + ";";
|
||||
const mime = isImage(data);
|
||||
if (mime) {
|
||||
dataURI += mime + ";";
|
||||
} else {
|
||||
throw new OperationError("Invalid file type");
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import Magic from "../lib/Magic";
|
||||
import {scanForFileTypes} from "../lib/FileType";
|
||||
import {FILE_SIGNATURES} from "../lib/FileSignatures";
|
||||
|
||||
/**
|
||||
* Scan for Embedded Files operation
|
||||
@@ -25,13 +26,13 @@ class ScanForEmbeddedFiles extends Operation {
|
||||
this.infoURL = "https://wikipedia.org/wiki/List_of_file_signatures";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Ignore common byte sequences",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
}
|
||||
];
|
||||
this.args = Object.keys(FILE_SIGNATURES).map(cat => {
|
||||
return {
|
||||
name: cat,
|
||||
type: "boolean",
|
||||
value: cat === "Miscellaneous" ? false : true
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,43 +42,33 @@ class ScanForEmbeddedFiles extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
let output = "Scanning data for 'magic bytes' which may indicate embedded files. The following results may be false positives and should not be treat as reliable. Any suffiently long file is likely to contain these magic bytes coincidentally.\n",
|
||||
type,
|
||||
numFound = 0,
|
||||
numCommonFound = 0;
|
||||
const ignoreCommon = args[0],
|
||||
commonExts = ["ico", "ttf", ""],
|
||||
numFound = 0;
|
||||
const categories = [],
|
||||
data = new Uint8Array(input);
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
type = Magic.magicFileType(data.slice(i));
|
||||
if (type) {
|
||||
if (ignoreCommon && commonExts.indexOf(type.ext) > -1) {
|
||||
numCommonFound++;
|
||||
continue;
|
||||
}
|
||||
numFound++;
|
||||
output += "\nOffset " + i + " (0x" + Utils.hex(i) + "):\n" +
|
||||
" File extension: " + type.ext + "\n" +
|
||||
" MIME type: " + type.mime + "\n";
|
||||
args.forEach((cat, i) => {
|
||||
if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]);
|
||||
});
|
||||
|
||||
if (type.desc && type.desc.length) {
|
||||
output += " Description: " + type.desc + "\n";
|
||||
const types = scanForFileTypes(data, categories);
|
||||
|
||||
if (types.length) {
|
||||
types.forEach(type => {
|
||||
numFound++;
|
||||
output += "\nOffset " + type.offset + " (0x" + Utils.hex(type.offset) + "):\n" +
|
||||
" File extension: " + type.fileDetails.extension + "\n" +
|
||||
" MIME type: " + type.fileDetails.mime + "\n";
|
||||
|
||||
if (type.fileDetails.description && type.fileDetails.description.length) {
|
||||
output += " Description: " + type.fileDetails.description + "\n";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (numFound === 0) {
|
||||
output += "\nNo embedded files were found.";
|
||||
}
|
||||
|
||||
if (numCommonFound > 0) {
|
||||
output += "\n\n" + numCommonFound;
|
||||
output += numCommonFound === 1 ?
|
||||
" file type was detected that has a common byte sequence. This is likely to be a false positive." :
|
||||
" file types were detected that have common byte sequences. These are likely to be false positives.";
|
||||
output += " Run this operation with the 'Ignore common byte sequences' option unchecked to see details.";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ class Split extends Operation {
|
||||
this.args = [
|
||||
{
|
||||
"name": "Split delimiter",
|
||||
"type": "editableOption",
|
||||
"type": "editableOptionShort",
|
||||
"value": SPLIT_DELIM_OPTIONS
|
||||
},
|
||||
{
|
||||
"name": "Join delimiter",
|
||||
"type": "editableOption",
|
||||
"type": "editableOptionShort",
|
||||
"value": JOIN_DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
|
||||
102
src/core/operations/SplitColourChannels.mjs
Normal file
102
src/core/operations/SplitColourChannels.mjs
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import Utils from "../Utils";
|
||||
import {isImage} from "../lib/FileType";
|
||||
|
||||
import jimp from "jimp";
|
||||
|
||||
/**
|
||||
* Split Colour Channels operation
|
||||
*/
|
||||
class SplitColourChannels extends Operation {
|
||||
|
||||
/**
|
||||
* SplitColourChannels constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Split Colour Channels";
|
||||
this.module = "Image";
|
||||
this.description = "Splits the given image into its red, green and blue colour channels.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Channel_(digital_image)";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "List<File>";
|
||||
this.presentType = "html";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {List<File>}
|
||||
*/
|
||||
async run(input, args) {
|
||||
// Make sure that the input is an image
|
||||
if (!isImage(input)) throw new OperationError("Invalid file type.");
|
||||
|
||||
const parsedImage = await jimp.read(Buffer.from(input));
|
||||
|
||||
const red = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const split = parsedImage
|
||||
.clone()
|
||||
.color([
|
||||
{apply: "blue", params: [-255]},
|
||||
{apply: "green", params: [-255]}
|
||||
])
|
||||
.getBufferAsync(jimp.MIME_PNG);
|
||||
resolve(new File([new Uint8Array((await split).values())], "red.png", {type: "image/png"}));
|
||||
} catch (err) {
|
||||
reject(new OperationError(`Could not split red channel: ${err}`));
|
||||
}
|
||||
});
|
||||
|
||||
const green = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const split = parsedImage.clone()
|
||||
.color([
|
||||
{apply: "red", params: [-255]},
|
||||
{apply: "blue", params: [-255]},
|
||||
]).getBufferAsync(jimp.MIME_PNG);
|
||||
resolve(new File([new Uint8Array((await split).values())], "green.png", {type: "image/png"}));
|
||||
} catch (err) {
|
||||
reject(new OperationError(`Could not split green channel: ${err}`));
|
||||
}
|
||||
});
|
||||
|
||||
const blue = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const split = parsedImage
|
||||
.color([
|
||||
{apply: "red", params: [-255]},
|
||||
{apply: "green", params: [-255]},
|
||||
]).getBufferAsync(jimp.MIME_PNG);
|
||||
resolve(new File([new Uint8Array((await split).values())], "blue.png", {type: "image/png"}));
|
||||
} catch (err) {
|
||||
reject(new OperationError(`Could not split blue channel: ${err}`));
|
||||
}
|
||||
});
|
||||
|
||||
return await Promise.all([red, green, blue]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the files in HTML for web apps.
|
||||
*
|
||||
* @param {File[]} files
|
||||
* @returns {html}
|
||||
*/
|
||||
async present(files) {
|
||||
return await Utils.displayFilesAsHTML(files);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default SplitColourChannels;
|
||||
@@ -44,7 +44,7 @@ class StandardDeviation extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const val = stdDev(createNumArray(input, args[0]));
|
||||
return val instanceof BigNumber ? val : new BigNumber(NaN);
|
||||
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
|
||||
|
||||
}
|
||||
|
||||
|
||||
153
src/core/operations/Subsection.mjs
Normal file
153
src/core/operations/Subsection.mjs
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import XRegExp from "xregexp";
|
||||
import Operation from "../Operation";
|
||||
import Recipe from "../Recipe";
|
||||
import Dish from "../Dish";
|
||||
|
||||
/**
|
||||
* Subsection operation
|
||||
*/
|
||||
class Subsection extends Operation {
|
||||
|
||||
/**
|
||||
* Subsection constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Subsection";
|
||||
this.flowControl = true;
|
||||
this.module = "Regex";
|
||||
this.description = "Select a part of the input data using a regular expression (regex), and run all subsequent operations on each match separately.<br><br>You can use up to one capture group, where the recipe will only be run on the data in the capture group. If there's more than one capture group, only the first one will be operated on.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Section (regex)",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "Case sensitive matching",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "Global matching",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "Ignore errors",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe
|
||||
* @returns {Object} - The updated state of the recipe
|
||||
*/
|
||||
async run(state) {
|
||||
const opList = state.opList,
|
||||
inputType = opList[state.progress].inputType,
|
||||
outputType = opList[state.progress].outputType,
|
||||
input = await state.dish.get(inputType),
|
||||
ings = opList[state.progress].ingValues,
|
||||
[section, caseSensitive, global, ignoreErrors] = ings,
|
||||
subOpList = [];
|
||||
|
||||
if (input && section !== "") {
|
||||
// Create subOpList for each tranche to operate on
|
||||
// all remaining operations unless we encounter a Merge
|
||||
for (let i = state.progress + 1; i < opList.length; i++) {
|
||||
if (opList[i].name === "Merge" && !opList[i].disabled) {
|
||||
break;
|
||||
} else {
|
||||
subOpList.push(opList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
let flags = "",
|
||||
inOffset = 0,
|
||||
output = "",
|
||||
m,
|
||||
progress = 0;
|
||||
|
||||
if (!caseSensitive) flags += "i";
|
||||
if (global) flags += "g";
|
||||
|
||||
const regex = new XRegExp(section, flags),
|
||||
recipe = new Recipe();
|
||||
|
||||
recipe.addOperations(subOpList);
|
||||
state.forkOffset += state.progress + 1;
|
||||
|
||||
// Take a deep(ish) copy of the ingredient values
|
||||
const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues)));
|
||||
let matched = false;
|
||||
|
||||
// Run recipe over each match
|
||||
while ((m = regex.exec(input))) {
|
||||
matched = true;
|
||||
// Add up to match
|
||||
let matchStr = m[0];
|
||||
|
||||
if (m.length === 1) { // No capture groups
|
||||
output += input.slice(inOffset, m.index);
|
||||
inOffset = m.index + m[0].length;
|
||||
} else if (m.length >= 2) {
|
||||
matchStr = m[1];
|
||||
|
||||
// Need to add some of the matched string that isn't in the capture group
|
||||
output += input.slice(inOffset, m.index + m[0].indexOf(m[1]));
|
||||
// Set i to be after the end of the first capture group
|
||||
inOffset = m.index + m[0].indexOf(m[1]) + m[1].length;
|
||||
}
|
||||
|
||||
// Baseline ing values for each tranche so that registers are reset
|
||||
recipe.opList.forEach((op, i) => {
|
||||
op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));
|
||||
});
|
||||
|
||||
const dish = new Dish();
|
||||
dish.set(matchStr, inputType);
|
||||
|
||||
try {
|
||||
progress = await recipe.execute(dish, 0, state);
|
||||
} catch (err) {
|
||||
if (!ignoreErrors) {
|
||||
throw err;
|
||||
}
|
||||
progress = err.progress + 1;
|
||||
}
|
||||
output += await dish.get(outputType);
|
||||
if (!regex.global) break;
|
||||
}
|
||||
|
||||
// If no matches were found, advance progress to after a Merge op
|
||||
// Otherwise, the operations below Subsection will be run on all the input data
|
||||
if (!matched) {
|
||||
state.progress += subOpList.length + 1;
|
||||
}
|
||||
|
||||
output += input.slice(inOffset);
|
||||
state.progress += progress;
|
||||
state.dish.set(output, outputType);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Subsection;
|
||||
@@ -44,7 +44,7 @@ class Subtract extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const val = sub(createNumArray(input, args[0]));
|
||||
return val instanceof BigNumber ? val : new BigNumber(NaN);
|
||||
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class Sum extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const val = sum(createNumArray(input, args[0]));
|
||||
return val instanceof BigNumber ? val : new BigNumber(NaN);
|
||||
return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class ToBase64 extends Operation {
|
||||
|
||||
this.name = "To Base64";
|
||||
this.module = "Default";
|
||||
this.description = "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.<br><br>This operation decodes data from an ASCII Base64 string back into its raw format.<br><br>e.g. <code>aGVsbG8=</code> becomes <code>hello</code>";
|
||||
this.description = "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.<br><br>This operation encodes raw data into an ASCII Base64 string.<br><br>e.g. <code>hello</code> becomes <code>aGVsbG8=</code>";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Base64";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
|
||||
39
src/core/operations/ToCaseInsensitiveRegex.mjs
Normal file
39
src/core/operations/ToCaseInsensitiveRegex.mjs
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @author masq [github.cyberchef@masq.cc]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
|
||||
/**
|
||||
* To Case Insensitive Regex operation
|
||||
*/
|
||||
class ToCaseInsensitiveRegex extends Operation {
|
||||
|
||||
/**
|
||||
* ToCaseInsensitiveRegex constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "To Case Insensitive Regex";
|
||||
this.module = "Default";
|
||||
this.description = "Converts a case-sensitive regex string into a case-insensitive regex string in case the i flag is unavailable to you.<br><br>e.g. <code>Mozilla/[0-9].[0-9] .*</code> becomes <code>[mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .*</code>";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Regular_expression";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return input.replace(/[a-z]/ig, m => `[${m.toLowerCase()}${m.toUpperCase()}]`);
|
||||
}
|
||||
}
|
||||
|
||||
export default ToCaseInsensitiveRegex;
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* @author gchq77703 []
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import geohash from "ngeohash";
|
||||
|
||||
/**
|
||||
* To Geohash operation
|
||||
*/
|
||||
class ToGeohash extends Operation {
|
||||
|
||||
/**
|
||||
* ToGeohash constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "To Geohash";
|
||||
this.module = "Crypto";
|
||||
this.description = "Converts Lat/Long coordinates into a Geohash string. For example, <code>37.8324,112.5584</code> becomes <code>ww8p1r4t8</code>.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Geohash";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Precision",
|
||||
type: "number",
|
||||
value: 9
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [precision] = args;
|
||||
|
||||
return input.split("\n").map(line => {
|
||||
line = line.replace(/ /g, "");
|
||||
if (line === "") return "";
|
||||
return geohash.encode(...line.split(",").map(num => parseFloat(num)), precision);
|
||||
}).join("\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ToGeohash;
|
||||
@@ -38,7 +38,9 @@ class ToMessagePack extends Operation {
|
||||
if (ENVIRONMENT_IS_WORKER()) {
|
||||
return notepack.encode(input);
|
||||
} else {
|
||||
return notepack.encode(input).buffer;
|
||||
const res = notepack.encode(input);
|
||||
// Safely convert from Node Buffer to ArrayBuffer using the correct view of the data
|
||||
return (new Uint8Array(res)).buffer;
|
||||
}
|
||||
} catch (err) {
|
||||
throw new OperationError(`Could not encode JSON to MessagePack: ${err}`);
|
||||
|
||||
@@ -57,7 +57,7 @@ class ToTable extends Operation {
|
||||
const [cellDelims, rowDelims, firstRowHeader, format] = args;
|
||||
|
||||
// Process the input into a nested array of elements.
|
||||
const tableData = Utils.parseCSV(input, cellDelims.split(""), rowDelims.split(""));
|
||||
const tableData = Utils.parseCSV(Utils.escapeHtml(input), cellDelims.split(""), rowDelims.split(""));
|
||||
|
||||
if (!tableData.length) return "";
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import Stream from "../lib/Stream";
|
||||
|
||||
/**
|
||||
* Untar operation
|
||||
@@ -41,38 +42,6 @@ class Untar extends Operation {
|
||||
* @returns {List<File>}
|
||||
*/
|
||||
run(input, args) {
|
||||
const Stream = function(input) {
|
||||
this.bytes = input;
|
||||
this.position = 0;
|
||||
};
|
||||
|
||||
Stream.prototype.getBytes = function(bytesToGet) {
|
||||
const newPosition = this.position + bytesToGet;
|
||||
const bytes = this.bytes.slice(this.position, newPosition);
|
||||
this.position = newPosition;
|
||||
return bytes;
|
||||
};
|
||||
|
||||
Stream.prototype.readString = function(numBytes) {
|
||||
let result = "";
|
||||
for (let i = this.position; i < this.position + numBytes; i++) {
|
||||
const currentByte = this.bytes[i];
|
||||
if (currentByte === 0) break;
|
||||
result += String.fromCharCode(currentByte);
|
||||
}
|
||||
this.position += numBytes;
|
||||
return result;
|
||||
};
|
||||
|
||||
Stream.prototype.readInt = function(numBytes, base) {
|
||||
const string = this.readString(numBytes);
|
||||
return parseInt(string, base);
|
||||
};
|
||||
|
||||
Stream.prototype.hasMore = function() {
|
||||
return this.position < this.bytes.length;
|
||||
};
|
||||
|
||||
const stream = new Stream(input),
|
||||
files = [];
|
||||
|
||||
@@ -85,7 +54,7 @@ class Untar extends Operation {
|
||||
ownerUID: stream.readString(8),
|
||||
ownerGID: stream.readString(8),
|
||||
size: parseInt(stream.readString(12), 8), // Octal
|
||||
lastModTime: new Date(1000 * stream.readInt(12, 8)), // Octal
|
||||
lastModTime: new Date(1000 * parseInt(stream.readString(12), 8)), // Octal
|
||||
checksum: stream.readString(8),
|
||||
type: stream.readString(1),
|
||||
linkedFileName: stream.readString(100),
|
||||
|
||||
122
src/core/operations/YARARules.mjs
Normal file
122
src/core/operations/YARARules.mjs
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import Yara from "libyara-wasm";
|
||||
|
||||
/**
|
||||
* YARA Rules operation
|
||||
*/
|
||||
class YARARules extends Operation {
|
||||
|
||||
/**
|
||||
* YARARules constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "YARA Rules";
|
||||
this.module = "Yara";
|
||||
this.description = "YARA is a tool developed at VirusTotal, primarily aimed at helping malware researchers to identify and classify malware samples. It matches based on rules specified by the user containing textual or binary patterns and a boolean expression. For help on writing rules, see the <a href='https://yara.readthedocs.io/en/latest/writingrules.html'>YARA documentation.</a>";
|
||||
this.infoURL = "https://wikipedia.org/wiki/YARA";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Rules",
|
||||
type: "text",
|
||||
value: "",
|
||||
rows: 5
|
||||
},
|
||||
{
|
||||
name: "Show strings",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Show string lengths",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Show metadata",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Show counts",
|
||||
type: "boolean",
|
||||
value: true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage("Instantiating YARA...");
|
||||
const [rules, showStrings, showLengths, showMeta, showCounts] = args;
|
||||
return new Promise((resolve, reject) => {
|
||||
Yara().then(yara => {
|
||||
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Converting data for YARA.");
|
||||
let matchString = "";
|
||||
|
||||
const inpArr = new Uint8Array(input); // Turns out embind knows that JS uint8array <==> C++ std::string
|
||||
|
||||
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Running YARA matching.");
|
||||
|
||||
const resp = yara.run(inpArr, rules);
|
||||
|
||||
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Processing data.");
|
||||
|
||||
if (resp.compileErrors.size() > 0) {
|
||||
for (let i = 0; i < resp.compileErrors.size(); i++) {
|
||||
const compileError = resp.compileErrors.get(i);
|
||||
if (!compileError.warning) {
|
||||
reject(new OperationError(`Error on line ${compileError.lineNumber}: ${compileError.message}`));
|
||||
} else {
|
||||
matchString += `Warning on line ${compileError.lineNumber}: ${compileError.message}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
const matchedRules = resp.matchedRules;
|
||||
for (let i = 0; i < matchedRules.size(); i++) {
|
||||
const rule = matchedRules.get(i);
|
||||
const matches = rule.resolvedMatches;
|
||||
let meta = "";
|
||||
if (showMeta && rule.metadata.size() > 0) {
|
||||
meta += " [";
|
||||
for (let j = 0; j < rule.metadata.size(); j++) {
|
||||
meta += `${rule.metadata.get(j).identifier}: ${rule.metadata.get(j).data}, `;
|
||||
}
|
||||
meta = meta.slice(0, -2) + "]";
|
||||
}
|
||||
const countString = showCounts ? `${matches.size()} time${matches.size() > 1 ? "s" : ""}` : "";
|
||||
if (matches.size() === 0 || !(showStrings || showLengths)) {
|
||||
matchString += `Input matches rule "${rule.ruleName}"${meta}${countString.length > 0 ? ` ${countString}`: ""}.\n`;
|
||||
} else {
|
||||
matchString += `Rule "${rule.ruleName}"${meta} matches (${countString}):\n`;
|
||||
for (let j = 0; j < matches.size(); j++) {
|
||||
const match = matches.get(j);
|
||||
if (showStrings || showLengths) {
|
||||
matchString += `Pos ${match.location}, ${showLengths ? `length ${match.matchLength}, ` : ""}identifier ${match.stringIdentifier}${showStrings ? `, data: "${match.data}"` : ""}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve(matchString);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default YARARules;
|
||||
@@ -238,12 +238,18 @@ class App {
|
||||
|
||||
/**
|
||||
* Sets up the adjustable splitter to allow the user to resize areas of the page.
|
||||
*
|
||||
* @param {boolean} [minimise=false] - Set this flag if attempting to minimuse frames to 0 width
|
||||
*/
|
||||
initialiseSplitter() {
|
||||
initialiseSplitter(minimise=false) {
|
||||
if (this.columnSplitter) this.columnSplitter.destroy();
|
||||
if (this.ioSplitter) this.ioSplitter.destroy();
|
||||
|
||||
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
|
||||
sizes: [20, 30, 50],
|
||||
minSize: [240, 370, 450],
|
||||
minSize: minimise ? [0, 0, 0] : [240, 370, 450],
|
||||
gutterSize: 4,
|
||||
expandToMin: false,
|
||||
onDrag: function() {
|
||||
this.manager.recipe.adjustWidth();
|
||||
}.bind(this)
|
||||
@@ -251,7 +257,8 @@ class App {
|
||||
|
||||
this.ioSplitter = Split(["#input", "#output"], {
|
||||
direction: "vertical",
|
||||
gutterSize: 4
|
||||
gutterSize: 4,
|
||||
minSize: minimise ? [0, 0] : [100, 100]
|
||||
});
|
||||
|
||||
this.resetLayout();
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../core/Utils";
|
||||
|
||||
/**
|
||||
* Object to handle the creation of operation ingredients.
|
||||
*/
|
||||
@@ -25,6 +27,7 @@ class HTMLIngredient {
|
||||
this.value = config.value;
|
||||
this.disabled = config.disabled || false;
|
||||
this.hint = config.hint || false;
|
||||
this.rows = config.rows || false;
|
||||
this.target = config.target;
|
||||
this.defaultIndex = config.defaultIndex || 0;
|
||||
this.toggleValues = config.toggleValues;
|
||||
@@ -155,7 +158,7 @@ class HTMLIngredient {
|
||||
} else if ((m = this.value[i].name.match(/\[\/([a-z0-9 -()^]+)\]/i))) {
|
||||
html += "</optgroup>";
|
||||
} else {
|
||||
html += `<option populate-value="${this.value[i].value}">${this.value[i].name}</option>`;
|
||||
html += `<option populate-value="${Utils.escapeHtml(this.value[i].value)}">${this.value[i].name}</option>`;
|
||||
}
|
||||
}
|
||||
html += `</select>
|
||||
@@ -165,6 +168,35 @@ class HTMLIngredient {
|
||||
this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this);
|
||||
break;
|
||||
case "editableOption":
|
||||
html += `<div class="form-group input-group">
|
||||
<label for="${this.id}" class="bmd-label-floating">${this.name}</label>
|
||||
<input type="text"
|
||||
class="form-control arg"
|
||||
id="${this.id}"
|
||||
arg-name="${this.name}"
|
||||
value="${this.value[this.defaultIndex].value}"
|
||||
${this.disabled ? "disabled" : ""}>
|
||||
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
|
||||
<div class="input-group-append">
|
||||
<button type="button"
|
||||
class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
|
||||
data-toggle="dropdown"
|
||||
data-boundary="scrollParent"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu editable-option-menu">`;
|
||||
for (i = 0; i < this.value.length; i++) {
|
||||
html += `<a class="dropdown-item" href="#" value="${this.value[i].value}">${this.value[i].name}</a>`;
|
||||
}
|
||||
html += `</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
|
||||
break;
|
||||
case "editableOptionShort":
|
||||
html += `<div class="form-group input-group inline">
|
||||
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
|
||||
<input type="text"
|
||||
@@ -200,6 +232,7 @@ class HTMLIngredient {
|
||||
class="form-control arg"
|
||||
id="${this.id}"
|
||||
arg-name="${this.name}"
|
||||
rows="${this.rows ? this.rows : 3}"
|
||||
${this.disabled ? "disabled" : ""}>${this.value}</textarea>
|
||||
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
|
||||
</div>`;
|
||||
|
||||
@@ -243,14 +243,22 @@ class InputWaiter {
|
||||
}
|
||||
|
||||
if (file) {
|
||||
this.closeFile();
|
||||
this.loaderWorker = new LoaderWorker();
|
||||
this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this));
|
||||
this.loaderWorker.postMessage({"file": file});
|
||||
this.set(file);
|
||||
this.loadFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for open input button events
|
||||
* Loads the opened data into the input textarea
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
inputOpen(e) {
|
||||
e.preventDefault();
|
||||
const file = e.srcElement.files[0];
|
||||
this.loadFile(file);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for messages sent back by the LoaderWorker.
|
||||
@@ -306,6 +314,22 @@ class InputWaiter {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a file into the input.
|
||||
*
|
||||
* @param {File} file
|
||||
*/
|
||||
loadFile(file) {
|
||||
if (file) {
|
||||
this.closeFile();
|
||||
this.loaderWorker = new LoaderWorker();
|
||||
this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this));
|
||||
this.loaderWorker.postMessage({"file": file});
|
||||
this.set(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for clear IO events.
|
||||
* Resets the input, output and info areas.
|
||||
|
||||
@@ -137,12 +137,16 @@ class Manager {
|
||||
this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe);
|
||||
this.addDynamicListener("#rec-list .dropdown-menu.toggle-dropdown a", "click", this.recipe.dropdownToggleClick, this.recipe);
|
||||
this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe));
|
||||
this.addDynamicListener("textarea.arg", "dragover", this.recipe.textArgDragover, this.recipe);
|
||||
this.addDynamicListener("textarea.arg", "dragleave", this.recipe.textArgDragLeave, this.recipe);
|
||||
this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe);
|
||||
|
||||
// Input
|
||||
this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input);
|
||||
this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input);
|
||||
document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
|
||||
document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input));
|
||||
this.addListeners("#open-file", "change", this.input.inputOpen, this.input);
|
||||
this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input);
|
||||
this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input);
|
||||
this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input);
|
||||
@@ -169,6 +173,7 @@ class Manager {
|
||||
this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output);
|
||||
this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output);
|
||||
document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output));
|
||||
this.addDynamicListener(".extract-file,.extract-file i", "click", this.output.extractFileClick, this.output);
|
||||
|
||||
// Options
|
||||
document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options));
|
||||
|
||||
@@ -319,6 +319,7 @@ class OutputWaiter {
|
||||
const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
|
||||
|
||||
if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) {
|
||||
this.app.initialiseSplitter(true);
|
||||
this.app.columnSplitter.collapse(0);
|
||||
this.app.columnSplitter.collapse(1);
|
||||
this.app.ioSplitter.collapse(0);
|
||||
@@ -328,6 +329,7 @@ class OutputWaiter {
|
||||
} else {
|
||||
$(el).attr("data-original-title", "Maximise output pane");
|
||||
el.querySelector("i").innerHTML = "fullscreen";
|
||||
this.app.initialiseSplitter(false);
|
||||
this.app.resetLayout();
|
||||
}
|
||||
}
|
||||
@@ -476,7 +478,7 @@ class OutputWaiter {
|
||||
*/
|
||||
showMagicButton(opSequence, result, recipeConfig) {
|
||||
const magicButton = document.getElementById("magic");
|
||||
magicButton.setAttribute("data-original-title", `<i>${opSequence}</i> will produce <span class="data-text">"${Utils.truncate(result, 30)}"</span>`);
|
||||
magicButton.setAttribute("data-original-title", `<i>${opSequence}</i> will produce <span class="data-text">"${Utils.escapeHtml(Utils.truncate(result), 30)}"</span>`);
|
||||
magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, "");
|
||||
magicButton.classList.remove("hidden");
|
||||
}
|
||||
@@ -492,6 +494,24 @@ class OutputWaiter {
|
||||
magicButton.setAttribute("data-original-title", "Magic!");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for extract file events.
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
async extractFileClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const el = e.target.nodeName === "I" ? e.target.parentNode : e.target;
|
||||
const blobURL = el.getAttribute("blob-url");
|
||||
const fileName = el.getAttribute("file-name");
|
||||
|
||||
const blob = await fetch(blobURL).then(r => r.blob());
|
||||
this.manager.input.loadFile(new File([blob], fileName, {type: blob.type}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default OutputWaiter;
|
||||
|
||||
@@ -376,6 +376,7 @@ class RecipeWaiter {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the specified operation to the recipe.
|
||||
*
|
||||
@@ -454,6 +455,75 @@ class RecipeWaiter {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for text argument dragover events.
|
||||
* Gives the user a visual cue to show that items can be dropped here.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
textArgDragover (e) {
|
||||
// This will be set if we're dragging an operation
|
||||
if (e.dataTransfer.effectAllowed === "move")
|
||||
return false;
|
||||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.target.closest("textarea.arg").classList.add("dropping-file");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for text argument dragleave events.
|
||||
* Removes the visual cue.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
textArgDragLeave (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.target.classList.remove("dropping-file");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for text argument drop events.
|
||||
* Loads the dragged data into the argument textarea.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
textArgDrop(e) {
|
||||
// This will be set if we're dragging an operation
|
||||
if (e.dataTransfer.effectAllowed === "move")
|
||||
return false;
|
||||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const targ = e.target;
|
||||
const file = e.dataTransfer.files[0];
|
||||
const text = e.dataTransfer.getData("Text");
|
||||
|
||||
targ.classList.remove("dropping-file");
|
||||
|
||||
if (text) {
|
||||
targ.value = text;
|
||||
return;
|
||||
}
|
||||
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
const self = this;
|
||||
reader.onload = function (e) {
|
||||
targ.value = e.target.result;
|
||||
// Trigger floating label move
|
||||
const changeEvent = new Event("change");
|
||||
targ.dispatchEvent(changeEvent);
|
||||
window.dispatchEvent(self.manager.statechange);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets register values.
|
||||
*
|
||||
@@ -479,6 +549,7 @@ class RecipeWaiter {
|
||||
op.insertAdjacentHTML("beforeend", registerListEl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adjusts the number of ingredient columns as the width of the recipe changes.
|
||||
*/
|
||||
@@ -490,20 +561,25 @@ class RecipeWaiter {
|
||||
this.ingredientChildRuleID = null;
|
||||
|
||||
// Find relevant rules in the stylesheet
|
||||
for (const i in document.styleSheets[0].cssRules) {
|
||||
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") {
|
||||
this.ingredientRuleID = i;
|
||||
}
|
||||
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") {
|
||||
this.ingredientChildRuleID = i;
|
||||
// try/catch for chrome 64+ CORS error on cssRules.
|
||||
try {
|
||||
for (const i in document.styleSheets[0].cssRules) {
|
||||
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") {
|
||||
this.ingredientRuleID = i;
|
||||
}
|
||||
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") {
|
||||
this.ingredientChildRuleID = i;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.ingredientRuleID || !this.ingredientChildRuleID) return;
|
||||
|
||||
const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID],
|
||||
ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID];
|
||||
const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID];
|
||||
const ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID];
|
||||
|
||||
if (recList.clientWidth < 450) {
|
||||
ingredientRule.style.gridTemplateColumns = "auto auto";
|
||||
|
||||
@@ -225,6 +225,10 @@
|
||||
<div class="title no-select">
|
||||
<label for="input-text">Input</label>
|
||||
<span class="float-right">
|
||||
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-open-file" data-toggle="tooltip" title="Open file as input" onclick="document.getElementById('open-file').click();">
|
||||
<i class="material-icons">input</i>
|
||||
<input type="file" id="open-file" style="display: none">
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary bmd-btn-icon" id="clr-io" data-toggle="tooltip" title="Clear input and output">
|
||||
<i class="material-icons">delete</i>
|
||||
</button>
|
||||
@@ -267,7 +271,7 @@
|
||||
<i class="material-icons">content_copy</i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary bmd-btn-icon" id="switch" data-toggle="tooltip" title="Move output to input">
|
||||
<i class="material-icons">loop</i>
|
||||
<i class="material-icons">open_in_browser</i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary bmd-btn-icon" id="undo-switch" data-toggle="tooltip" title="Undo" disabled="disabled">
|
||||
<i class="material-icons">undo</i>
|
||||
|
||||
@@ -91,3 +91,7 @@
|
||||
padding-right: 6px;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
#files .card-header .float-right a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
width: 100%;
|
||||
height: var(--controls-height);
|
||||
bottom: 0;
|
||||
padding: 10px;
|
||||
padding: 0;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--primary-border-colour);
|
||||
background-color: var(--secondary-background-colour);
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
|
||||
.dropping-file {
|
||||
border: 5px dashed var(--drop-file-border-colour) !important;
|
||||
margin: -5px;
|
||||
}
|
||||
|
||||
#stale-indicator {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* To Geohash tests
|
||||
*
|
||||
* @author gchq77703
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "From Geohash",
|
||||
input: "ww8p1r4t8",
|
||||
expectedOutput: "37.83238649368286,112.55838632583618",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Geohash",
|
||||
args: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "From Geohash",
|
||||
input: "ww8p1r",
|
||||
expectedOutput: "37.83416748046875,112.5604248046875",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Geohash",
|
||||
args: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "From Geohash",
|
||||
input: "ww8",
|
||||
expectedOutput: "37.265625,113.203125",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Geohash",
|
||||
args: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "From Geohash",
|
||||
input: "w",
|
||||
expectedOutput: "22.5,112.5",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Geohash",
|
||||
args: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* To Geohash tests
|
||||
*
|
||||
* @author gchq77703
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "To Geohash",
|
||||
input: "37.8324,112.5584",
|
||||
expectedOutput: "ww8p1r4t8",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "To Geohash",
|
||||
args: [9],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "To Geohash",
|
||||
input: "37.9324,-112.2584",
|
||||
expectedOutput: "9w8pv3ruj",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "To Geohash",
|
||||
args: [9],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "To Geohash",
|
||||
input: "37.8324,112.5584",
|
||||
expectedOutput: "ww8",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "To Geohash",
|
||||
args: [3],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "To Geohash",
|
||||
input: "37.9324,-112.2584",
|
||||
expectedOutput: "9w8pv3rujxy5b99",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "To Geohash",
|
||||
args: [15],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
182
tests/browser/nightwatch.js
Normal file
182
tests/browser/nightwatch.js
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Tests to ensure that the app loads correctly in a reasonable time and that operations can be run.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
module.exports = {
|
||||
before: browser => {
|
||||
browser
|
||||
.resizeWindow(1280, 800)
|
||||
.url(browser.launchUrl);
|
||||
},
|
||||
|
||||
"Loading screen": browser => {
|
||||
// Check that the loading screen appears and then disappears within a reasonable time
|
||||
browser
|
||||
.waitForElementVisible("#preloader", 300)
|
||||
.waitForElementNotPresent("#preloader", 10000);
|
||||
},
|
||||
|
||||
"App loaded": browser => {
|
||||
browser.useCss();
|
||||
// Check that various important elements are loaded
|
||||
browser.expect.element("#operations").to.be.visible;
|
||||
browser.expect.element("#recipe").to.be.visible;
|
||||
browser.expect.element("#input").to.be.present;
|
||||
browser.expect.element("#output").to.be.present;
|
||||
browser.expect.element(".op-list").to.be.present;
|
||||
browser.expect.element("#rec-list").to.be.visible;
|
||||
browser.expect.element("#controls").to.be.visible;
|
||||
browser.expect.element("#input-text").to.be.visible;
|
||||
browser.expect.element("#output-text").to.be.visible;
|
||||
},
|
||||
|
||||
"Operations loaded": browser => {
|
||||
browser.useXpath();
|
||||
// Check that an operation in every category has been populated
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='To Base64']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='To Binary']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='AES Decrypt']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='PEM to Hex']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Power Set']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Parse IP range']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Remove Diacritics']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Sort']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='To UNIX Timestamp']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Extract dates']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Gzip']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Keccak']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='JSON Beautify']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Detect File Type']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Play Media']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Disassemble x86']").to.be.present;
|
||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Register']").to.be.present;
|
||||
},
|
||||
|
||||
"Recipe can be run": browser => {
|
||||
const toHex = "//li[contains(@class, 'operation') and text()='To Hex']";
|
||||
const op = "#rec-list .operation .op-title";
|
||||
|
||||
// Check that operation is visible
|
||||
browser
|
||||
.useXpath()
|
||||
.expect.element(toHex).to.be.visible;
|
||||
|
||||
// Add it to the recipe by double clicking
|
||||
browser
|
||||
.useXpath()
|
||||
.moveToElement(toHex, 10, 10)
|
||||
.useCss()
|
||||
.waitForElementVisible(".popover-body", 1000)
|
||||
.doubleClick();
|
||||
|
||||
// Confirm that it has been added to the recipe
|
||||
browser
|
||||
.useCss()
|
||||
.waitForElementVisible(op)
|
||||
.expect.element(op).text.to.contain("To Hex");
|
||||
|
||||
// Enter input
|
||||
browser
|
||||
.useCss()
|
||||
.setValue("#input-text", "Don't Panic.")
|
||||
.click("#bake");
|
||||
|
||||
// Check output
|
||||
browser
|
||||
.useCss()
|
||||
.waitForElementNotVisible("#stale-indicator", 1000)
|
||||
.expect.element("#output-text").to.have.value.that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e");
|
||||
|
||||
// Clear recipe
|
||||
browser
|
||||
.useCss()
|
||||
.moveToElement(op, 10, 10)
|
||||
.waitForElementNotPresent(".popover-body", 1000)
|
||||
.click("#clr-recipe")
|
||||
.waitForElementNotPresent(op);
|
||||
},
|
||||
|
||||
"Test every module": browser => {
|
||||
browser.useCss();
|
||||
|
||||
// BSON
|
||||
loadOp("BSON deserialise", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// Ciphers
|
||||
loadOp("AES Encrypt", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// Code
|
||||
loadOp("XPath expression", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// Compression
|
||||
loadOp("Gzip", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// Crypto
|
||||
loadOp("MD5", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// Default
|
||||
loadOp("Fork", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// Diff
|
||||
loadOp("Diff", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// Encodings
|
||||
loadOp("Encode text", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// Image
|
||||
loadOp("Extract EXIF", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// PGP
|
||||
loadOp("PGP Encrypt", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// PublicKey
|
||||
loadOp("Hex to PEM", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// Regex
|
||||
loadOp("Strings", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// Shellcode
|
||||
loadOp("Disassemble x86", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// URL
|
||||
loadOp("URL Encode", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
|
||||
// UserAgent
|
||||
loadOp("Parse User Agent", browser)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
},
|
||||
|
||||
after: browser => {
|
||||
browser.end();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the current recipe and loads a new operation.
|
||||
*
|
||||
* @param {string} opName
|
||||
* @param {Browser} browser
|
||||
*/
|
||||
function loadOp(opName, browser) {
|
||||
return browser
|
||||
.useCss()
|
||||
.click("#clr-recipe")
|
||||
.urlHash("op=" + opName);
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import Chef from "../src/core/Chef";
|
||||
import Chef from "../../src/core/Chef";
|
||||
|
||||
(function() {
|
||||
/**
|
||||
@@ -24,64 +24,68 @@ global.ENVIRONMENT_IS_WEB = function() {
|
||||
};
|
||||
|
||||
import TestRegister from "./TestRegister";
|
||||
import "./tests/operations/BCD";
|
||||
import "./tests/operations/BSON";
|
||||
import "./tests/operations/Base58";
|
||||
import "./tests/operations/Base64";
|
||||
import "./tests/operations/Base62";
|
||||
import "./tests/operations/BitwiseOp";
|
||||
import "./tests/operations/ByteRepr";
|
||||
import "./tests/operations/CartesianProduct";
|
||||
import "./tests/operations/CharEnc";
|
||||
import "./tests/operations/Checksum";
|
||||
import "./tests/operations/Ciphers";
|
||||
import "./tests/operations/Code";
|
||||
import "./tests/operations/Comment";
|
||||
import "./tests/operations/Compress";
|
||||
import "./tests/operations/ConditionalJump";
|
||||
import "./tests/operations/Crypt";
|
||||
import "./tests/operations/CSV";
|
||||
import "./tests/operations/DateTime";
|
||||
import "./tests/operations/ExtractEmailAddresses";
|
||||
import "./tests/operations/Fork";
|
||||
import "./tests/operations/FromDecimal";
|
||||
import "./tests/operations/FromGeohash";
|
||||
import "./tests/operations/Hash";
|
||||
import "./tests/operations/HaversineDistance";
|
||||
import "./tests/operations/Hexdump";
|
||||
import "./tests/operations/Image";
|
||||
import "./tests/operations/Jump";
|
||||
import "./tests/operations/JSONBeautify";
|
||||
import "./tests/operations/JSONMinify";
|
||||
import "./tests/operations/JWTDecode";
|
||||
import "./tests/operations/JWTSign";
|
||||
import "./tests/operations/JWTVerify";
|
||||
import "./tests/operations/MS";
|
||||
import "./tests/operations/Magic";
|
||||
import "./tests/operations/MorseCode";
|
||||
import "./tests/operations/NetBIOS";
|
||||
import "./tests/operations/OTP";
|
||||
import "./tests/operations/PGP";
|
||||
import "./tests/operations/PHP";
|
||||
import "./tests/operations/ParseIPRange";
|
||||
import "./tests/operations/ParseQRCode";
|
||||
import "./tests/operations/PowerSet";
|
||||
import "./tests/operations/Regex";
|
||||
import "./tests/operations/Register";
|
||||
import "./tests/operations/RemoveDiacritics";
|
||||
import "./tests/operations/Rotate";
|
||||
import "./tests/operations/SeqUtils";
|
||||
import "./tests/operations/SetDifference";
|
||||
import "./tests/operations/SetIntersection";
|
||||
import "./tests/operations/SetUnion";
|
||||
import "./tests/operations/StrUtils";
|
||||
import "./tests/operations/SymmetricDifference";
|
||||
import "./tests/operations/TextEncodingBruteForce";
|
||||
import "./tests/operations/ToGeohash";
|
||||
import "./tests/operations/TranslateDateTimeFormat";
|
||||
import "./tests/operations/Magic";
|
||||
import "./tests/operations/ParseTLV";
|
||||
import "./tests/operations/Media";
|
||||
import "./tests/BCD";
|
||||
import "./tests/BSON";
|
||||
import "./tests/Base58";
|
||||
import "./tests/Base64";
|
||||
import "./tests/Base62";
|
||||
import "./tests/BitwiseOp";
|
||||
import "./tests/ByteRepr";
|
||||
import "./tests/CartesianProduct";
|
||||
import "./tests/CharEnc";
|
||||
import "./tests/Checksum";
|
||||
import "./tests/Ciphers";
|
||||
import "./tests/Code";
|
||||
import "./tests/Comment";
|
||||
import "./tests/Compress";
|
||||
import "./tests/ConditionalJump";
|
||||
import "./tests/Crypt";
|
||||
import "./tests/CSV";
|
||||
import "./tests/DateTime";
|
||||
import "./tests/ExtractEmailAddresses";
|
||||
import "./tests/Fork";
|
||||
import "./tests/FromDecimal";
|
||||
import "./tests/Hash";
|
||||
import "./tests/HaversineDistance";
|
||||
import "./tests/Hexdump";
|
||||
import "./tests/Image";
|
||||
import "./tests/Jump";
|
||||
import "./tests/JSONBeautify";
|
||||
import "./tests/JSONMinify";
|
||||
import "./tests/JWTDecode";
|
||||
import "./tests/JWTSign";
|
||||
import "./tests/JWTVerify";
|
||||
import "./tests/MS";
|
||||
import "./tests/Magic";
|
||||
import "./tests/MorseCode";
|
||||
import "./tests/NetBIOS";
|
||||
import "./tests/OTP";
|
||||
import "./tests/PGP";
|
||||
import "./tests/PHP";
|
||||
import "./tests/ParseIPRange";
|
||||
import "./tests/ParseQRCode";
|
||||
import "./tests/PowerSet";
|
||||
import "./tests/Regex";
|
||||
import "./tests/Register";
|
||||
import "./tests/RemoveDiacritics";
|
||||
import "./tests/Rotate";
|
||||
import "./tests/SeqUtils";
|
||||
import "./tests/SetDifference";
|
||||
import "./tests/SetIntersection";
|
||||
import "./tests/SetUnion";
|
||||
import "./tests/StrUtils";
|
||||
import "./tests/SymmetricDifference";
|
||||
import "./tests/TextEncodingBruteForce";
|
||||
import "./tests/TranslateDateTimeFormat";
|
||||
import "./tests/Magic";
|
||||
import "./tests/ParseTLV";
|
||||
import "./tests/Media";
|
||||
import "./tests/ToFromInsensitiveRegex";
|
||||
import "./tests/YARA.mjs";
|
||||
import "./tests/ConvertCoordinateFormat";
|
||||
|
||||
// Cannot test operations that use the File type yet
|
||||
//import "./tests/SplitColourChannels";
|
||||
|
||||
let allTestsPassing = true;
|
||||
const testStatusCounts = {
|
||||
@@ -5,7 +5,7 @@
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
@@ -6,7 +6,7 @@
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
@@ -6,7 +6,7 @@
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
@@ -7,7 +7,7 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
@@ -6,7 +6,7 @@
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
const ALL_BYTES = [
|
||||
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
|
||||
@@ -5,7 +5,7 @@
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
@@ -5,7 +5,7 @@
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
const ALL_BYTES = [
|
||||
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
|
||||
@@ -6,7 +6,7 @@
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
const EXAMPLE_CSV = `A,B,C,D,E,F\r
|
||||
1,2,3,4,5,6\r
|
||||
@@ -6,7 +6,7 @@
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
@@ -5,7 +5,7 @@
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
@@ -5,7 +5,7 @@
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
const BASIC_STRING = "The ships hung in the sky in much the same way that bricks don't.";
|
||||
const UTF8_STR = "ნუ პანიკას";
|
||||
@@ -7,7 +7,7 @@
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
|
||||
TestRegister.addTests([
|
||||
@@ -7,7 +7,7 @@
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
const JSON_TEST_DATA = {
|
||||
"store": {
|
||||
@@ -6,7 +6,7 @@
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
const ALL_BYTES = [
|
||||
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
|
||||
@@ -5,7 +5,7 @@
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
@@ -6,7 +6,7 @@
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
211
tests/operations/tests/ConvertCoordinateFormat.mjs
Normal file
211
tests/operations/tests/ConvertCoordinateFormat.mjs
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Convert co-ordinate format tests
|
||||
*
|
||||
* @author j433866
|
||||
*
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* TEST CO-ORDINATES
|
||||
* DD: 51.504°,-0.126°,
|
||||
* DDM: 51° 30.24',-0° 7.56',
|
||||
* DMS: 51° 30' 14.4",-0° 7' 33.6",
|
||||
* Geohash: gcpvj0h0x,
|
||||
* MGRS: 30U XC 99455 09790,
|
||||
* OSNG: TQ 30163 80005,
|
||||
* UTM: 30N 699456 5709791,
|
||||
*/
|
||||
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Co-ordinates: From Decimal Degrees to Degrees Minutes Seconds",
|
||||
input: "51.504°,-0.126°,",
|
||||
expectedOutput: "51° 30' 14.4\",-0° 7' 33.6\",",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Degrees Minutes Seconds", "Comma", "None", 1]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From Degrees Minutes Seconds to Decimal Degrees",
|
||||
input: "51° 30' 14.4\",-0° 7' 33.6\",",
|
||||
expectedOutput: "51.504°,-0.126°,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Degrees Minutes Seconds", "Comma", "Decimal Degrees", "Comma", "None", 3]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From Decimal Degrees to Degrees Decimal Minutes",
|
||||
input: "51.504°,-0.126°,",
|
||||
expectedOutput: "51° 30.24',-0° 7.56',",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Degrees Decimal Minutes", "Comma", "None", 2]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From Degrees Decimal Minutes to Decimal Degrees",
|
||||
input: "51° 30.24',-0° 7.56',",
|
||||
expectedOutput: "51.504°,-0.126°,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Degrees Decimal Minutes", "Comma", "Decimal Degrees", "Comma", "None", 3]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From Decimal Degrees to Decimal Degrees",
|
||||
input: "51.504°,-0.126°,",
|
||||
expectedOutput: "51.504°,-0.126°,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "None", 3]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From Decimal Degrees to Geohash",
|
||||
input: "51.504°,-0.126°,",
|
||||
expectedOutput: "gcpvj0h0x,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Geohash", "Comma", "None", 9]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From Geohash to Decimal Degrees",
|
||||
input: "gcpvj0h0x,",
|
||||
expectedOutput: "51.504°,-0.126°,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Geohash", "Comma", "Decimal Degrees", "Comma", "None", 3]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From Decimal Degrees to MGRS",
|
||||
input: "51.504°,-0.126°,",
|
||||
expectedOutput: "30U XC 99455 09790,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Military Grid Reference System", "Comma", "None", 10]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From MGRS to Decimal Degrees",
|
||||
input: "30U XC 99455 09790,",
|
||||
expectedOutput: "51.504°,-0.126°,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Military Grid Reference System", "Comma", "Decimal Degrees", "Comma", "None", 3]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From Decimal Degrees to OSNG",
|
||||
input: "51.504°,-0.126°,",
|
||||
expectedOutput: "TQ 30163 80005,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Ordnance Survey National Grid", "Comma", "None", 10]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From OSNG to Decimal Degrees",
|
||||
input: "TQ 30163 80005,",
|
||||
expectedOutput: "51.504°,-0.126°,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Ordnance Survey National Grid", "Comma", "Decimal Degrees", "Comma", "None", 3]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From Decimal Degrees to UTM",
|
||||
input: "51.504°,-0.126°,",
|
||||
expectedOutput: "30 N 699456 5709791,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Universal Transverse Mercator", "Comma", "None", 0]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: From UTM to Decimal Degrees",
|
||||
input: "30 N 699456 5709791,",
|
||||
expectedOutput: "51.504°,-0.126°,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Universal Transverse Mercator", "Comma", "Decimal Degrees", "Comma", "None", 3]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: Directions in input, not output",
|
||||
input: "N51.504°,W0.126°,",
|
||||
expectedOutput: "51.504°,-0.126°,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "None", 3]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: Directions in input and output",
|
||||
input: "N51.504°,W0.126°,",
|
||||
expectedOutput: "N 51.504°,W 0.126°,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "Before", 3]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: Directions not in input, in output",
|
||||
input: "51.504°,-0.126°,",
|
||||
expectedOutput: "N 51.504°,W 0.126°,",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Decimal Degrees", "Comma", "Before", 3]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Co-ordinates: Directions not in input, in converted output",
|
||||
input: "51.504°,-0.126°,",
|
||||
expectedOutput: "N 51° 30' 14.4\",W 0° 7' 33.6\",",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Convert co-ordinate format",
|
||||
args: ["Decimal Degrees", "Comma", "Degrees Minutes Seconds", "Comma", "Before", 3]
|
||||
},
|
||||
],
|
||||
}
|
||||
]);
|
||||
@@ -6,7 +6,7 @@
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
/**
|
||||
@@ -6,7 +6,7 @@
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
@@ -5,7 +5,7 @@
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
@@ -6,7 +6,7 @@
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../TestRegister";
|
||||
import TestRegister from "../TestRegister";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user