Mon 21 Jul 22:43:21 CEST 2025

This commit is contained in:
sbosse 2025-07-21 23:18:13 +02:00
parent 8ff7774ae4
commit bd77d0e29d

788
js/top/wex.js Normal file
View File

@ -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<ta.length;i++) b.writeFloatLE(ta[i],i*4);
return b.toString('base64');
case Float64Array:
b = Buffer(ta.length*8);
for(i=0;i<ta.length;i++) b.writeDoubleLE(ta[i],i*8);
return b.toString('base64');
case Int16Array:
b = Buffer(ta.length*2);
for(i=0;i<ta.length;i++) b.writeInt16LE(ta[i],i*2);
return b.toString('base64');
case Int32Array:
b = Buffer(ta.length*4);
for(i=0;i<ta.length;i++) b.writeInt32LE(ta[i],i*4);
return b.toString('base64');
}
return ta.toString();
}
function base64TOtypedarray(buff,ftyp) {
var i,ta;
if (buff.buffer instanceof ArrayBuffer) {
switch (ftyp) {
case Float32Array: return new Float32Array((new Uint8Array(buff)).buffer);
case Float64Array: return new Float64Array((new Uint8Array(buff)).buffer);
case Int16Array: return new Int16Array((new Uint8Array(buff)).buffer);
case Int32Array: return new Int32Array((new Uint8Array(buff)).buffer);
}
} else if (typeof Uint8Array.from != 'undefined') {
switch (ftyp) {
case Float32Array: return new Float32Array(Uint8Array.from(buff).buffer);
case Float64Array: return new Float64Array(Uint8Array.from(buff).buffer);
case Int16Array: return new Int16Array(Uint8Array.from(buff).buffer);
case Int32Array: return new Int32Array(Uint8Array.from(buff).buffer);
}
} else {
// Fall-back conversion
switch (ftyp) {
case Float32Array:
ta=new Float32Array(buff.length/4);
for(i=0;i<ta.length;i++)
ta[i]=buff.readFloatLE(i*4);
return ta;
case Float64Array:
ta=new Float64Array(buff.length/8);
for(i=0;i<ta.length;i++)
ta[i]=buff.readDoubleLE(i*8);
return ta;
case Int16Array:
ta=new Int16Array(buff.length/2);
for(i=0;i<ta.length;i++)
ta[i]=buff.readInt16LE(i*2);
return ta;
case Int32Array:
ta=new Int32Array(buff.length/4);
for(i=0;i<ta.length;i++)
ta[i]=buff.readInt32LE(i*4);
return ta;
}
}
}
var JSONfn = {}
JSONfn.stringify = function (obj) {
return JSON.stringify(obj, function (key, value) {
if (value instanceof Function || typeof value == 'function')
return '_PxEnUf_' +Buffer(value.toString(true)).toString('base64'); // try minification (true) if supported
if (value instanceof Buffer)
return '_PxEfUb_' +value.toString('base64');
if (typeof Float64Array != 'undefined' && value instanceof Float64Array)
return '_PxE6Lf_' + typedarrayTObase64(value,Float64Array);
if (typeof Float32Array != 'undefined' && value instanceof Float32Array)
return '_PxE3Lf_' + typedarrayTObase64(value,Float32Array);
if (typeof Int16Array != 'undefined' && value instanceof Int16Array)
return '_PxE1Ni_' + typedarrayTObase64(value,Int16Array);
if (typeof Int32Array != 'undefined' && value instanceof Int32Array)
return '_PxE3Ni_' + typedarrayTObase64(value,Int32Array);
if (value instanceof RegExp)
return '_PxEgEr_' + value;
return value;
});
};
JSONfn.parse = function (str, mask) {
var code;
try {
with (mask||{}) {
code= JSON.parse(str, function (key, value) {
var prefix;
try {
if (typeof value != 'string') {
return value;
}
if (value.length < 8) {
return value;
}
prefix = value.substring(0, 8);
if (prefix === '_PxEnUf_') {
var code = value.slice(8);
if (code.indexOf('function')==0) // Backward comp.
return eval('(' + code + ')');
else
return eval('(' + Buffer(code,'base64').toString() + ')');
}
if (prefix === '_PxEfUb_')
return Buffer(value.slice(8),'base64');
if (prefix === '_PxE6Lf_')
return base64TOtypedarray(Buffer(value.slice(8),'base64'),Float64Array);
if (prefix === '_PxE3Lf_')
return base64TOtypedarray(Buffer(value.slice(8),'base64'),Float32Array);
if (prefix === '_PxE1Ni_')
return base64TOtypedarray(Buffer(value.slice(8),'base64'),Int16Array);
if (prefix === '_PxE3Ni_')
return base64TOtypedarray(Buffer(value.slice(8),'base64'),Int32Array);
if (prefix === '_PxEgEr_')
return eval(value.slice(8));
return value;
} catch (e) {
throw {error:e,value:value};
}
});
};
} catch (e) {
throw e.error||e;
}
return code;
};
JSONfn.clone = function (obj, date2obj) {
return exports.parse(exports.stringify(obj), date2obj);
};
JSONfn.current =function (module) { current=module.current; };
/* Remove any buffer toJSON bindings */
if (typeof Buffer != 'undefined' && Buffer.prototype.toJSON) delete Buffer.prototype.toJSON;
if (typeof buffer == 'object' && buffer.Buffer) delete buffer.Buffer.prototype.toJSON;
// Alias
JSONfn.serialize = exports.stringify;
JSONfn.deserialize = exports.parse;
function mkdtemp (index) {
rmdtemp(index);
fs.mkdirSync('/tmp/webx-'+index);
return '/tmp/webx-'+index
}
function rmdir(path) {
if( fs.existsSync(path) ) {
fs.readdirSync(path).forEach(function(file,index){
var curPath = path + "/" + file;
if(fs.lstatSync(curPath).isDirectory()) { // recurse
rmdir(curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
};
function rmdtemp (index) {
rmdir('/tmp/webx-'+index)
}
/*
** Parse query string '?attr=val&attr=val... and return parameter record
*/
function parseQueryString( url ) {
var queryString = url.substring( url.indexOf('?') + 1 );
if (queryString == url) return [];
var params = {}, queries, temp, i, l;
// Split into key/value pairs
queries = queryString.split("&");
// Convert the array of strings into an object
for ( i = 0, l = queries.length; i < l; i++ ) {
temp = queries[i].split('=');
if (temp[1]==undefined) temp[1]='true';
params[temp[0]] = temp[1].replace('%20',' ');
}
return params;
}
function reply(response,body,headers) {
header={'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': 'true',
'Content-Type': 'text/plain'};
if (headers) { header={}; for(var p in headers) header[p]=headers[p] };
response.writeHead(200,header);
response.write(body);
response.end();
}
function execute(options) {
if (commands.indexOf(options.command)==-1) return;
var stdoutBroken,stderrBroken;
var tmp=mkdtemp(options.handle);
Consoles[options.handle]={stdout:[],stderr:[],output:{},exit:false,status:0,tid:0};
if (options.input) {
for (var p in options.input) {
fs.writeFileSync(tmp+'/'+p,Buffer(options.input[p],'base64'),'binary');
}
}
var pro = proc.spawn(options.command, options.arguments, {
cwd:tmp
});
if (config.verbose) print(options)
else print('[PRO '+options.handle+']',options.command,options.arguments);
pro.stdout.on('data', function (data) {
var msg = data.toString(),
lines = msg.split('\n');
if (config.verbose) print('['+options.handle+'] stdout:<'+msg+'>');
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()