From 4a2dc1680db14f76649595cdf6f0e08d05ce0ce2 Mon Sep 17 00:00:00 2001 From: zefie Date: Mon, 9 Aug 2021 17:31:31 -0400 Subject: [PATCH] add catchall system & http pc server - define a catchall name to run globally or per service - catchall must be javascript, but not necessarily a .js file - catchall can request async mode - catchall will catch any non-existing requests under its directory - see wtv-flashrom:/content/content-serve.js as an example, which will catch wtv-flashrom:/content/ URLs. - http pc: sends HTTP/1.0 to PC clients - can be disabled with `pc_server_hidden_service_enabled`: false - can change servicevault path by changing string of pc_server_hidden_service - get.js in default PC service vault to get any WTV Url on the service --- user_config_README.md | 4 + .../ServiceVault/http_pc/get.js | 37 +++++ .../ServiceVault/http_pc/index.js | 31 +++++ .../wtv-flashrom/content/content-serve.js | 23 ++++ zefie_wtvp_minisrv/WTVFlashrom.js | 2 +- zefie_wtvp_minisrv/app.js | 127 +++++++++++++++--- zefie_wtvp_minisrv/config.json | 6 +- zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj | 5 + 8 files changed, 213 insertions(+), 22 deletions(-) create mode 100644 zefie_wtvp_minisrv/ServiceVault/http_pc/get.js create mode 100644 zefie_wtvp_minisrv/ServiceVault/http_pc/index.js create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/content/content-serve.js diff --git a/user_config_README.md b/user_config_README.md index 392e5398..7474264f 100644 --- a/user_config_README.md +++ b/user_config_README.md @@ -22,6 +22,10 @@ If you would like to see debug information about realtime bytes received from a "allow_guests": false ``` If you would like to require registration, disabling guest mode, you can set `allow_guests` to `false`. Default is `true`; +``` + "pc_server_hidden_service": false +``` +Set this option to false to disable the HTTP Server for Browsers. Set it to a string to use that directory under the service vaults. ``` "post_percentages": [ 0, 25, 50, 100] ``` diff --git a/zefie_wtvp_minisrv/ServiceVault/http_pc/get.js b/zefie_wtvp_minisrv/ServiceVault/http_pc/get.js new file mode 100644 index 00000000..658b975a --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/http_pc/get.js @@ -0,0 +1,37 @@ +if (request_headers.query.url) { + if (request_headers.query.url.indexOf(":/") > 0) { + var service_request = request_headers.query.url.split(":/")[0]; + var service_port = 0; + Object.keys(minisrv_config.services).forEach(function (k) { + if (minisrv_config.services[k].disabled) return; + if (k == service_request) service_port = minisrv_config.services[k].port; + }); + if (service_port > 0) { + request_is_async = true; + var request_headers_out = new Array() + request_headers_out.request = "GET " + request_headers.query.url; + request_headers_out.request_url = request_headers.query.url; + request_headers_out['wtv-client-serial-number'] = socket.id + "HTTPPCReq"; + processURL(socket, request_headers_out); +/* + var s = require('net').Socket(); + var outdata = ""; + s.connect(service_port); + s.setTimeout(1, function () { + outdata = outdata.split() + sendToClient(socket,outdata); + }); + s.on('data', function (data) { + outdata += data; + }); + s.write() +*/ + } + } +} + +if (!headers) { + var errpage = doErrorPage(500) + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/http_pc/index.js b/zefie_wtvp_minisrv/ServiceVault/http_pc/index.js new file mode 100644 index 00000000..802a1f4b --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/http_pc/index.js @@ -0,0 +1,31 @@ +headers = `200 OK +Content-Type: text/html` + +var splash_logo = minisrv_config.config.service_splash_logo; +if (splash_logo.substring(0, 4) == "file") splash_logo = "wtv-star:/ROMCache/splash_logo_hacktv.gif"; + +data = ` + + +zefie minisrv ${minisrv_config.version} + + + +
+ +
+ +


+


+


+ +
+Mini service +
+zefie minisrv v${minisrv_config.version}`; +if (minisrv_config.config.git_commit) data += " (git " + minisrv_config.config.git_commit + ")"; +data += ` +
+

+ +`; \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/content/content-serve.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/content/content-serve.js new file mode 100644 index 00000000..b8209c2a --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/content/content-serve.js @@ -0,0 +1,23 @@ +const WTVFlashrom = require("./WTVFlashrom.js"); +request_is_async = true; +console.log(request_headers); + +var bf0app_update = false; +var request_path = request_headers.request_url.replace(service_name + ":/", ""); +var romtype = ssid_sessions[socket.ssid].get("wtv-client-rom-type"); +var bootver = ssid_sessions[socket.ssid].get("wtv-client-bootrom-version") + +if ((romtype == "bf0app" || !romtype) && (bootver == "105" || !bootver)) { + // assume old classic in flash mode, override user setting and send tellyscript + // because it is required to proceed in flash mode + bf0app_update = true; + ssid_sessions[socket.ssid].set("bf0app_update", bf0app_update); +} + +if (!ssid_sessions[socket.ssid].data_store.WTVFlashrom) { + ssid_sessions[socket.ssid].data_store.WTVFlashrom = new WTVFlashrom(service_vaults, service_name, minisrv_config.services[service_name].use_zefie_server, bf0app_update, minisrv_config.services[service_name].debug); +} + +ssid_sessions[socket.ssid].data_store.WTVFlashrom.getFlashRom(request_path, function (data, headers) { + sendToClient(socket, headers, data); +}); diff --git a/zefie_wtvp_minisrv/WTVFlashrom.js b/zefie_wtvp_minisrv/WTVFlashrom.js index 478e5629..5b69b01f 100644 --- a/zefie_wtvp_minisrv/WTVFlashrom.js +++ b/zefie_wtvp_minisrv/WTVFlashrom.js @@ -117,7 +117,7 @@ class WTVFlashrom { flashrom_info.message = new Buffer.from(part_header.toString('hex').substring(36 * 2, 68 * 2), 'hex').toString('ascii').replace(/[^0-9a-z\ \.\-]/gi, ""); flashrom_info.is_last_part = ((flashrom_info.byte_progress + flashrom_info.part_total_size) == flashrom_info.total_parts_size) ? true : false; - flashrom_info.rompath = 'wtv-flashrom:/get-by-path?path=' + path + '&raw=true'; + flashrom_info.rompath = `wtv-flashrom:/${path}`; if (this.zdebug) console.log(" # Flashrom Part Bytes Sent (after this part):", flashrom_info.byte_progress + flashrom_info.part_total_size); if (this.zdebug) console.log(" # Flashrom Part is Last Part", flashrom_info.is_last_part); diff --git a/zefie_wtvp_minisrv/app.js b/zefie_wtvp_minisrv/app.js index 0f5942dd..08af903f 100644 --- a/zefie_wtvp_minisrv/app.js +++ b/zefie_wtvp_minisrv/app.js @@ -74,24 +74,31 @@ function getFileExt(path) { return path.reverse().split(".")[0].reverse(); } -function doErrorPage(code, data = null) { +function doErrorPage(code, data = null, pc_mode = false) { var headers = null; switch (code) { case 404: if (data === null) data = "The service could not find the requested page."; - headers = "404 " + data + "\r\n"; - headers += "Content-Type: text/html\r\n"; + if (pc_mode) headers = "404 Not Found\n"; + else headers = code + " "+ data + "\n"; + headers += "Content-Type: text/html\n"; break; case 400: + case 500: if (data === null) data = "HackTV ran into a technical problem."; - headers = "400 " + data + "\r\n"; - headers += "Content-Type: text/html\r\n"; + if (pc_mode) headers = "500 Internal Server Error\n"; + else headers = code + " " + data + "\n"; + headers += "Content-Type: text/html\n"; + break; + case 401: + if (data === null) data = "Access Denied."; + if (pc_mode) headers = "401 Access Denied\n"; + else headers = code + " " + data + "\n"; + headers += "Content-Type: text/html\n"; break; default: - // what we send when we did not detect a wtv-url. - // e.g. when a pc browser connects - headers = "HTTP/1.1 200 OK\r\n"; - headers += "Content-Type: text/html\r\n"; + headers = code + " " + data + "\n"; + headers += "Content-Type: text/html\n"; break; } console.error("doErrorPage Called:", code, data); @@ -175,8 +182,25 @@ async function processPath(socket, service_vault_file_path, request_headers = ne try { service_vaults.forEach(function (service_vault_dir) { if (service_vault_found) return; - service_vault_file_path = makeSafePath(service_vault_dir,service_path); + service_vault_file_path = makeSafePath(service_vault_dir, service_path); + // deny access to catchall file name directly + var service_path_split = service_path.split("/"); + var service_path_request_file = service_path_split[service_path_split.length - 1]; + if (minisrv_config.config.catchall_file_name) { + var minisrv_catchall = null; + if (minisrv_config.services[service_name]) minisrv_catchall = minisrv_config.services[service_name].catchall_file_name || minisrv_config.config.catchall_file_name || null; + else minisrv_catchall = minisrv_config.config.catchall_file_name || null; + if (minisrv_catchall) { + if (service_path_request_file == minisrv_catchall) { + var errpage = doErrorPage(401, "Access Denied"); + headers = errpage[0]; + data = errpage[1]; + return; + } + } + } + minisrv_catchall, service_path_split, service_path_request_file = null; if (fs.existsSync(service_vault_file_path)) { // file exists, read it and return it @@ -242,6 +266,31 @@ async function processPath(socket, service_vault_file_path, request_headers = ne fs.readFile(service_vault_file_path + ".html", null, function (err, data) { sendToClient(socket, headers, data); }); + } else { + // look for a catchallin the current path and all parent paths up until the service root + if (minisrv_config.config.catchall_file_name) { + var minisrv_catchall_file_name = null; + if (minisrv_config.services[service_name]) minisrv_catchall_file_name = minisrv_config.services[service_name].catchall_file_name || minisrv_config.config.catchall_file_name || null; + else minisrv_catchall_file_name = minisrv_config.config.catchall_file_name || null; + if (minisrv_catchall_file_name) { + var service_check_dir = service_vault_file_path.split(path.sep); + service_check_dir.pop(); // pop filename + + while (service_check_dir.join(path.sep) != service_vault_dir) { + var catchall_file = service_check_dir.join(path.sep) + path.sep + minisrv_catchall_file_name; + if (fs.existsSync(catchall_file)) { + if (!zquiet) console.log(" * Found catchall at " + catchall_file + ".html to handle request (HTML Mode) [Socket " + socket.id + "]"); + var jscript_eval = fs.readFileSync(catchall_file).toString(); + // don't pass these vars to the script + var service_check_dir, minisrv_catchall_file_name = null; + eval(jscript_eval); + if (request_is_async && !zquiet) console.log(" * Script requested Asynchronous mode"); + } else { + service_check_dir.pop(); + } + } + } + } } // either `request_is_async`, or `headers` and `data` MUST be defined by this point! }); @@ -254,12 +303,12 @@ async function processPath(socket, service_vault_file_path, request_headers = ne if (!request_is_async) { if (!service_vault_found) { console.error(" * Could not find a Service Vault for " + service_name + ":/" + service_path.replace(service_name + path.sep, "")); - var errpage = doErrorPage(404); + var errpage = doErrorPage(404, null, socket.minisrv_pc_mode); headers = errpage[0]; data = errpage[1]; } if (headers == null && !request_is_async) { - var errpage = doErrorPage(400); + var errpage = doErrorPage(400, null, socket.minisrv_pc_mode); headers = errpage[0]; data = errpage[1]; console.error(" * Scripting or Data error: Headers were not defined. (headers,data) as follows:") @@ -300,7 +349,7 @@ function filterSSID(obj) { } } -function makeSafeSSID(ssid) { +function makeSafeSSID(ssid = "") { ssid = ssid.replace(/[^a-zA-Z0-9]/g, ""); if (ssid.length == 0) ssid = null; return ssid; @@ -313,10 +362,21 @@ function makeSafePath(base, target) { return base + path.sep + targetPath; } -async function processURL(socket, request_headers) { - if (request_headers === null) { - return; +function getServiceBySocket(socket) { + var socket_port = socket.server._connectionKey; + if (socket_port) { + socket_port = socket.server._connectionKey.split(":")[2]; + var service_name = null; + Object.keys(minisrv_config.services).forEach(function (k) { + if (minisrv_config.services[k].disabled) return; + if (minisrv_config.services[k].port == socket_port) service_name = k; + }); + return service_name; } + return null; +} + +async function processURL(socket, request_headers) { var shortURL, headers, data = ""; request_headers.query = new Array(); if (request_headers.request_url) { @@ -372,6 +432,32 @@ async function processURL(socket, request_headers) { shortURL_split.shift(); var shortURL_service_path = shortURL_split.join(":"); shortURL = shortURL_service_name + ":/" + shortURL_service_path; + } else if (shortURL.indexOf(":") == -1 && request_headers.request.indexOf("HTTP/1") > 0) { + // is this an odd WTVP request or HTTP Request? + if (request_headers.Host) { + if (minisrv_config.config.pc_server_hidden_service_enabled) { + // browsers typically send a Host header + service_name = minisrv_config.config.pc_server_hidden_service; + socket.minisrv_pc_mode = true; + shortURL = service_name + ":" + shortURL; + + // if a directory, request index + if (shortURL.substring(shortURL.length - 1) == "/") shortURL += "index"; + } else { + // minimal pc mode to send error + socket.minisrv_pc_mode = true; + var errpage = 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 { + // look up service name by socket port + service_name = getServiceBySocket(socket); + shortURL = service_name + ":" + shortURL; + } } if (shortURL.indexOf(':/') >= 0 && shortURL.indexOf('://') < 0) { @@ -744,10 +830,10 @@ async function sendToClient(socket, headers_obj, data) { } var end_of_line = "\n"; - if (headers_obj['minisrv-use-carriage-return'] == "true") end_of_line = "\r\n"; - if (headers_obj['minisrv-use-carriage-return']) delete headers_obj['minisrv-use-carriage-return']; - - if (end_of_line == "\r\n" && zdebug) console.log(" * Script requested to send headers with carriage return (out of WTVP Spec)"); + if (socket.minisrv_pc_mode) { + end_of_line = "\r\n"; + headers_obj['http_response'] = "HTTP/1.0 " + headers_obj['http_response']; + } // header object to string if (zshowheaders) console.log(" * Outgoing headers on socket ID", socket.id, (await filterSSID(headers_obj))); @@ -1359,6 +1445,7 @@ async function handleSocket(socket) { // create unique socket id with client address and port socket.id = parseInt(crc16('CCITT-FALSE', Buffer.from(String(socket.remoteAddress) + String(socket.remotePort), "utf8")).toString(16), 16); socket_sessions[socket.id] = []; + socket.minisrv_pc_mode = false; socket.setEncoding('hex'); //set data encoding (Text: 'ascii', 'utf8' ~ Binary: 'hex', 'base64' (do not trust 'binary' encoding)) socket.setTimeout(10800000); // 3 hours socket.on('data', function (data_hex) { diff --git a/zefie_wtvp_minisrv/config.json b/zefie_wtvp_minisrv/config.json index 1ce17dd8..6cad0dfb 100644 --- a/zefie_wtvp_minisrv/config.json +++ b/zefie_wtvp_minisrv/config.json @@ -14,8 +14,11 @@ "post_percentages": [ 0, 25, 50, 100 ], "verbosity": 2, "error_log_file": "errors.log", + "catchall_file_name": "catchall.js", "enable_lzpf_compression": false, "enable_gzip_compression": true, + "pc_server_hidden_service": "http_pc", + "pc_server_hidden_service_enabled": false, "allow_guests": true }, "services": { @@ -53,7 +56,8 @@ "flags": "0x00000040", "debug": false, "use_zefie_server": true, - "bf0app_default_rom": "content/artemis-webtv-000/build7181/daily-nondebug/bf0app-part000.rom" + "bf0app_default_rom": "content/artemis-webtv-000/build7181/daily-nondebug/bf0app-part000.rom", + "catchall_file_name": "content-serve.js" }, "wtv-setup": { "port": 1613, diff --git a/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj b/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj index cd03b72f..ad4244e0 100644 --- a/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj +++ b/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj @@ -32,6 +32,8 @@ + + @@ -50,6 +52,7 @@ Code + Code @@ -289,10 +292,12 @@ + +