From bd77d0e29d74b76c56230866cc9ccb82b832af29 Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:18:13 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- js/top/wex.js | 788 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 788 insertions(+) create mode 100644 js/top/wex.js diff --git a/js/top/wex.js b/js/top/wex.js new file mode 100644 index 0000000..86f0de3 --- /dev/null +++ b/js/top/wex.js @@ -0,0 +1,788 @@ +// 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()