Initial command line/TCP server script

This fairly simple script leverages the Node API provided by CyberChef
to allow performing operations on local filesystem objects without
needing to copy them into and out of a browser window.

It requires a recipe to be specified at runtime as a JSON file.

Also includes a simple TCP server for use by other local processes. This
server is hard-coded to use localhost in order to discourage production
use of the script.
This commit is contained in:
David Perry 2020-05-28 11:44:37 -04:00
parent 4c3324aea1
commit 648ecb6e69
2 changed files with 161 additions and 0 deletions

157
cli.js Executable file
View file

@ -0,0 +1,157 @@
#!/usr/bin/env node
/**
* @author Boolean263 [boolean263@protonmail.com]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
'use strict';
const fs = require("fs");
const path = require("path");
const chef = require("cyberchef");
const program = require("commander");
let slurpStream = (istream) => { // {{{1
var ret = [];
var len = 0;
return new Promise(resolve => {
istream.on('readable', () => {
var chunk;
while ((chunk = istream.read()) !== null) {
ret.push(chunk);
len += chunk.length;
}
resolve(Buffer.concat(ret, len));
});
});
}; // }}}1
let slurp = (fname) => { // {{{1
let istream;
if (fname === undefined || fname == '-') {
istream = process.stdin;
if (istream.isTTY) {
throw new Error("TTY input not supported");
}
}
else {
istream = fs.createReadStream(fname, { flags: 'r' });
}
return slurpStream(istream);
}; // }}}1
let getPort = (value, dummyPrevious) => { // {{{1
// Get a valid port number from the command line
let ret = parseInt(value, 10);
if (ret < 1 || ret > 65535) {
throw new Error("invalid port number");
}
return ret;
};
// }}}1
///////// MAIN ///////// {{{1
program
.version(require('./package.json').version)
.usage('[options] [file [file ...]]')
.requiredOption('-r, --recipe-file <file>',
'recipe JSON file')
.option('-l, --listen [port]',
'listen on TCP port for data (default:random)', getPort, false)
.option('-o, --output <file-or-dir>',
'write result here (not for TCP; default:stdout)')
.parse(process.argv);
// If we get no inputs and we aren't running a server,
// make stdin our single input
let inputs = program.args;
if (inputs.length == 0 && !program.listen) {
inputs = [ '-' ];
}
// Likewise stdout for our output
let ostream;
let outputIsDir = false;
if (program.output === undefined && !program.listen) {
ostream = process.stdout;
}
else if (inputs.length > 0) {
// See if our output is a directory
let st;
try {
st = fs.statSync(program.output);
outputIsDir = st.isDirectory();
}
catch(err) {
if (err.code != 'ENOENT') throw err;
}
if (!outputIsDir) {
ostream = fs.createWriteStream(program.output);
}
}
let recipe;
slurp(program.recipeFile).then((data) => {
recipe = JSON.parse(data);
})
.catch((err) => {
console.error(`Error parsing recipe: ${err}`);
process.exit(1);
})
.then(() => {
// First, deal with any files we want to read
for(let i of inputs) {
slurp(i).then((data) => {
let output = chef.bake(data, recipe);
if (outputIsDir) {
let outFileName = path.basename(i);
if (outFileName == '-') outFileName = 'from-stdin';
ostream = fs.createWriteStream(
path.join(program.output, outFileName));
}
ostream.write(output.presentAs("string", true));
if (outputIsDir) ostream.end();
})
.catch((err) => {
console.error(err);
});
}
// Next, listen for TCP requests.
// This is intentionally hardcoded to localhost to discourage
// the use of this script as a production system.
if (program.listen) {
const net = require('net');
const server = net.createServer((socket) => {
slurpStream(socket).then((data) => {
let output = chef.bake(data, recipe);
socket.write(output.presentAs("string", true));
socket.end();
})
.catch((err) => {
console.error(err);
});
});
// If no port given, let the OS choose one
if (program.listen === true) program.listen = 0;
server.listen(program.listen, '127.0.0.1')
.on('listening', () => {
console.log('Now listening on '
+ server.address().address
+ ":" + server.address().port);
});
// Exit gracefully
process.on('SIGINT', () => {
console.log("Exiting");
server.close();
});
}
})
.catch((err) => {
console.error(err);
process.exit(2);
})

View file

@ -35,6 +35,9 @@
"Firefox >= 38", "Firefox >= 38",
"node >= 10" "node >= 10"
], ],
"bin": {
"cyberchef": "./cli.js"
},
"devDependencies": { "devDependencies": {
"@babel/core": "^7.8.7", "@babel/core": "^7.8.7",
"@babel/plugin-transform-runtime": "^7.8.3", "@babel/plugin-transform-runtime": "^7.8.3",
@ -98,6 +101,7 @@
"bson": "^4.0.3", "bson": "^4.0.3",
"chi-squared": "^1.1.0", "chi-squared": "^1.1.0",
"codepage": "^1.14.0", "codepage": "^1.14.0",
"commander": "^2.14.1",
"core-js": "^3.6.4", "core-js": "^3.6.4",
"crypto-api": "^0.8.5", "crypto-api": "^0.8.5",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",