pc_services now uses node Express

- reduces chance of error with older webtv clients sending http request
- version bump to 0.9.32
This commit is contained in:
zefie
2022-10-10 15:40:09 -04:00
parent 300a8427cd
commit d4ceda7211
4 changed files with 1217 additions and 285 deletions

View File

@@ -19,6 +19,8 @@ const WTVMime = require("./WTVMime.js");
const { WTVShared, clientShowAlert } = require("./WTVShared.js"); const { WTVShared, clientShowAlert } = require("./WTVShared.js");
const WTVFlashrom = require("./WTVFlashrom.js"); const WTVFlashrom = require("./WTVFlashrom.js");
const vm = require('vm'); const vm = require('vm');
const express = require('express');
const { url } = require('inspector');
process process
.on('SIGTERM', shutdown('SIGTERM')) .on('SIGTERM', shutdown('SIGTERM'))
@@ -39,6 +41,7 @@ var ssid_sessions = new Array();
var socket_sessions = new Array(); var socket_sessions = new Array();
var ports = []; var ports = [];
var pc_ports = [];
// add .reverse() feature to all JavaScript Strings in this application // add .reverse() feature to all JavaScript Strings in this application
// works for service vault scripts too. // works for service vault scripts too.
@@ -185,12 +188,16 @@ var runScriptInVM = function (script_data, user_contextObj = {}, privileged = fa
return contextObj; // updated context object with whatever global varibles the script set return contextObj; // updated context object with whatever global varibles the script set
} }
async function processPath(socket, service_vault_file_path, request_headers = new Array(), service_name, shared_romcache = null) { function sendToPCClient(headers, data) {
}
async function processPath(socket, service_vault_file_path, request_headers = new Array(), service_name, shared_romcache = null, pc_services = false) {
var headers, data = null; var headers, data = null;
var request_is_async = false; var request_is_async = false;
var service_vault_found = false; var service_vault_found = false;
var service_path = unescape(service_vault_file_path); var service_path = unescape(service_vault_file_path);
var usingSharedROMCache = false; var usingSharedROMCache = false;
var contextObj = { var contextObj = {
"privileged": false, "privileged": false,
"socket": socket, "socket": socket,
@@ -208,11 +215,14 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
// format: [ ourvarname, scriptsvarname ] // format: [ ourvarname, scriptsvarname ]
["headers", "headers"], // we need to be able to read the script's response headers ["headers", "headers"], // we need to be able to read the script's response headers
["data", "data"], // we need to be able to read the script's response data ["data", "data"], // we need to be able to read the script's response data
["request_is_async", "request_is_async"], // we need to know if the script is async or not ["request_is_async", "request_is_async"] // we need to know if the script is async or not
["ssid_sessions", "ssid_sessions"], // global ssid_sessions object for privileged service scripts, such as wtv-setup, wtv-head-waiter, etc ]
["socket_sessions", "socket_sessions"], // global socket_sessions object for privileged service scripts, such as wtv-1800, etc
[`ssid_sessions[${socket.ssid}]`, "session_data"] // user-specific session data from unprivileged scripts if (!pc_services) {
]; updateFromVM.push(["ssid_sessions", "ssid_sessions"]); // global ssid_sessions object for privileged service scripts, such as wtv-setup, wtv-head-waiter, etc
updateFromVM.push(["socket_sessions", "socket_sessions"]); // global socket_sessions object for privileged service scripts, such as wtv-1800, etc
updateFromVM.push([`ssid_sessions[${socket.ssid}]`, "session_data"]); // user-specific session data from unprivileged scripts
}
try { try {
service_vaults.forEach(function (service_vault_dir) { service_vaults.forEach(function (service_vault_dir) {
@@ -238,6 +248,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
} else { } else {
service_vault_file_path = wtvshared.makeSafePath(service_vault_dir, service_path); service_vault_file_path = wtvshared.makeSafePath(service_vault_dir, service_path);
} }
// deny access to catchall file name directly // deny access to catchall file name directly
var service_path_split = service_path.split("/"); var service_path_split = service_path.split("/");
var service_path_request_file = service_path_split[service_path_split.length - 1]; var service_path_request_file = service_path_split[service_path_split.length - 1];
@@ -373,7 +384,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
var script_data = fs.readFileSync(service_vault_file_path + ".js").toString(); var script_data = fs.readFileSync(service_vault_file_path + ".js").toString();
var priv = false; var priv = false;
if (minisrv_config.services[service_name]) priv = (minisrv_config.services[service_name].privileged) ? true : false; if (minisrv_config.services[service_name]) priv = (minisrv_config.services[service_name].privileged) ? true : false;
else if (socket.minisrv_pc_mode) priv = (minisrv_config.services['pc_services'].privileged) ? true : false; else if (pc_services) priv = (minisrv_config.services['pc_services'].privileged) ? true : false;
var vmResults = runScriptInVM(script_data, contextObj, priv, service_vault_file_path + ".js"); var vmResults = runScriptInVM(script_data, contextObj, priv, service_vault_file_path + ".js");
// Here we read back certain data from the ServiceVault Script Context Object // Here we read back certain data from the ServiceVault Script Context Object
@@ -417,7 +428,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
var script_data = fs.readFileSync(catchall_file).toString(); var script_data = fs.readFileSync(catchall_file).toString();
var priv = false; var priv = false;
if (minisrv_config.services[service_name]) priv = (minisrv_config.services[service_name].privileged) ? true : false; if (minisrv_config.services[service_name]) priv = (minisrv_config.services[service_name].privileged) ? true : false;
else if (socket.minisrv_pc_mode) priv = (minisrv_config.services['pc_services'].privileged) ? true : false; else if (pc_services) priv = (minisrv_config.services['pc_services'].privileged) ? true : false;
runScriptInVM(script_data, contextObj, priv, catchall_file); runScriptInVM(script_data, contextObj, priv, catchall_file);
@@ -444,7 +455,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
var errpage = wtvshared.doErrorPage(400); var errpage = wtvshared.doErrorPage(400);
headers = errpage[0]; headers = errpage[0];
data = errpage[1]; data = errpage[1];
if (socket.minisrv_pc_mode) { if (pc_services) {
if (minisrv_config.services.pc_services.show_verbose_errors) if (minisrv_config.services.pc_services.show_verbose_errors)
data += "<br><br>The interpreter said:<br><pre>" + e.stack + "</pre>"; data += "<br><br>The interpreter said:<br><pre>" + e.stack + "</pre>";
} }
@@ -453,12 +464,12 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
if (!request_is_async) { if (!request_is_async) {
if (!service_vault_found) { if (!service_vault_found) {
console.error(" * Could not find a Service Vault for " + service_name + ":/" + service_path.replace(service_name + path.sep, "").replace(path.sep, '/')); console.error(" * Could not find a Service Vault for " + service_name + ":/" + service_path.replace(service_name + path.sep, "").replace(path.sep, '/'));
var errpage = wtvshared.doErrorPage(404, null, socket.minisrv_pc_mode); var errpage = wtvshared.doErrorPage(404, null, pc_services);
headers = errpage[0]; headers = errpage[0];
data = errpage[1]; data = errpage[1];
} }
if (headers == null && !request_is_async) { if (headers == null && !request_is_async) {
var errpage = wtvshared.doErrorPage(400, null, socket.minisrv_pc_mode); var errpage = wtvshared.doErrorPage(400, null, pc_services);
headers = errpage[0]; headers = errpage[0];
data = errpage[1]; data = errpage[1];
console.error(" * Scripting or Data error: Headers were not defined. (headers,data) as follows:") console.error(" * Scripting or Data error: Headers were not defined. (headers,data) as follows:")
@@ -486,50 +497,51 @@ function verifyServicePort(service_name, socket) {
return false; return false;
} }
async function processURL(socket, request_headers) { async function processURL(socket, request_headers, pc_services = false) {
var shortURL, headers, data, service_name = ""; var shortURL, headers, data, service_name = "";
var enable_multi_query = false; var enable_multi_query = false;
request_headers.query = new Array(); request_headers.query = {};
if (request_headers.request_url) { if (request_headers.request_url) {
if (pc_services) {
service_name = verifyServicePort(request_headers.service_name, socket);
delete request_headers.service_name;
}
if (request_headers.request_url.indexOf('?') >= 0) { if (request_headers.request_url.indexOf('?') >= 0) {
shortURL = request_headers.request_url.split('?')[0]; shortURL = request_headers.request_url.split('?')[0];
if (socket.minisrv_pc_mode) if (!service_name) service_name = verifyServicePort(shortURL.split(':')[0], socket);
service_name = verifyServicePort("pc_services", socket);
else
service_name = verifyServicePort(shortURL.split(':')[0], socket);
if (!service_name) { if (!service_name) {
service_name = verifyServicePort("pc_services", socket); var errpage = wtvshared.doErrorPage(500, null, pc_services);
socket.minisrv_pc_mode = true;
}
if (!service_name) {
var errpage = wtvshared.doErrorPage(500, null, socket.minisrv_pc_mode);
socket_sessions[socket.id].close_me = true; socket_sessions[socket.id].close_me = true;
sendToClient(socket, errpage[0], errpage[1]); sendToClient(socket, errpage[0], errpage[1]);
return return
} }
if (minisrv_config.services[service_name]) enable_multi_query = minisrv_config.services[service_name].enable_multi_query || false; if (request_headers.request_url.indexOf('?') >= 0) {
var qraw = request_headers.request_url.split('?')[1]; shortURL = request_headers.request_url.split('?')[0];
if (qraw.length > 0) { if (minisrv_config.services[service_name]) enable_multi_query = minisrv_config.services[service_name].enable_multi_query || false;
qraw = qraw.split("&"); var qraw = request_headers.request_url.split('?')[1];
for (let i = 0; i < qraw.length; i++) { if (qraw.length > 0) {
var qraw_split = qraw[i].split("="); qraw = qraw.split("&");
if (qraw_split.length == 2) { for (let i = 0; i < qraw.length; i++) {
var k = qraw_split[0]; var qraw_split = qraw[i].split("=");
if (request_headers.query[k] && enable_multi_query) { if (qraw_split.length == 2) {
if (typeof request_headers.query[k] === 'string') { var k = qraw_split[0];
var keyarray = [request_headers.query[k]]; if (request_headers.query[k] && enable_multi_query) {
request_headers.query[k] = keyarray; if (typeof request_headers.query[k] === 'string') {
var keyarray = [request_headers.query[k]];
request_headers.query[k] = keyarray;
}
request_headers.query[k].push(unescape(qraw[i].split("=")[1].replace(/\+/g, "%20")));
} else {
request_headers.query[k] = unescape(qraw[i].split("=")[1].replace(/\+/g, "%20"));
} }
request_headers.query[k].push(unescape(qraw[i].split("=")[1].replace(/\+/g, "%20"))); } else if (qraw[i].length == 1) {
} else { request_headers.query[qraw[i]] = null;
request_headers.query[k] = unescape(qraw[i].split("=")[1].replace(/\+/g, "%20"));
} }
} else if (qraw[i].length == 1) {
request_headers.query[qraw[i]] = null;
} }
} }
} else {
shortURL = unescape(request_headers.request_url);
} }
} else { } else {
shortURL = unescape(request_headers.request_url); shortURL = unescape(request_headers.request_url);
@@ -598,49 +610,9 @@ async function processURL(socket, request_headers) {
shortURL_split.shift(); shortURL_split.shift();
var shortURL_service_path = shortURL_split.join(":"); var shortURL_service_path = shortURL_split.join(":");
shortURL = shortURL_service_name + ":/" + shortURL_service_path; shortURL = shortURL_service_name + ":/" + shortURL_service_path;
} else if (shortURL.indexOf(":") == -1 && request_headers.request.indexOf("HTTP/1") > 0 && request_headers.Host && socket.ssid == null) { }
// PC browsers typically send a Host header
if (minisrv_config.services.pc_services) {
if (!minisrv_config.services.pc_services.disabled) {
socket.minisrv_pc_mode = true;
service_name = verifyServicePort("pc_services", socket);
if (!service_name) {
if (minisrv_config.services.pc_services.drop_connection_on_wrong_port) {
// just close the connection, no fancy error
socket.end();
return;
}
var errpage = wtvshared.doErrorPage(500, null, socket.minisrv_pc_mode);
socket_sessions[socket.id].close_me = true;
sendToClient(socket, errpage[0], errpage[1]);
return;
}
// if a directory, request index
if (shortURL.substring(shortURL.length - 1) == "/") shortURL += "index";
shortURL = service_name + ":" + shortURL;
} else {
// minimal pc mode to send error
socket.minisrv_pc_mode = true;
var errpage = wtvshared.doErrorPage(401, "PC services are disabled on this server", socket.minisrv_pc_mode);
headers = errpage[0];
data = errpage[1]
socket_sessions[socket.id].close_me = true;
sendToClient(socket, headers, data);
return;
}
} else {
// minimal pc mode to send error
socket.minisrv_pc_mode = true;
var errpage = wtvshared.doErrorPage(401, "PC services are disabled on this server", socket.minisrv_pc_mode);
headers = errpage[0];
data = errpage[1]
socket_sessions[socket.id].close_me = true;
sendToClient(socket, headers, data);
return;
}
}
if (!socket.minisrv_pc_mode && !socket.ssid) { if (socket.ssid) {
// skip box auth tests for pc mode // skip box auth tests for pc mode
// check security // check security
@@ -704,10 +676,10 @@ minisrv-no-mail-count: true`;
} else { } else {
console.log(" * " + reqverb + " for " + request_headers.request_url, 'on', socket.id); console.log(" * " + reqverb + " for " + request_headers.request_url, 'on', socket.id);
} }
if (!socket.minisrv_pc_mode) { if (!pc_services) {
var service_name = verifyServicePort(shortURL.split(':/')[0], socket); var service_name = verifyServicePort(shortURL.split(':/')[0], socket);
if (!service_name) { if (!service_name) {
var errpage = wtvshared.doErrorPage(500, null, socket.minisrv_pc_mode); var errpage = wtvshared.doErrorPage(500, null, pc_services);
socket_sessions[socket.id].close_me = true; socket_sessions[socket.id].close_me = true;
sendToClient(socket, errpage[0], errpage[1]); sendToClient(socket, errpage[0], errpage[1]);
return return
@@ -720,9 +692,14 @@ minisrv-no-mail-count: true`;
} }
if (minisrv_config.config.debug_flags.show_headers) console.log(" * Incoming headers on socket ID", socket.id, (await wtvshared.decodePostData(wtvshared.filterRequestLog(wtvshared.filterSSID(request_headers))))); if (minisrv_config.config.debug_flags.show_headers) console.log(" * Incoming headers on socket ID", socket.id, (await wtvshared.decodePostData(wtvshared.filterRequestLog(wtvshared.filterSSID(request_headers)))));
socket_sessions[socket.id].request_headers = request_headers; socket_sessions[socket.id].request_headers = request_headers;
processPath(socket, urlToPath, request_headers, service_name, shared_romcache); processPath(socket, urlToPath, request_headers, service_name, shared_romcache, pc_services);
} else if (shortURL.indexOf('http://') >= 0 || shortURL.indexOf('https://') >= 0) { } else if ((shortURL.indexOf('http://') >= 0 || shortURL.indexOf('https://') >= 0) && !pc_services) {
doHTTPProxy(socket, request_headers); doHTTPProxy(socket, request_headers);
} else if (pc_services) {
// if a directory, request index
if (shortURL.substring(shortURL.length - 1) == "/") shortURL += "index";
var urlToPath = wtvshared.fixPathSlashes(service_name + path.sep + shortURL);
processPath(socket, urlToPath, request_headers, service_name, shared_romcache, pc_services);
} else { } else {
// error reading headers (no request_url provided) // error reading headers (no request_url provided)
var errpage = wtvshared.doErrorPage(400); var errpage = wtvshared.doErrorPage(400);
@@ -922,7 +899,7 @@ function stripHeaders(headers_obj, whitelist) {
function headerStringToObj(headers, response = false) { function headerStringToObj(headers, response = false) {
var inc_headers = 0; var inc_headers = 0;
var headers_obj = new Array(); var headers_obj = {};
var headers_obj_pre = headers.split("\n"); var headers_obj_pre = headers.split("\n");
headers_obj_pre.forEach(function (d) { headers_obj_pre.forEach(function (d) {
if (/^SECURE ON/.test(d) && !response) { if (/^SECURE ON/.test(d) && !response) {
@@ -973,20 +950,22 @@ async function sendToClient(socket, headers_obj, data) {
socket.destroy(); socket.destroy();
return; return;
} }
var wtv_connection_close = (headers_obj["wtv-connection-close"]) ? true : false; if (!socket.res) {
if (typeof (headers_obj["wtv-connection-close"]) != 'undefined') delete headers_obj["wtv-connection-close"]; var wtv_connection_close = (headers_obj["wtv-connection-close"]) ? true : false;
if (typeof (headers_obj["wtv-connection-close"]) != 'undefined') delete headers_obj["wtv-connection-close"];
if (!headers_obj['minisrv-no-mail-count']) { if (!headers_obj['minisrv-no-mail-count']) {
if (ssid_sessions[socket.ssid]) { if (ssid_sessions[socket.ssid]) {
if (ssid_sessions[socket.ssid].isRegistered()) { if (ssid_sessions[socket.ssid].isRegistered()) {
if (ssid_sessions[socket.ssid].mailstore) { if (ssid_sessions[socket.ssid].mailstore) {
headers_obj['wtv-mail-count'] = ssid_sessions[socket.ssid].mailstore.countUnreadMessages(0); headers_obj['wtv-mail-count'] = ssid_sessions[socket.ssid].mailstore.countUnreadMessages(0);
}
} }
} }
} else {
if (headers_obj['wtv-mail-count']) delete headers_obj['wtv-mail-count'];
delete headers_obj['minisrv-no-mail-count'];
} }
} else {
if (headers_obj['wtv-mail-count']) delete headers_obj['wtv-mail-count'];
delete headers_obj['minisrv-no-mail-count'];
} }
// add Connection header if missing, default to Keep-Alive // add Connection header if missing, default to Keep-Alive
@@ -1041,6 +1020,10 @@ async function sendToClient(socket, headers_obj, data) {
delete headers_obj["minisrv-force-compression"]; delete headers_obj["minisrv-force-compression"];
} }
if (socket.res) { // pc mode with response object available
if (compression_type == 1) compression_type = 2; // just in case
}
// compress if needed // compress if needed
if (compression_type > 0 && content_length > 0 && headers_obj['Response'].substring(0, 3) == "200") { if (compression_type > 0 && content_length > 0 && headers_obj['Response'].substring(0, 3) == "200") {
var uncompressed_content_length = content_length; var uncompressed_content_length = content_length;
@@ -1076,21 +1059,22 @@ async function sendToClient(socket, headers_obj, data) {
if (uncompressed_content_length != compressed_content_length) if (minisrv_config.config.debug_flags.debug) console.log(" # Compression stats: Orig Size:", uncompressed_content_length, "~ Comp Size:", compressed_content_length, "~ Ratio:", compression_ratio, "~ Saved:", compression_percentage.toString() + "%"); if (uncompressed_content_length != compressed_content_length) if (minisrv_config.config.debug_flags.debug) console.log(" # Compression stats: Orig Size:", uncompressed_content_length, "~ Comp Size:", compressed_content_length, "~ Ratio:", compression_ratio, "~ Saved:", compression_percentage.toString() + "%");
} }
if (!socket.res) {
// encrypt if needed // encrypt if needed
if (socket_sessions[socket.id].secure == true && !socket_sessions[socket.id].do_not_encrypt) { if (socket_sessions[socket.id].secure == true && !socket_sessions[socket.id].do_not_encrypt) {
headers_obj["wtv-encrypted"] = 'true'; headers_obj["wtv-encrypted"] = 'true';
headers_obj = moveObjectElement('wtv-encrypted', 'Connection', headers_obj); headers_obj = moveObjectElement('wtv-encrypted', 'Connection', headers_obj);
if (content_length > 0 && socket_sessions[socket.id].wtvsec) { if (content_length > 0 && socket_sessions[socket.id].wtvsec) {
if (!minisrv_config.config.debug_flags.quiet) console.log(" * Encrypting response to client ...") if (!minisrv_config.config.debug_flags.quiet) console.log(" * Encrypting response to client ...")
var enc_data = socket_sessions[socket.id].wtvsec.Encrypt(1, data); var enc_data = socket_sessions[socket.id].wtvsec.Encrypt(1, data);
data = enc_data; data = enc_data;
}
} }
}
if (socket_sessions[socket.id].do_not_encrypt) { if (socket_sessions[socket.id].do_not_encrypt) {
if (headers_obj["wtv-encrypted"]) delete headers_obj["wtv-encrypted"]; if (headers_obj["wtv-encrypted"]) delete headers_obj["wtv-encrypted"];
if (headers_obj["secure"]) delete headers_obj["secure"]; if (headers_obj["secure"]) delete headers_obj["secure"];
}
} }
@@ -1109,77 +1093,72 @@ async function sendToClient(socket, headers_obj, data) {
} }
// Send wtv-ticket if it has been flagged as updated if (!socket.res) {
if (ssid_sessions[socket.ssid]) { // Send wtv-ticket if it has been flagged as updated
if (ssid_sessions[socket.ssid].data_store.wtvsec_login) { if (ssid_sessions[socket.ssid]) {
if (ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64) { if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
if (ssid_sessions[socket.ssid].data_store.wtvsec_login.update_ticket) { if (ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64) {
headers_obj["wtv-ticket"] = ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64; if (ssid_sessions[socket.ssid].data_store.wtvsec_login.update_ticket) {
headers_obj = moveObjectElement("wtv-ticket", "Connection", headers_obj); headers_obj["wtv-ticket"] = ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64;
ssid_sessions[socket.ssid].data_store.wtvsec_login.update_ticket = false; headers_obj = moveObjectElement("wtv-ticket", "Connection", headers_obj);
ssid_sessions[socket.ssid].data_store.wtvsec_login.update_ticket = false;
}
} }
} }
} }
} }
var end_of_line = "\n"; if (!socket.res) {
if (socket.minisrv_pc_mode) { var end_of_line = "\n";
end_of_line = "\r\n";
headers_obj['Response'] = "HTTP/1.0 " + headers_obj['Response'];
}
/* // wtv-request-type download wants minimal headers? // header object to string
if (data.byteLength > 0) { if (minisrv_config.config.debug_flags.show_headers) console.log(" * Outgoing headers on socket ID", socket.id, (await wtvshared.filterSSID(headers_obj)));
if (socket_sessions[socket.id].wtv_request_type == "download") { Object.keys(headers_obj).forEach(function (k) {
if (headers_obj['Content-Type'] != "wtv/download-list") { if (k == "Response") {
// minimalize headers headers += headers_obj[k] + end_of_line;
var new_headers = { "Response": headers_obj['Response'].split(" ")[0] + " " }
if (headers_obj['wtv-encrypted']) new_headers['wtv-encrypted'] = headers_obj['wtv-encrypted'];
new_headers["content-type"] = headers_obj['Content-Type'];
new_headers["content-length"] = headers_obj['Content-length'];
headers_obj = new_headers;
}
}
}
*/
// header object to string
if (minisrv_config.config.debug_flags.show_headers) console.log(" * Outgoing headers on socket ID", socket.id, (await wtvshared.filterSSID(headers_obj)));
Object.keys(headers_obj).forEach(function (k) {
if (k == "Response") {
headers += headers_obj[k] + end_of_line;
} else {
if (k.indexOf('_') >= 0) {
var j = k.split('_')[0];
headers += j + ": " + headers_obj[k] + end_of_line;
} else { } else {
headers += k + ": " + headers_obj[k] + end_of_line; if (k.indexOf('_') >= 0) {
var j = k.split('_')[0];
headers += j + ": " + headers_obj[k] + end_of_line;
} else {
headers += k + ": " + headers_obj[k] + end_of_line;
}
} }
} });
});
if (headers_obj["Connection"]) { if (headers_obj["Connection"]) {
if (headers_obj["Connection"].toLowerCase() == "close" && wtv_connection_close) { if (headers_obj["Connection"].toLowerCase() == "close" && wtv_connection_close) {
socket_sessions[socket.id].destroy_me = true; socket_sessions[socket.id].destroy_me = true;
}
} }
} }
// send to client // send to client
var toClient = null; if (socket.res) {
if (typeof data == 'string') {
toClient = headers + end_of_line + data; var resCode = parseInt(headers_obj.Response.substr(0, 3));
sendToSocket(socket, Buffer.from(toClient)); headers_obj['x-powered-by'] = "Express via " + z_title;
} else if (typeof data == 'object') { socket.res.writeHead(resCode, headers_obj);
if (minisrv_config.config.debug_flags.quiet) var verbosity_mod = (headers_obj["wtv-encrypted"] == 'true') ? " encrypted response" : ""; socket.res.end(data);
if (socket_sessions[socket.id].secure_headers == true) { var log_obj = Object.assign({}, socket.res.getHeaders());
// encrypt headers if (minisrv_config.config.debug_flags.show_headers) console.log(" * Outgoing PC headers on " + socket.service_name + " socket ID", socket.id, log_obj);
if (minisrv_config.config.debug_flags.quiet) verbosity_mod += " with encrypted headers"; } else {
var enc_headers = socket_sessions[socket.id].wtvsec.Encrypt(1, headers + end_of_line); var toClient = null;
sendToSocket(socket, new Buffer.from(concatArrayBuffer(enc_headers, data))); if (typeof data == 'string') {
} else { toClient = headers + end_of_line + data;
sendToSocket(socket, new Buffer.from(concatArrayBuffer(Buffer.from(headers + end_of_line), data))); sendToSocket(socket, Buffer.from(toClient));
} else if (typeof data == 'object') {
if (minisrv_config.config.debug_flags.quiet) var verbosity_mod = (headers_obj["wtv-encrypted"] == 'true') ? " encrypted response" : "";
if (socket_sessions[socket.id].secure_headers == true) {
// encrypt headers
if (minisrv_config.config.debug_flags.quiet) verbosity_mod += " with encrypted headers";
var enc_headers = socket_sessions[socket.id].wtvsec.Encrypt(1, headers + end_of_line);
sendToSocket(socket, new Buffer.from(concatArrayBuffer(enc_headers, data)));
} else {
sendToSocket(socket, new Buffer.from(concatArrayBuffer(Buffer.from(headers + end_of_line), data)));
}
if (minisrv_config.config.debug_flags.quiet) console.log(" * Sent" + verbosity_mod + " " + headers_obj.Response + " to client (Content-Type:", headers_obj['Content-Type'], "~", headers_obj['Content-length'], "bytes)");
} }
if (minisrv_config.config.debug_flags.quiet) console.log(" * Sent" + verbosity_mod + " " + headers_obj.Response + " to client (Content-Type:", headers_obj['Content-Type'], "~", headers_obj['Content-length'], "bytes)");
} }
} }
@@ -1542,132 +1521,125 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
socket_sessions[socket.id].headers = headers; socket_sessions[socket.id].headers = headers;
} }
} else { } else {
if (!socket.ssid) {
// incomplete, dont use https on pc services yet
socket = new tls.TLSSocket(socket);
socket.minisrv_pc_mode = true;
console.log(socket)
console.log("pc https?");
} else {
// handle streaming POST // handle streaming POST
if (socket_sessions[socket.id].expecting_post_data && headers) { if (socket_sessions[socket.id].expecting_post_data && headers) {
socket_sessions[socket.id].headers = headers; socket_sessions[socket.id].headers = headers;
if (socket_sessions[socket.id].post_data.length < (socket_sessions[socket.id].post_data_length * 2)) { if (socket_sessions[socket.id].post_data.length < (socket_sessions[socket.id].post_data_length * 2)) {
new_header_obj = null; new_header_obj = null;
var enc_data = CryptoJS.enc.Hex.parse(data_hex); var enc_data = CryptoJS.enc.Hex.parse(data_hex);
if (socket_sessions[socket.id].secure) { if (socket_sessions[socket.id].secure) {
// decrypt if encrypted // decrypt if encrypted
var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data)) var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data))
} else { } else {
// just pass it over // just pass it over
var dec_data = enc_data; var dec_data = enc_data;
} }
socket_sessions[socket.id].post_data += dec_data.toString(CryptoJS.enc.Hex); socket_sessions[socket.id].post_data += dec_data.toString(CryptoJS.enc.Hex);
var post_string = "POST"; var post_string = "POST";
if (socket_sessions[socket.id].secure == true) post_string = "Encrypted " + post_string; if (socket_sessions[socket.id].secure == true) post_string = "Encrypted " + post_string;
if (minisrv_config.config.post_debug) { if (minisrv_config.config.post_debug) {
// `post_debug` logging of every chunk // `post_debug` logging of every chunk
console.log(" * ", Math.floor(new Date().getTime() / 1000), "Receiving", post_string, "data on", socket.id, "[", socket_sessions[socket.id].post_data.length / 2, "of", socket_sessions[socket.id].post_data_length, "bytes ]"); console.log(" * ", Math.floor(new Date().getTime() / 1000), "Receiving", post_string, "data on", socket.id, "[", socket_sessions[socket.id].post_data.length / 2, "of", socket_sessions[socket.id].post_data_length, "bytes ]");
} else { } else {
// calculate and display percentage of data received // calculate and display percentage of data received
var postPercent = wtvshared.getPercentage(socket_sessions[socket.id].post_data.length, (socket_sessions[socket.id].post_data_length * 2)); var postPercent = wtvshared.getPercentage(socket_sessions[socket.id].post_data.length, (socket_sessions[socket.id].post_data_length * 2));
if (minisrv_config.config.post_percentages) { if (minisrv_config.config.post_percentages) {
if (minisrv_config.config.post_percentages.includes(postPercent)) { if (minisrv_config.config.post_percentages.includes(postPercent)) {
if (!socket_sessions[socket.id].post_data_percents_shown) socket_sessions[socket.id].post_data_percents_shown = new Array(); if (!socket_sessions[socket.id].post_data_percents_shown) socket_sessions[socket.id].post_data_percents_shown = new Array();
if (!socket_sessions[socket.id].post_data_percents_shown[postPercent]) { if (!socket_sessions[socket.id].post_data_percents_shown[postPercent]) {
console.log(" * Received", postPercent, "% of", socket_sessions[socket.id].post_data_length, "bytes on", socket.id, "from", wtvshared.filterSSID(socket.ssid)); console.log(" * Received", postPercent, "% of", socket_sessions[socket.id].post_data_length, "bytes on", socket.id, "from", wtvshared.filterSSID(socket.ssid));
socket_sessions[socket.id].post_data_percents_shown[postPercent] = true; socket_sessions[socket.id].post_data_percents_shown[postPercent] = true;
}
if (postPercent == 100) delete socket_sessions[socket.id].post_data_percents_shown;
} }
if (postPercent == 100) delete socket_sessions[socket.id].post_data_percents_shown;
} }
} }
} }
if (socket_sessions[socket.id].post_data.length == (socket_sessions[socket.id].post_data_length * 2)) { }
// got all expected data if (socket_sessions[socket.id].post_data.length == (socket_sessions[socket.id].post_data_length * 2)) {
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data; // got all expected data
socket.setTimeout(minisrv_config.config.socket_timeout * 1000); if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
headers.post_data = CryptoJS.enc.Hex.parse(socket_sessions[socket.id].post_data); socket.setTimeout(minisrv_config.config.socket_timeout * 1000);
if (socket_sessions[socket.id].secure == true) { headers.post_data = CryptoJS.enc.Hex.parse(socket_sessions[socket.id].post_data);
if (minisrv_config.config.debug_flags.debug) console.log(" # Encrypted POST Content (SECURE ON)", "on", socket.id, "[", headers.post_data.sigBytes, "bytes ]"); if (socket_sessions[socket.id].secure == true) {
if (minisrv_config.config.debug_flags.debug) console.log(" # Encrypted POST Content (SECURE ON)", "on", socket.id, "[", headers.post_data.sigBytes, "bytes ]");
} else {
if (minisrv_config.config.debug_flags.debug) console.log(" # Unencrypted POST Content", "on", socket.id);
}
socket_sessions[socket.id].expecting_post_data = false;
delete socket_sessions[socket.id].headers;
delete socket_sessions[socket.id].post_data;
delete socket_sessions[socket.id].post_data_length;
processURL(socket, headers);
return;
} else if (socket_sessions[socket.id].post_data.length > (socket_sessions[socket.id].post_data_length * 2)) {
socket_sessions[socket.id].expecting_post_data = false;
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
socket.setTimeout(minisrv_config.config.socket_timeout * 1000);
// got too much data ? ... should not ever reach this code
var errpage = wtvshared.doErrorPage(400, "Received too much data in POST request<br>Got " + (socket_sessions[socket.id].post_data.length / 2) + ", expected " + socket_sessions[socket.id].post_data_length);
headers = errpage[0];
data = errpage[1];
sendToClient(socket, headers, data);
return;
}
} else if (!skipSecure) {
if (!encryptedRequest) {
if (socket_sessions[socket.id].secure != true) {
socket_sessions[socket.id].wtvsec = new WTVSec(minisrv_config);
socket_sessions[socket.id].wtvsec.IssueChallenge();
socket_sessions[socket.id].wtvsec.SecureOn();
socket_sessions[socket.id].secure = true;
}
var enc_data = CryptoJS.enc.Hex.parse(data_hex);
if (enc_data.sigBytes > 0) {
if (!socket_sessions[socket.id].wtvsec) {
var errpage = wtvshared.doErrorPage(400);
var headers = errpage[0];
headers += "wtv-visit: client:relog\n";
data = errpage[1];
sendToClient(socket, headers, data);
return;
}
var str_test = enc_data.toString(CryptoJS.enc.Latin1);
if (isUnencryptedString(str_test)) {
var dec_data = enc_data;
} else { } else {
if (minisrv_config.config.debug_flags.debug) console.log(" # Unencrypted POST Content", "on", socket.id); var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data));
} }
socket_sessions[socket.id].expecting_post_data = false; if (!socket_sessions[socket.id].secure_buffer) socket_sessions[socket.id].secure_buffer = "";
delete socket_sessions[socket.id].headers; socket_sessions[socket.id].secure_buffer += dec_data.toString(CryptoJS.enc.Hex);
delete socket_sessions[socket.id].post_data; var secure_headers = null;
delete socket_sessions[socket.id].post_data_length; if (headers['request']) {
processURL(socket, headers); if (headers['request'] == "GET") {
return; if (socket_sessions[socket.id].secure_buffer.indexOf("0d0a0d0a") || socket_sessions[socket.id].secure_buffer.indexOf("0a0a")) {
} else if (socket_sessions[socket.id].post_data.length > (socket_sessions[socket.id].post_data_length * 2)) { secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true);
socket_sessions[socket.id].expecting_post_data = false;
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
socket.setTimeout(minisrv_config.config.socket_timeout * 1000);
// got too much data ? ... should not ever reach this code
var errpage = wtvshared.doErrorPage(400, "Received too much data in POST request<br>Got " + (socket_sessions[socket.id].post_data.length / 2) + ", expected " + socket_sessions[socket.id].post_data_length);
headers = errpage[0];
data = errpage[1];
sendToClient(socket, headers, data);
return;
}
} else if (!skipSecure) {
if (!encryptedRequest) {
if (socket_sessions[socket.id].secure != true) {
socket_sessions[socket.id].wtvsec = new WTVSec(minisrv_config);
socket_sessions[socket.id].wtvsec.IssueChallenge();
socket_sessions[socket.id].wtvsec.SecureOn();
socket_sessions[socket.id].secure = true;
}
var enc_data = CryptoJS.enc.Hex.parse(data_hex);
if (enc_data.sigBytes > 0) {
if (!socket_sessions[socket.id].wtvsec) {
var errpage = wtvshared.doErrorPage(400);
var headers = errpage[0];
headers += "wtv-visit: client:relog\n";
data = errpage[1];
sendToClient(socket, headers, data);
return;
}
var str_test = enc_data.toString(CryptoJS.enc.Latin1);
if (isUnencryptedString(str_test)) {
var dec_data = enc_data;
} else {
var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data));
}
if (!socket_sessions[socket.id].secure_buffer) socket_sessions[socket.id].secure_buffer = "";
socket_sessions[socket.id].secure_buffer += dec_data.toString(CryptoJS.enc.Hex);
var secure_headers = null;
if (headers['request']) {
if (headers['request'] == "GET") {
if (socket_sessions[socket.id].secure_buffer.indexOf("0d0a0d0a") || socket_sessions[socket.id].secure_buffer.indexOf("0a0a")) {
secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true);
}
} else {
var secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true);
} }
} else { } else {
var secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true); var secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true);
} }
if (secure_headers) { } else {
delete socket_sessions[socket.id].secure_buffer; var secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true);
if (!headers) headers = new Array(); }
headers.encrypted = true; if (secure_headers) {
Object.keys(secure_headers).forEach(function (k, v) { delete socket_sessions[socket.id].secure_buffer;
headers[k] = secure_headers[k]; if (!headers) headers = new Array();
}); headers.encrypted = true;
if (headers['request']) { Object.keys(secure_headers).forEach(function (k, v) {
if (headers['request'].substring(0, 4) == "POST") { headers[k] = secure_headers[k];
if (!socket_sessions[socket.id].post_data) { });
socket_sessions[socket.id].post_data_length = headers['Content-length'] || headers['Content-Length'] || 0; if (headers['request']) {
socket_sessions[socket.id].post_data = ""; if (headers['request'].substring(0, 4) == "POST") {
} if (!socket_sessions[socket.id].post_data) {
processRequest(socket, dec_data.toString(CryptoJS.enc.Hex)); socket_sessions[socket.id].post_data_length = headers['Content-length'] || headers['Content-Length'] || 0;
} else { socket_sessions[socket.id].post_data = "";
processURL(socket, headers);
} }
processRequest(socket, dec_data.toString(CryptoJS.enc.Hex));
} else {
processURL(socket, headers);
} }
} }
} }
@@ -1784,8 +1756,8 @@ function reloadConfig() {
// SERVER START // SERVER START
var git_commit = getGitRevision() var git_commit = getGitRevision()
var z_title = "zefie's wtv minisrv v" + require('./package.json').version; var z_title = "zefie's wtv minisrv v" + require('./package.json').version;
if (git_commit) console.log("**** Welcome to " + z_title + " (git " + git_commit + ") ****"); if (git_commit) z_title += " (git " + git_commit + ")";
else console.log("**** Welcome to " + z_title + " ****"); console.log("**** Welcome to " + z_title + " ****");
const wtvshared = new WTVShared(); // creates minisrv_config const wtvshared = new WTVShared(); // creates minisrv_config
minisrv_config = wtvshared.getMiniSrvConfig(); // snatches minisrv_config minisrv_config = wtvshared.getMiniSrvConfig(); // snatches minisrv_config
@@ -1834,7 +1806,8 @@ Object.keys(minisrv_config.services).forEach(function (k) {
minisrv_config.services[k].host = service_ip; minisrv_config.services[k].host = service_ip;
} }
if (minisrv_config.services[k].port && !minisrv_config.services[k].nobind) { if (minisrv_config.services[k].port && !minisrv_config.services[k].nobind) {
ports.push(minisrv_config.services[k].port); if (minisrv_config.services[k].pc_services) pc_ports.push(minisrv_config.services[k].port);
else ports.push(minisrv_config.services[k].port);
} }
// minisrv_config service toString // minisrv_config service toString
minisrv_config.services[k].toString = function (overrides) { minisrv_config.services[k].toString = function (overrides) {
@@ -1892,8 +1865,22 @@ process.on('uncaughtException', function (err) {
}); });
function findServiceByPort(port) {
var service_name = null;
Object.keys(minisrv_config.services).forEach(function (k) {
if (service_name) return;
if (minisrv_config.services[k].port) {
if (port == parseInt(minisrv_config.services[k].port))
service_name = k;
}
})
return service_name;
}
var initstring = ''; var initstring = '';
ports.sort(); ports.sort();
pc_ports.sort();
// de-duplicate ports in case user configured multiple services on same port // de-duplicate ports in case user configured multiple services on same port
const bind_ports = [...new Set(ports)] const bind_ports = [...new Set(ports)]
@@ -1909,6 +1896,40 @@ bind_ports.every(function (v) {
} }
return false; return false;
}); });
// PC Services via express
// de-duplicate ports in case user configured multiple services on same port
const pc_bind_ports = [...new Set(pc_ports)]
if (!minisrv_config.config.bind_ip) minisrv_config.config.bind_ip = "0.0.0.0";
pc_bind_ports.every(function (v) {
try {
var server = express();
server.listen(v, minisrv_config.config.bind_ip);
initstring += v + ", ";
server.get('*', (req, res) => {
var request_headers = {};
req.socket.id = parseInt(crc16('CCITT-FALSE', Buffer.from(String(req.socket.remoteAddress) + String(req.socket.remotePort), "utf8")).toString(16), 16);
socket_sessions[req.socket.id] = [];
var service_name = findServiceByPort(v);
request_headers['request'] = "GET " + req.originalUrl + " HTTP/1.1";
request_headers.request_url = req.originalUrl;
Object.keys(req.headers).forEach(function (k) {
request_headers[k] = req.headers[k];
});
request_headers.query = req.query;
if (minisrv_config.config.debug_flags.show_headers) console.log(" * Incoming PC Headers on", service_name, "socket ID", req.socket.id, wtvshared.filterRequestLog(request_headers));
request_headers.service_name = service_name;
req.socket.minisrv_pc_mode = true;
req.socket.res = res;
req.socket.service_name = service_name;
processURL(req.socket, request_headers, true)
})
return true;
} catch (e) {
throw ("Could not bind to port", v, "on", minisrv_config.config.bind_ip, e.toString());
}
return false;
});
initstring = initstring.substring(0, initstring.length - 2); initstring = initstring.substring(0, initstring.length - 2);

View File

@@ -155,6 +155,8 @@
}, },
"pc_services": { "pc_services": {
"port": 1699, "port": 1699,
"pc_services": true,
"hide_minisrv_version": true,
"disabled": true, "disabled": true,
"servicevault_dir": "http_pc", "servicevault_dir": "http_pc",
"drop_connection_on_wrong_port": false, "drop_connection_on_wrong_port": false,

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "zefie_wtvp_minisrv", "name": "zefie_wtvp_minisrv",
"version": "0.9.31", "version": "0.9.32",
"description": "WebTV Service (WTVP) Emulation Server", "description": "WebTV Service (WTVP) Emulation Server",
"main": "app.js", "main": "app.js",
"homepage": "https://github.com/zefie/zefie_wtvp_minisrv", "homepage": "https://github.com/zefie/zefie_wtvp_minisrv",
@@ -31,6 +31,7 @@
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"easy-crc": "0.0.2", "easy-crc": "0.0.2",
"endianness": "^8.0.2", "endianness": "^8.0.2",
"express": "^4.18.2",
"html-entities": "^2.3.3", "html-entities": "^2.3.3",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",