// some annoying nodejs fixes process.noDeprecation = true; process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; module.paths.push(process.env.HOME+'/node_modules') var proc = require('child_process'); var http = require('http') var https var fs = require('fs') var Path = require('path') var execSync = require("child_process").execSync; // Optional websocket interface var WebSocket; try { WebSocket = require('ws') } catch (e) { /* no ws module installed */ } try { https = require('https') } catch (e) { /* no https module installed */ } process.argv.shift(); process.argv.shift(); var config = { version : "1.7.1", TIMEOUT : 100000, PORT : 11111, PORTWS : 11112, verbose : 0, handleIndex : 1000, commands : process.argv, } if (process.argv.indexOf('-v')!=-1) { config.verbose++; } var time = function () { return Math.floor(Date.now()/1000) } print = console.log; function log(msg) { console.log('[WEX] '+msg); } var Consoles = []; function GET(url,params, cb) { var ishttps= url.match(/https:/)!=null; if (ishttps && !https) { if (cb) return cb(new Error('ENOTSUPPORTED')); } url=url.replace(/http[s]?:\/\//,''); var parts = url.split(':'), host = parts[0].split('/')[0], path = parts[parts.length-1].split('/').slice(1).join('/'), port = (parts[1]||(ishttps?'443':'80')).split('/')[0]; if (params) { var o=params,sep=''; params='/?'; for(var p in o) { params = params + sep + p + '='+o[p]; sep='&'; } } else params=''; path += params; // print(host,path,port,ishttps) var get_options = { host: host, port: port, path: path, method: 'GET', keepAlive: true, headers: { // 'Content-Type': 'application/x-www-form-urlencoded', } }; // console.log('GET', post_options,post_data) var get_req = (ishttps?https:http).request(get_options, function(res) { res.setEncoding('utf8'); var data=''; res.on('data', function (chunk) { data += chunk; // console.log('Response: ' + chunk); }); res.on('end', function () { try { var result=JSONfn.parse(data); // console.log('GET: ',result); } catch (e) { print(data.slice(0,100)); result=new Error(e.toString()+' ['+data.slice(0,200)+']'); } if (cb) cb(result); }); }); get_req.on('error',function (err) { // console.log(err) if (cb) cb(err); else console.log(url,err); }); get_req.setNoDelay(); // get_req.write(); get_req.end(); } function get(url, cb) { var ishttps= url.match(/https:/)!=null; if (ishttps && !https) { if (cb) return cb(new Error('ENOTSUPPORTED')); } url=url.replace(/http[s]?:\/\//,''); var parts = url.split(':'), host = parts[0].split('/')[0], path = parts[parts.length-1].split('/').slice(1).join('/'), port = (parts[1]||(ishttps?'443':'80')).split('/')[0]; // print(host,path,port,ishttps) var get_options = { host: host, port: port, path: path[0]=='/'?path:'/'+path, method: 'GET', keepAlive: true, headers: { // 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent':'Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0', 'Accept':"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", } }; // console.log('GET', get_options) var get_req = (ishttps?https:http).request(get_options, function(res) { res.setEncoding('binary'); // res.setEncoding('utf8'); var data = []; res.on('data', function (chunk) { data.push(Buffer.from(chunk, 'binary')); // console.log('Response: ' + chunk); }); res.on('end', function () { var buffer = Buffer.concat(data); cb(buffer,res.headers) }); }); get_req.on('error',function (err) { // console.log(err) if (cb) cb(err); else console.log(url,err); }); get_req.setNoDelay(); // get_req.write(); get_req.end(); } function POST(url, data, cb) { var params,headers; if (data && data.params && data.data != undefined) { params=data.params; headers=data.headers; data=data.data; } var ishttps= url.match(/https:/); if (ishttps && !https) { if (cb) return cb(new Error('ENOTSUPPORTED')); } url=url.replace(/http[s]?:\/\//,''); var parts = url.split(':'), host = parts[0].split('/')[0], path = parts[parts.length-1].split('/').slice(1).join('/'), port = (parts[1]||(ishttps?'443':'80')).split('/')[0]; if (params) { var o=params,sep=''; params='/?'; for(var p in o) { params = params + sep + p + '='+o[p]; sep='&'; } } else params=''; var post_data = typeof data == 'string'?data:JSON.stringify(data); var post_options = { host: host, port: port, path: '/'+path+params, method: 'POST', keepAlive: true, headers: headers || { 'Content-Type': 'application/json', // ?? 'application/x-www-form-urlencoded', // 'Content-Length': Buffer.byteLength(post_data) 'Content-Length': post_data.length, } }; // console.log('POST', post_options,post_data) var post_req = (ishttps?https:http).request(post_options, function(res) { res.setEncoding('utf8'); var data=''; res.on('data', function (chunk) { data += chunk; // console.log('Response: ' + chunk); }); res.on('end', function () { try { var result=JSON.parse(data); // console.log('POST: ',result); } catch (e) { print(data.slice(0,100)); result=new Error(e.toString()+' ['+data.slice(0,200)+']'); } if (cb) cb(result); }); }); post_req.on('error',function (err) { if (cb) cb(err); else console.log(url,err) }); post_req.setNoDelay(); // console.log('POST: ',post_data); // post the data post_req.write(post_data); post_req.end(); } function Post(url, data, cb) { var params,headers; if (data && data.params && data.data != undefined) { params=data.params; headers=data.headers; data=data.data; } var ishttps= url.match(/https:/); if (ishttps && !https) { if (cb) return cb(new Error('ENOTSUPPORTED')); } url=url.replace(/http[s]?:\/\//,''); var parts = url.split(':'), host = parts[0].split('/')[0], path = parts[parts.length-1].split('/').slice(1).join('/'), port = (parts[1]||(ishttps?'443':'80')).split('/')[0]; if (params) { var o=params,sep=''; params='/?'; for(var p in o) { params = params + sep + p + '='+o[p]; sep='&'; } } else params=''; var text=[]; for(var p in data) { text.push(p+'='+encodeURI(data[p])); } text=text.join('&'); var post_data = typeof data == 'string'?data:JSON.stringify(data); var post_options = { host: host, port: port, path: '/'+path+params, method: 'POST', keepAlive: true, headers: headers || { 'Content-Type': 'application/x-www-form-urlencoded', // 'Content-Length': Buffer.byteLength(post_data) 'Content-Length': text.length, } }; // console.log('Post', post_options,post_data) var post_req = (ishttps?https:http).request(post_options, function(res) { res.setEncoding('utf8'); var data=''; res.on('data', function (chunk) { data += chunk; // console.log('Response: ' + chunk); }); res.on('end', function () { try { var result=JSON.parse(data); // console.log('POST: ',result); } catch (e) { print(data.slice(0,100)); result=new Error(e.toString()+' ['+data.slice(0,200)+']'); } if (cb) cb(result); }); }); post_req.on('error',function (err) { if (cb) cb(err); else console.log(url,err) }); post_req.setNoDelay(); // console.log('POST: ',post_data); // post the data // console.log(text) post_req.write(text); post_req.end(); } function typedarrayTObase64(ta,ftyp) { var b,i; if (ta.buffer instanceof ArrayBuffer) { b=Buffer(ta.buffer); if (b.length>0) return b.toString('base64'); } // Fall-back conversion switch (ftyp) { case Float32Array: b = Buffer(ta.length*4); for(i=0;i'); if (stdoutBroken) lines[0] = stdoutBroken+lines[0]; stdoutBroken=null; if (lines[lines.length-1]!='') stdoutBroken=lines.pop(); else lines.pop(); Consoles[options.handle].stdout=Consoles[options.handle].stdout.concat(lines); }); pro.stderr.on('data', function (data) { var msg = data.toString(), lines = msg.split('\n'); if (config.verbose) print('['+options.handle+'] stderr:<'+msg+'>'); if (stderrBroken) lines[0] = stderrBroken+lines[0]; stderrBroken=null; if (lines[lines.length-1]!='') stderrBroken=lines.pop(); else lines.pop(); Consoles[options.handle].stderr=Consoles[options.handle].stderr.concat(lines); }); pro.on('exit', function (code) { print('[PRO '+options.handle+'] child process exited with code ' + String(code)); if (options.output) { // All files will be base64 encoded (handling binary and text data the same way) options.output.forEach(function (file) { try { var data = fs.readFileSync(tmp+'/'+file,'binary'); Consoles[options.handle].stdout.push('[PRO] Saving '+file+' ['+data.length+'] ..'); if (data && data.length) Consoles[options.handle].output[file]= Buffer(data,'binary').toString('base64'); // print(Consoles[options.handle].output[file].substring(0,16)); } catch (e) { print(e) } }) } Consoles[options.handle].exit=code; rmdtemp(options.handle) }); return 'OK'; } function rpc(request,response) { var status=0,data='',files; if (config.verbose) print(request.command,request.dir,request.file,request.mimetype,request.data && request.data.length) try { switch (request.command) { case 'GET': console.log(request) GET(request.url,request.params, function (result) { // console.log('result',result) if (result instanceof Error) {status=result.toString(); result=''}; reply(response,JSONfn.stringify({status:status,reply:result,handle:request.handle})) }) return; break; case 'Post': console.log(request) Post(request.url,request.data, function (result) { // console.log('result',result) if (result instanceof Error) {status=result.toString(); result=''}; reply(response,JSONfn.stringify({status:status,reply:result,handle:request.handle})) }) return; break; case 'POST': console.log(request) POST(request.url,request.data, function (result) { // console.log('result',result) if (result instanceof Error) {status=result.toString(); result=''}; reply(response,JSONfn.stringify({status:status,reply:result,handle:request.handle})) }) return; break; case 'list': request.dir=request.dir.replace(/^~/,process.env.HOME); files = fs.readdirSync(request.dir); files = files.map(function (entry) { try { var stat=fs.statSync(request.dir+'/'+entry); return {name:entry,dir:stat.isDirectory(),size:stat.size,time:stat.mtime}; } catch (e) {}; }).filter(function (entry) { return entry }); data = [{name:'..',dir:true}].concat(files); break; case 'load': request.dir=request.dir.replace(/^~/,process.env.HOME); if (request.mimetype.indexOf('text')==0) { data=fs.readFileSync(request.dir+'/'+request.file,'utf8'); } else if (request.mimetype.indexOf('binary')!=-1) { data=fs.readFileSync(request.dir+'/'+request.file,'binary'); } break; case 'lookup': var stat=fs.statSync(request.path); data={name:request.path,dir:stat.isDirectory(),size:stat.size,time:stat.mtime}; break; case 'save': request.dir=request.dir.replace(/^~/,process.env.HOME); if (request.mimetype.indexOf('text')==0) { fs.writeFileSync(request.dir+'/'+request.file,request.data,'utf8',{}); } else if (request.mimetype.indexOf('binary')!=-1) { fs.writeFileSync(request.dir+'/'+request.file,Buffer(request.data),'binary',{}); } break; case 'shell': status=null;data=null; var pro = proc.exec('cd '+request.dir+';'+request.exec, function (err,stdout,stderr) { data=stdout; if (stderr) date = data + '\n' + stderr; print(data) if (status!=null) reply(response,JSON.stringify({status:status,reply:data,handle:request.handle})) }); pro.on('exit', function (code) { if (data==null) status=code; else reply(response,JSON.stringify({status:code,reply:data,handle:request.handle})) }); return; // reply send on exit or error break; dedault: status=execute(request,response); } } catch (e) { status=e.toString(); } // print(status) reply(response,JSONfn.stringify({status:status,reply:data,handle:request.handle})) } function isThisLocalhost(ip) { return ip.indexOf("127.0.0.1")==0 || ip.indexOf("::ffff:127.0.0.1") == 0 || ip.indexOf("::1") == 0 || ip.indexOf("localhost") == 0; } function proxy(method,headers,path,response) { // console.log(method,headers,path); // TODO: curreltly assumes binary requests... if (config.verbose) print('proxy '+path); if (method=='GET') get(path,function (data,headers) { // console.log(headers,data.length); headers['access-control-allow-origin']='*'; response.writeHead(200,headers||{}); response.write(data); response.end(); }) } function server () { var dirCache=[]; var webSrv = http.createServer(function (request,response) { // only local requests are accepted if (!isThisLocalhost(request.headers.host)) return; var body,header,sep,query,res,now,path, rid = request.connection.remoteAddress+':'+request.connection.remotePort; if (config.verbose) print(request.url,request.connection.remoteAddress,request.connection.remotePort); if (request.url.length) query=parseQueryString(request.url); else query={} path=request.url.match(/\/([^?]+)/); if (path) path=path[1]; if (config.verbose) print(query,path); now=time() if (/^http[s]*/.test(path)) { // proxy service return proxy(request.method,request.headers,path,response); } switch (request.method) { case 'GET': if (query.handle && Consoles[query.handle]) { reply(response,JSONfn.stringify({ handle:query.handle, stdout:Consoles[query.handle].stdout, stderr:Consoles[query.handle].stderr, output:Consoles[query.handle].output, tid:Consoles[query.handle].tid++, exit:Consoles[query.handle].exit, })); Consoles[query.handle].stdout=[]; Consoles[query.handle].stderr=[]; } else if (path) { try { if (path[0]!='/' && dirCache[rid]) path=dirCache[rid]+'/'+path; else dirCache[rid]=Path.dirname(path); var data=fs.readFileSync(path,'utf8'); if (config.verbose) print('Loading '+path); reply(response,data,{ 'Content-Type': 'text/html; charset=UTF-8', 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp', }); } catch (e) { if (config.verbose) print(e.toString()) reply(response,e.toString()); } } break; case 'POST': body = ''; request.on('data', function (chunk) { body = body + chunk; }); request.on('end', function () { var cmd,stat; // handle copy request {id:string,data:string} try { // print(body) cmd=JSONfn.parse(body) cmd.handle=config.handleIndex++; rpc(cmd,response) } catch (err) { log(err.toString()) reply(response,JSONfn.stringify({err:err.toString()})); } }); break; } }) webSrv.on("connection", function (socket) { // socket.setNoDelay(true); }); webSrv.on("error", function (err) { log(err) process.exit(); }); webSrv.listen(config.PORT,function (err) { log('HTTP Service started (http://localhost:'+config.PORT+')'); }); // Start garbage collector gc=setInterval(function () { var now = time(); },1000); if (WebSocket) { // Start a connection-based WebSocket Server var wss = new WebSocket.Server({ port: config.PORTWS }) wss.on('connection', function (ws) { log('ws.connection.open') ws.on('message', function (message) { var cmd, response = { write : function (data) { ws.send (data) }, writeHead : function () {}, end : function () {} }; try { // print(body) cmd=JSONfn.parse(message) cmd.handle=config.handleIndex++; rpc(cmd,response) } catch (err) { log(err.toString()) reply(response,JSONfn.stringify({err:err.toString()})); } }) ws.on('error', function (err) { console.log(err); }) ws.on('close', function () { console.log('ws.connection.close') }); }) wss.on('error', function (error) { console.log(error) }) log('WebSocket Service started (ws://localhost:'+config.PORTWS+')'); } log('WEB Processor Ver. '+config.version+' started.'); return webSrv; } server()