- numerous bug fixes
 - add UTV tellyscripts
 - BREAKING CHANGE: doErrorPage moved to wtvshared.doErrorPage
	- but I fixed flashrom error handling..
	- added a passthrough for old scripts
 - added eMac's lzpf compression
 - Bump vm2 from 3.9.3 to 3.9.5 in /zefie_wtvp_minisrv
This commit is contained in:
zefie
2021-08-13 17:39:14 -04:00
parent 72c15305fa
commit 245e126298
57 changed files with 740 additions and 509 deletions

View File

@@ -1,7 +1,7 @@
# wtv minisrv node.js
The ***wtv minisrv***, or "***zefie_wtvp_minisrv***" project is a node.js project that provides a mini WebTV Server, aiming for full WTVP (WebTV Protocol) support.
This open source server is in alpha status. Use at your own risk.
This open source server is in beta status. Use at your own risk.
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
@@ -14,22 +14,20 @@ This open source server is in alpha status. Use at your own risk.
- WebTV cookie support (wtv-cookie) for HTTP(s)
- Flashrom flashing support for all known units (including bf0app 'Old Classic')
- Can flash anything on [Ultra Willies](https://wtv.zefie.com/willie.php) with optional `use_zefie_server` flag set on `wtv-flashrom` service.
- `wtv-update:/sync` for Download-o-Rama style file downloading
- `wtv-disk:/sync` for Download-o-Rama style file downloading
- Custom Tellyscripts *(not yet customizable though)*
- Flat file client session store and registration system
- wtv-lzpf compression support by eMac (99.9%)
### Current issues:
- wtv-cookie implementation is still partial
- wtv-lzpf compression support is not yet reliable
- Mis-configuring wtv-update:/sync DiskMaps may cause units to delete contents of partitions (need more info)
- Satellite Receiver units reportedly cannot surf with http or https proxy. *(May be fixed by current partial wtv-cookie implementation)*
- Mis-configuring wtv-disk:/sync DiskMaps may cause units to delete contents of partitions (need more info)
### Won't fix:
- wtv-encryption stream breaks when two different sessions have the same SSID (eg spoofing, won't fix (production did it too))
- ~~No intentions to support user accounts, registration, or any form of database system~~ *(I guess this was a lie, but we still don't use a database!)*
### Feature Todo:
- wtv-lzpf support *(Milestone v1.0)*
- wtv-setup and bgm support
- TellyScript generation and/or manipulation without external dependancies
- ~~wtv-cookie full support~~ ***Done [v0.9.13](https://github.com/zefie/zefie_wtvp_minisrv/releases/tag/v0.9.13)***
- ~~Flashrom flashing for bf0app old classic~~ ***Done [v0.9.9](https://github.com/zefie/zefie_wtvp_minisrv/releases/tag/v0.9.9)***

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
if (request_headers.query.url) {
if (request_headers.query.url.indexOf(":/") > 0) {
var service_request = request_headers.query.url.split(":/")[0];
@@ -31,7 +33,7 @@ if (request_headers.query.url) {
}
if (!headers) {
var errpage = doErrorPage(500)
var errpage = wtvshared.doErrorPage(500)
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
Content-Type: text/html`

View File

@@ -1,7 +1,9 @@
var minisrv_service_file = true;
if (socket.ssid != null && !ssid_sessions[socket.ssid].get("wtvsec_login")) {
var wtvsec_login = new WTVSec(minisrv_config);
wtvsec_login.IssueChallenge();
wtvsec_login.set_incarnation(request_headers["wtv-incarnation"]);
if (request_headers["wtv-incarnation"]) wtvsec_login.set_incarnation(request_headers["wtv-incarnation"]);
ssid_sessions[socket.ssid].set("wtvsec_login", wtvsec_login);
} else if (socket.ssid != null) {
var wtvsec_login = ssid_sessions[socket.ssid].get("wtvsec_login");

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
var gourl = "wtv-1800:/finish-prereg?";
if (request_headers.query.relogin) gourl += "relogin=true";

View File

@@ -1,45 +1,47 @@
var gourl = "wtv-head-waiter:/login?";
var minisrv_service_file = true;
if (socket.ssid) {
if (ssid_sessions[socket.ssid].loadSessionData() == true) {
console.log(" * Loaded session data from disk for", wtvshared.filterSSID(socket.ssid))
ssid_sessions[socket.ssid].setSessionData("registered", (ssid_sessions[socket.ssid].getSessionData("registered") == true) ? true : false);
} else {
ssid_sessions[socket.ssid].session_data = {};
ssid_sessions[socket.ssid].setSessionData("registered", false);
}
if (ssid_sessions[socket.ssid].data_store) {
if (ssid_sessions[socket.ssid].data_store.sockets) {
var i = 0;
ssid_sessions[socket.ssid].data_store.sockets.forEach(function (k) {
if (typeof k != "undefined") {
if (k != socket) {
k.destroy();
ssid_sessions[socket.ssid].data_store.sockets.delete(k);
i++;
}
}
});
if (i > 0 && minisrv_config.config.debug_flags.debug) console.log(" # Closed", i, "previous sockets for", wtvshared.filterSSID(socket.ssid));
}
}
if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
if (minisrv_config.config.debug_flags.debug) console.log(" # Recreating primary WTVSec login instance for", wtvshared.filterSSID(socket.ssid));
delete ssid_sessions[socket.ssid].data_store.wtvsec_login;
}
var gourl = "wtv-head-waiter:/login?";
ssid_sessions[socket.ssid].data_store.wtvsec_login = new WTVSec(minisrv_config);
ssid_sessions[socket.ssid].data_store.wtvsec_login.IssueChallenge();
ssid_sessions[socket.ssid].data_store.wtvsec_login.set_incarnation(request_headers["wtv-incarnation"] || 1);
if (socket.ssid) {
if (ssid_sessions[socket.ssid].loadSessionData() == true) {
console.log(" * Loaded session data from disk for", wtvshared.filterSSID(socket.ssid))
ssid_sessions[socket.ssid].setSessionData("registered", (ssid_sessions[socket.ssid].getSessionData("registered") == true) ? true : false);
} else {
console.log(" * Something bad happened (we don't know the client ssid???)");
var errpage = doErrorPage(400)
headers = errpage[0];
data = errpage[1];
ssid_sessions[socket.ssid].session_data = {};
ssid_sessions[socket.ssid].setSessionData("registered", false);
}
if (ssid_sessions[socket.ssid].data_store) {
if (ssid_sessions[socket.ssid].data_store.sockets) {
var i = 0;
ssid_sessions[socket.ssid].data_store.sockets.forEach(function (k) {
if (typeof k != "undefined") {
if (k != socket) {
k.destroy();
ssid_sessions[socket.ssid].data_store.sockets.delete(k);
i++;
}
}
});
if (i > 0 && minisrv_config.config.debug_flags.debug) console.log(" # Closed", i, "previous sockets for", wtvshared.filterSSID(socket.ssid));
}
}
if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
if (minisrv_config.config.debug_flags.debug) console.log(" # Recreating primary WTVSec login instance for", wtvshared.filterSSID(socket.ssid));
delete ssid_sessions[socket.ssid].data_store.wtvsec_login;
}
if (request_headers.query.relogin && ssid_sessions[socket.ssid].getSessionData("registered")) gourl += "relogin=true";
if (request_headers.query.reconnect && ssid_sessions[socket.ssid].getSessionData("registered")) gourl += "reconnect=true";
ssid_sessions[socket.ssid].data_store.wtvsec_login = new WTVSec(minisrv_config);
ssid_sessions[socket.ssid].data_store.wtvsec_login.IssueChallenge();
if (request_headers["wtv-incarnation"]) ssid_sessions[socket.ssid].data_store.wtvsec_login.set_incarnation(request_headers["wtv-incarnation"]);
} else {
console.log(" * Something bad happened (we don't know the client ssid???)");
var errpage = wtvshared.doErrorPage(400)
headers = errpage[0];
data = errpage[1];
}
if (request_headers.query.relogin && ssid_sessions[socket.ssid].getSessionData("registered")) gourl += "relogin=true";
if (request_headers.query.reconnect && ssid_sessions[socket.ssid].getSessionData("registered")) gourl += "reconnect=true";
if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
var prereg_contype = "text/html";
@@ -66,7 +68,7 @@ if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
} else {
romtype = ssid_sessions[socket.ssid].get("wtv-client-rom-type");
}
var file_path = null;
switch (romtype) {
case "US-LC2-disk-0MB-8MB":
case "US-LC2-disk-0MB-8MB-softmodem-CPU5230":
@@ -75,15 +77,30 @@ if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
case "US-WEBSTAR-disk-0MB-16MB-softmodem-CPU5230":
prereg_contype = "text/tellyscript";
// if wtv-open-access: true then client expects OpenISP
if (ssid_sessions[socket.ssid].get("wtv-open-access")) var file_path = __dirname + "/ServiceDeps/premade_tellyscripts/LC2/LC2_OpenISP_56k.tok";
if (ssid_sessions[socket.ssid].get("wtv-open-access")) file_path = __dirname + "/ServiceDeps/premade_tellyscripts/LC2/LC2_OpenISP_56k.tok";
else var file_path = __dirname + "/ServiceDeps/premade_tellyscripts/LC2/LC2_WTV_18006138199.tok";
break;
case "US-DTV-disk-0MB-32MB-softmodem-CPU5230":
if (wtvshared.isMiniBrowser()) {
prereg_contype = "text/tellyscript";
if (ssid_sessions[socket.ssid].get("wtv-open-access")) file_path = __dirname + "/ServiceDeps/premade_tellyscripts/LC2/LC2_OpenISP_56k.tok";
else file_path = __dirname + "/ServiceDeps/premade_tellyscripts/LC2/LC2_WTV_18006138199.tok";
} else {
prereg_contype = "text/dialscript";
if (ssid_sessions[socket.ssid].get("wtv-lan") == "true") {
file_path = __dirname + "/ServiceDeps/premade_tellyscripts/UTV/utv_hsd.tok";
} else {
// todo OpenISP telly
file_path = __dirname + "/ServiceDeps/premade_tellyscripts/UTV/utv_normal.tok";
}
}
case "bf0app":
prereg_contype = "text/tellyscript";
// if wtv-open-access: true then client expects OpenISP
if (ssid_sessions[socket.ssid].get("wtv-open-access")) var file_path = __dirname + "/ServiceDeps/premade_tellyscripts/bf0app/bf0app_OISP.tok";
else var file_path = __dirname + "/ServiceDeps/premade_tellyscripts/bf0app/bf0app_WTV_18006138199.tok";
if (ssid_sessions[socket.ssid].get("wtv-open-access")) file_path = __dirname + "/ServiceDeps/premade_tellyscripts/bf0app/bf0app_OISP.tok";
else file_path = __dirname + "/ServiceDeps/premade_tellyscripts/bf0app/bf0app_WTV_18006138199.tok";
break;
// the following are not yet zefie generated and may have an unknown username/password attached
@@ -99,6 +116,11 @@ if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
data = '';
break;
}
if (socket.ssid.substr(0, 8) == "MSTVSIMU") {
prereg_contype = "text/dialscript";
var file_path = __dirname + "/ServiceDeps/premade_tellyscripts/UTV/utv_hsd.tok";
}
}
@@ -157,7 +179,7 @@ if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
request_is_async = true;
fs.readFile(file_path, null, function (err, file_read_data) {
if (err) {
var errmsg = doErrorPage(400);
var errmsg = wtvshared.doErrorPage(400);
headers = errmsg[0];
file_read_data = errmsg[1] + "\n" + err.toString();
}
@@ -165,7 +187,7 @@ if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
});
}
} else {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = "200 OK";
if (request_headers.query.nick) headers += "\n" + ssid_sessions[socket.ssid].setIRCNick(request_headers.query.nick);
headers += "\nContent-Type: text/html";
@@ -148,7 +150,7 @@ ${request_headers.query.channel}
</body>
</html>`;
} else {
var errpage = doErrorPage("400 Chat requires host, port and channel arguments. Do not use the # on channels.");
var errpage = wtvshared.doErrorPage("400 Chat requires host, port and channel arguments. Do not use the # on channels.");
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
var irc_nick = "";
headers = "200 OK";
if (request_headers.query.nick) headers += "\n" + ssid_sessions[socket.ssid].setIRCNick(request_headers.query.nick);

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
if (socket.ssid) {
if (request_headers.post_data) {
if (ssid_sessions[socket.ssid]) {
@@ -9,7 +11,7 @@ if (socket.ssid) {
}
if (!headers) {
var errpage = doErrorPage(400)
var errpage = wtvshared.doErrorPage(400)
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
if (request_headers.post_data) {
if (request_headers.query.domain && request_headers.query.path) {
if (socket.ssid) {
@@ -11,7 +13,7 @@ if (request_headers.post_data) {
}
if (!headers) {
var errpage = doErrorPage(400)
var errpage = wtvshared.doErrorPage(400)
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
if (socket.ssid) {
if (ssid_sessions[socket.ssid]) {
@@ -8,7 +10,7 @@ if (socket.ssid) {
}
if (!headers) {
var errpage = doErrorPage(400)
var errpage = wtvshared.doErrorPage(400)
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
if (socket.ssid) {
if (ssid_sessions[socket.ssid]) {
ssid_sessions[socket.ssid].resetCookies();
@@ -19,7 +21,7 @@ Redirecting shortly... <a href="client:goback">Go Back</a>
}
if (!headers) {
var errpage = doErrorPage(400)
var errpage = wtvshared.doErrorPage(400)
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
if (request_headers.query.group) {
const WTVDownloadList = require("./WTVDownloadList.js");
var wtvdl = new WTVDownloadList(minisrv_config, service_name);

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
const WTVDownloadList = require("./WTVDownloadList.js");
var wtvdl = new WTVDownloadList(minisrv_config, service_name);
@@ -212,9 +214,7 @@ if (request_headers['wtv-request-type'] == 'download') {
Object.keys(service_vaults).forEach(function (g) {
if (diskmap_data_file != null) return;
diskmap_data_file = service_vaults[g] + "/" + service_name + "/" + diskmap_group_data.files[k].location;
if (!fs.existsSync(diskmap_data_file)) {
console.error("Could not find a file for", diskmap_group_data.files[k].location, "(Last tried SV:", diskmap_data_file, ")");
}
if (!fs.existsSync(diskmap_data_file)) diskmap_data_file = null;
});
var diskmap_file_stat = fs.lstatSync(diskmap_data_file);
@@ -298,20 +298,20 @@ if (request_headers['wtv-request-type'] == 'download') {
headers = "200 OK\nContent-Type: wtv/download-list";
} catch (e) {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
console.error(" # " + service_name+":/sync error", e);
}
}
} else {
var errpage = doErrorPage(404, "The requested DiskMap does not exist.");
var errpage = wtvshared.doErrorPage(404, "The requested DiskMap does not exist.");
headers = errpage[0];
data = errpage[1];
if (minisrv_config.config.debug_flags.debug) console.error(" # " + service_name +":/sync error", "could not find diskmap");
}
} else {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
if (minisrv_config.config.debug_flags.debug) console.error(" # " + service_name + ":/sync error", "missing query arguments");

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
if (request_headers.post_data) {
if (request_headers.query.partialPath || request_headers.query.path) {
if (socket.ssid) {
@@ -15,7 +17,7 @@ if (request_headers.post_data) {
}
if (!headers) {
var errpage = doErrorPage(400)
var errpage = wtvshared.doErrorPage(400)
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
const WTVFlashrom = require("./WTVFlashrom.js");
request_is_async = true;

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
const WTVFlashrom = require("./WTVFlashrom.js");
request_is_async = true;

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
const WTVFlashrom = require("./WTVFlashrom.js");
request_is_async = true;
@@ -28,7 +30,7 @@ if (request_headers.query.raw || bf0app_update) {
headers += "Content-type: text/html"
data = '';
} else {
var errpage = doErrorPage(404)
var errpage = wtvshared.doErrorPage(404)
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,10 +1,11 @@
var minisrv_service_file = true;
const WTVFlashrom = require("./WTVFlashrom.js");
var wtvflashrom;
request_is_async = true;
if (!request_headers.query.path) {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
} else {
@@ -21,7 +22,7 @@ async function processLC2DownloadPage(path, flashrom_info, numparts = null) {
if (numparts != null) flashrom_info.part_count = parseInt(numparts);
if (!flashrom_info.part_count) flashrom_info.part_count = parseInt(flashrom_info.message.substring(flashrom_info.message.length - 4).replace(/\D/g, ''));
if (!flashrom_info.part_number || !flashrom_info.is_last_part || !flashrom_info.rompath || !flashrom_info.next_rompath || !flashrom_info.is_bootrom) {
if (!flashrom_info.is_last_part || request_headers.query.last_part) {
if (!flashrom_info.is_last_part) {
flashrom_info.next_rompath = request_headers.request_url.replace(escape(request_headers.query.path), escape(flashrom_info.next_rompath.replace(service_name+":/","")));
}
@@ -92,8 +93,7 @@ data += `
<upgradeblock width=280 height=15
nexturl="${flashrom_info.next_rompath}"
errorurl="${service_name}:/lc2-download-failed?"
`
if (!flashrom_info.is_last_part) data += `blockurl = "${flashrom_info.rompath}"`;
blockurl="${flashrom_info.rompath}"`;
data += `
lastblock="${flashrom_info.is_last_part}"
@@ -144,7 +144,7 @@ ${flashrom_info.message}
</body>
</html>`;
} else {
var errpage = doErrorPage(400)
var errpage = wtvshared.doErrorPage(400)
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,9 +1,11 @@
var minisrv_service_file = true;
if (request_headers.query.path) {
var url = service_name + ":/get-lc2-page?path=" + request_headers.query.path;
var romtype = ssid_sessions[socket.ssid].get("wtv-client-rom-type");
if (romtype == "bf0app") {
url = "client:updateflash?ipaddr=" + minisrv_config.services[service_name].host + "&port=" + minisrv_config.services[service_name].port + "&path=" + escape(service_name + ":/" +request_headers.query.path);
if (request_headers.query.numparts) url += escape("&numparts=" + request_headers.query.numparts);
if (request_headers.query.numparts) url += escape("?numparts=" + request_headers.query.numparts);
}
headers = "300 OK\n";
headers += "wtv-visit: " + url + "\n";
@@ -11,7 +13,7 @@ var romtype = ssid_sessions[socket.ssid].get("wtv-client-rom-type");
headers += "Content-type: text/html";
data = '';
} else {
var errpage = doErrorPage(400)
var errpage = wtvshared.doErrorPage(400)
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
Connection: Close
wtv-connection-close: true
@@ -43,6 +45,7 @@ Updating complete
<img src="${service_name}:/ROMCache/S40H1.gif" width=560 height=6>
<tr>
<td width=104 height=10 valign=top align=left>
<td width=20 valign=top align=left>
<td width=67 valign=top align=left>
<td width=20 valign=top align=left>

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
var error = '';
if (request_headers.query.error) {
switch (request_headers.query.error) {

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
const WTVFlashrom = require("./WTVFlashrom.js");
request_is_async = true;

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
// willie is just a graphical frontend to a list of ROMs
// the rest of the scripts should work if you manually link to a ROM, and actually have it.
@@ -33,7 +35,7 @@ const req = https.request(options, function (res) {
res.on('error', function (e) {
if (!minisrv_config.config.debug_flags.quiet) console.log(" * Upstream Ultra Willies HTTP Error:", e);
var errpage = doErrorPage(400)
var errpage = wtvshared.doErrorPage(400)
headers = errpage[0];
data = errpage[1];
sendToClient(socket, headers, data);

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
var challenge_response, challenge_header = '';
var gourl;

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
var challenge_response, challenge_header = '';
var gourl;
@@ -61,7 +63,7 @@ else {
if (request_headers.query.skip_splash) var home_url = "wtv-home:/home?";
else var home_url = "wtv-home:/splash?";
} else if (!ssid_sessions[socket.ssid].getSessionData("registered")) {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
} else {

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
var challenge_response, challenge_header = "";
var gourl = "wtv-head-waiter:/login-stage-two?";

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
var gourl = "wtv-1800:/preregister?";
if (request_headers.query.relogin) gourl += "relogin=true";
else if (request_headers.query.reconnect) gourl += "reconnect=true";

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers =`200 OK
Connection: Keep-Alive
wtv-expire-all: wtv-home:/splash

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
Connection: Keep-Alive
wtv-expire-all: wtv-

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
// write posted log data to disk. should be decrypted by this point (if it was encrypted) if the crypto stream didn't break
request_is_async = true;

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
Content-Type: text/html`;

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
if (minisrv_config.config.allow_guests) {
headers = `300 Moved
Connection: Close
@@ -20,7 +22,7 @@ wtv-reconnect-url: wtv-1800:/preregister?guest_login=true&reconnect=true
wtv-boot-url: wtv-1800:/preregister?guest_login=true
Location: client:relogin`;
} else {
var errpage = doErrorPage(400, "Guest mode is not enabled on this service.");
var errpage = wtvshared.doErrorPage(400, "Guest mode is not enabled on this service.");
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `300 Moved
Connection: Close
wtv-noback-all: wtv-register:

View File

@@ -1,22 +1,22 @@
var minisrv_service_file = true;
if (!request_headers.query.registering) {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
} else {
const WTVRegister = require("./WTVRegister.js")
var wtvr = new WTVRegister(minisrv_config, SessionStore);
var errpage = null;
if (!request_headers.query.registering) errpage = doErrorPage(400);
else if (!request_headers.query.subscriber_name) errpage = doErrorPage(400, "Please enter your name. This can be your real name, or your well-known online alias.");
else if (!request_headers.query.subscriber_username) errpage = doErrorPage(400, "Please enter a username.");
else if (request_headers.query.subscriber_username.length < 5) errpage = doErrorPage(400, "Please choose a username with 5 or more characters.");
else if (request_headers.query.subscriber_username.length > 16) errpage = doErrorPage(400, "Please choose a username with 16 or less characters.");
else if (!wtvr.checkUsernameSanity(request_headers.query.subscriber_username)) errpage = doErrorPage(400, "The username you have chosen contains invalid characters. Please choose a username with only <b>letters</b>, <b>numbers</b>, <b>_</b> or <b>-</b>. Also, please be sure your username begins with a letter.");
else if (!wtvr.checkUsernameAvailable(request_headers.query.subscriber_username, ssid_sessions)) errpage = doErrorPage(400, "The username you have selected is already in use. Please select another username.");
else if (!request_headers.query.subscriber_contact) errpage = doErrorPage(400, "Please enter your contact information.");
else if (request_headers.query.subscriber_contact_method == "") errpage = doErrorPage(400, "Please select the type of contact information you provided.");
if (!request_headers.query.registering) errpage = wtvshared.doErrorPage(400);
else if (!request_headers.query.subscriber_name) errpage = wtvshared.doErrorPage(400, "Please enter your name. This can be your real name, or your well-known online alias.");
else if (!request_headers.query.subscriber_username) errpage = wtvshared.doErrorPage(400, "Please enter a username.");
else if (request_headers.query.subscriber_username.length < 5) errpage = wtvshared.doErrorPage(400, "Please choose a username with 5 or more characters.");
else if (request_headers.query.subscriber_username.length > 16) errpage = wtvshared.doErrorPage(400, "Please choose a username with 16 or less characters.");
else if (!wtvr.checkUsernameSanity(request_headers.query.subscriber_username)) errpage = wtvshared.doErrorPage(400, "The username you have chosen contains invalid characters. Please choose a username with only <b>letters</b>, <b>numbers</b>, <b>_</b> or <b>-</b>. Also, please be sure your username begins with a letter.");
else if (!wtvr.checkUsernameAvailable(request_headers.query.subscriber_username, ssid_sessions)) errpage = wtvshared.doErrorPage(400, "The username you have selected is already in use. Please select another username.");
else if (!request_headers.query.subscriber_contact) errpage = wtvshared.doErrorPage(400, "Please enter your contact information.");
else if (request_headers.query.subscriber_contact_method == "") errpage = wtvshared.doErrorPage(400, "Please select the type of contact information you provided.");
if (errpage) {

View File

@@ -1,5 +1,7 @@
var minisrv_service_file = true;
if (!request_headers.query.registering) {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
} else {

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
if (!request_headers.query.registering ||
!request_headers.query.subscriber_name ||
!request_headers.query.subscriber_username ||
@@ -7,7 +9,7 @@ if (!request_headers.query.registering ||
!ssid_sessions[socket.ssid] ||
!socket.ssid
) {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
} else {
@@ -18,7 +20,7 @@ if (!request_headers.query.registering ||
ssid_sessions[socket.ssid].setSessionData("subscriber_userid", '1' + Math.floor(Math.random() * 1000000000000000000));
ssid_sessions[socket.ssid].setSessionData("registered", true);
if (!ssid_sessions[socket.ssid].storeSessionData(true)) {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
} else {

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
Content-Type: text/html`;

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
Connection: Keep-Alive
wtv-expire-all: wtv-

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
wtv-backgroundmusic-load-playlist: wtv-music:/get-playlist
wtv-printer-model: -1,-1

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
// Allow URL access outside our trusted minisrv
if (request_headers.query.url) var url = request_headers.query.url;

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
wtv-expire-all: wtv-
wtv-expire-all: http

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
wtv-noback-all: wtv-
wtv-expire-all: wtv-

View File

@@ -1,3 +1,4 @@
var minisrv_service_file = true;
var client_caps = null;
@@ -167,7 +168,7 @@ ${wtv_system_sysconfig_str}
</body> </html>`;
} else {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
}

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
Content-Type: text/html`;

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
Content-Type: text/html`

View File

@@ -1,3 +1,5 @@
var minisrv_service_file = true;
headers = `200 OK
Content-Type: text/html`;

View File

@@ -8,51 +8,27 @@ class WTVFlashrom {
no_debug = false;
service_name = "";
minisrv_config = [];
wtvshared = null;
constructor(minisrv_config, service_vaults, service_name, use_zefie_server = true, bf0app_update = false, no_debug = false) {
var { WTVShared } = require('./WTVShared.js');
this.service_vaults = service_vaults;
this.service_name = service_name;
this.use_zefie_server = use_zefie_server;
this.bf0app_update = bf0app_update;
this.no_debug = no_debug;
this.minisrv_config = minisrv_config;
this.wtvshared = new WTVShared(minisrv_config);
}
doErrorPage(code, data = null) {
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";
break;
case 400:
if (data === null) data = "HackTV ran into a technical problem.";
headers = "400 " + data + "\r\n";
headers += "Content-Type: text/html\r\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";
break;
}
console.error("doErrorPage Called:", code, data);
return new Array(headers, data);
}
async doLocalFlashROM(flashrom_file_path, request_path, callback, info_only = false) {
// use local flashrom files;
console.log(info_only);
var self = this;
try {
this.fs.readFile(flashrom_file_path, null, function (err, data) {
if (err) {
errpage = doErrorPage(400)
errpage = wtvshared.doErrorPage(400)
var headers = errpage[0];
data = err.toString();
callback(data, headers);
@@ -65,7 +41,7 @@ class WTVFlashrom {
}
});
} catch (e) {
var errpage = doErrorPage(404, "The service could not find the requested ROM.")
var errpage = wtvshared.doErrorPage(404, "The service could not find the requested ROM.")
var headers = errpage[0];
var data = errpage[1];
callback(data, headers);
@@ -107,24 +83,25 @@ class WTVFlashrom {
flashrom_info.total_parts_size = data.readUInt32BE(32);
flashrom_info.percent_complete = ((((flashrom_info.byte_progress + flashrom_info.part_total_size) / flashrom_info.total_parts_size)) * 100).toFixed(1);
if (this.minisrv_config.config.debug_flags.debug && !this.no_debug) console.log(" # Flashrom Part Size :", flashrom_info.part_total_size);
if (this.minisrv_config.config.debug_flags.debug && !this.no_debug) console.log(" # Flashrom Bytes Sent :", flashrom_info.byte_progress);
if (this.minisrv_config.config.debug_flags.debug && !this.no_debug) console.log(" # Flashrom Bytes Sent+:", flashrom_info.byte_progress + flashrom_info.part_total_size, "(" + flashrom_info.percent_complete + "% complete)");
if (this.minisrv_config.config.debug_flags.debug && !this.no_debug) console.log(" # Flashrom Total Size :", flashrom_info.total_parts_size);
if (this.minisrv_config.config.debug_flags.debug && !this.minisrv_config.config.debug_flags.quiet && !this.no_debug) console.log(" # Flashrom Part Size :", flashrom_info.part_total_size);
if (this.minisrv_config.config.debug_flags.debug && !this.minisrv_config.config.debug_flags.quiet && !this.no_debug) console.log(" # Flashrom Bytes Sent :", flashrom_info.byte_progress);
if (this.minisrv_config.config.debug_flags.debug && !this.minisrv_config.config.debug_flags.quiet && !this.no_debug) console.log(" # Flashrom Bytes Sent+:", flashrom_info.byte_progress + flashrom_info.part_total_size, "(" + flashrom_info.percent_complete + "% complete)");
if (this.minisrv_config.config.debug_flags.debug && !this.minisrv_config.config.debug_flags.quiet && !this.no_debug) console.log(" # Flashrom Total Size :", flashrom_info.total_parts_size);
// read current part number bit from part header
flashrom_info.part_number = data.readUInt16BE(28);
if (this.minisrv_config.config.debug_flags.debug && !this.no_debug) console.log(" # Flashrom Curr Part Number :", flashrom_info.part_number);
if (this.minisrv_config.config.debug_flags.debug && !this.minisrv_config.config.debug_flags.quiet && !this.no_debug) console.log(" # Flashrom Curr Part Number :", flashrom_info.part_number);
flashrom_info.is_last_part = ((flashrom_info.byte_progress + flashrom_info.part_total_size) == flashrom_info.total_parts_size) ? true : false;
if (flashrom_info.is_last_part) {
if (this.minisrv_config.config.debug_flags.debug && !this.no_debug) console.log(" # Flashrom Curr Part is Last:", flashrom_info.is_last_part);
if (this.minisrv_config.config.debug_flags.debug && !this.minisrv_config.config.debug_flags.quiet && !this.no_debug) console.log(" # Flashrom Curr Part is Last:", flashrom_info.is_last_part);
} else {
flashrom_info.next_part_number = flashrom_info.part_number + 1;
if (this.minisrv_config.config.debug_flags.debug && !this.no_debug) console.log(" # Flashrom Next Part Number :", flashrom_info.next_part_number);
if (this.minisrv_config.config.debug_flags.debug && !this.minisrv_config.config.debug_flags.quiet && !this.no_debug) console.log(" # Flashrom Next Part Number :", flashrom_info.next_part_number);
}
if (this.minisrv_config.config.debug_flags.debug && this.minisrv_config.config.debug_flags.quiet) console.log(" # Sending", (flashrom_info.is_last_part) ? "Last Flashrom" : "Flashrom", "Part", flashrom_info.part_number, "- Bytes Sent:", flashrom_info.byte_progress + flashrom_info.part_total_size, "of", flashrom_info.total_parts_size, "(" + flashrom_info.percent_complete + " % complete)");
// read current part display message from part header
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.rompath = `wtv-flashrom:/${path}`;
@@ -142,7 +119,6 @@ class WTVFlashrom {
async sendToClient(data, request_path, callback) {
var headers = "200 OK\n";
if (this.bf0app_update) headers += "minisrv-use-carriage-return: false\n";
var flashrom_info = this.getFlashromInfo(data, request_path)
if (flashrom_info.is_bootrom) headers += "Content-Type: binary/x-wtv-bootrom"; // maybe?
else headers += "Content-Type: binary/x-wtv-flashblock";
@@ -193,15 +169,16 @@ class WTVFlashrom {
} else if (res.statusCode == 206) {
var data = self.getFlashromInfo(Buffer.from(data_hex, 'hex'), request_path);
} else if (res.statusCode == 404) {
var errpage = doErrorPage(404, "The service could not find the requested ROM on zefie's server.")
console.log(request_path);
var errpage = self.wtvshared.doErrorPage(404, "The service could not find the requested ROM on zefie's server.")
headers = errpage[0];
var data = errpage[1];
} else {
var errpage = doErrorPage(400)
var errpage = self.wtvshared.doErrorPage(400)
headers = errpage[0];
var data = errpage[1];
}
if (res.statusCode != 206) {
if (!headers && res.statusCode != 206) {
self.sendToClient(data, request_path, callback);
} else {
callback(data, headers);

View File

@@ -1,118 +1,154 @@
/**
* Pure-JS implementation of WebTV's LZPF compression
*
* This is a port of my Lzpf compression code from my ROMFS Python tool
* Originally reverse engineered from the box
*
* By: Eric MacDonald (eMac)
* Modified By: zefie
*/
* Pure-JS implementation of WebTV's LZPF compression
*
* This compression algorithm is based on LZP by Charles Bloom and was originally written for server to client communication by Andy McFadden
* This uses a (static) Huffman dictionary that was tuned for character occurances in a typical HTML page at the time (around 1996-1997).
*
* Andy McFadden:
* https://fadden.com/
* LZP:
* https://cbloom.com/src/index_lz.html
* https://en.wikibooks.org/wiki/Data_Compression/Dictionary_compression#LZP
*
* I wouldn't recommend using LZPF on anything but HTML and other text-based data (unless the data has many repeating bytes)
* LZPF can be replaced with gzip for LC2 and newer boxes. Classic is stuck with LZPF.
*
* Reverse engineered and ported by: Eric MacDonald (eMac)
* Modified By: zefie
**/
class WTVLzpf {
// Note: currentlty doesn't offer optimal streaming support but this is good enough to meet perf demands at the scale we're at.
current_length = 0;
current_literal = 0;
flag = 0xFFFF;
current_bit_length = 0;
current_bits = 0;
ring_bufer_index = 0xFFFF;
working_data = 0;
match_index = 0;
type_index = 0;
compression_mode = 0;
checksum = 0;
flag_table = new Uint16Array(0x1000)
filler_byte = 0x20
hash_table = new Uint16Array(0x1000)
ring_buffer = new Uint8Array(0x2000)
encoded_data = [];
/**
* This is used to encode (one-byte) literals with no previous tracked occurence.
*
* - Bytes with best compression: SPACE and LF and e"/<>Tainoprst
* - Bytes with good compression: TAB and ,-.1=ABCDEFGHILNOPRSbcdfghlmuw
* - Bytes that don't change the length of the bit stream: 024:MW_kvy
* - The rest will increase the length of bit stream
*
* I don't know what process they used to build this table. I assume they
* frequency-scanned a bunch of HTML files they had.
*
* Using Windows-1252 (based off of ISO-8859-1) chracter encoding to fill in this table. Didn't
* seem like they used a different table for Japan builds (ISO-2022-JP).
**/
nomatchEncode = [
[0x0000, 0x10], [0x0001, 0x10], [0x0002, 0x10],
[0x0003, 0x10], [0x0004, 0x10], [0x009A, 0x0F],
[0x0005, 0x10], [0x009C, 0x0F], [0x009E, 0x0F],
[0x3400, 0x06], [0x7000, 0x05], [0x00A0, 0x0F],
[0x0006, 0x10], [0x0380, 0x09], [0x0007, 0x10],
[0x0008, 0x10], [0x0009, 0x10], [0x000A, 0x10],
[0x000B, 0x10], [0x000C, 0x10], [0x000D, 0x10],
[0x000E, 0x10], [0x000F, 0x10], [0x00A2, 0x0F],
[0x0010, 0x10], [0x0011, 0x10], [0x0012, 0x10],
[0x0013, 0x10], [0x0014, 0x10], [0x0015, 0x10],
[0x0016, 0x10], [0x0017, 0x10], [0xE000, 0x04],
[0x0200, 0x0A], [0x7800, 0x05], [0x0400, 0x09],
[0x00B0, 0x0E], [0x0018, 0x10], [0x0120, 0x0B],
[0x0480, 0x09], [0x0140, 0x0B], [0x0160, 0x0B],
[0x0240, 0x0A], [0x00B8, 0x0D], [0x1400, 0x07],
[0x1600, 0x07], [0x3800, 0x06], [0x8000, 0x05],
[0x0A00, 0x08], [0x1800, 0x07], [0x0B00, 0x08],
[0x0500, 0x09], [0x0C00, 0x08], [0x0580, 0x09],
[0x0600, 0x09], [0x0680, 0x09], [0x0700, 0x09],
[0x0780, 0x09], [0x0D00, 0x08], [0x0180, 0x0B],
[0x8800, 0x05], [0x3C00, 0x06], [0x9000, 0x05],
[0x0280, 0x0A], [0x00B4, 0x0E], [0x4000, 0x06],
[0x1A00, 0x07], [0x1C00, 0x07], [0x1E00, 0x07],
[0x4400, 0x06], [0x2000, 0x07], [0x2200, 0x07],
[0x2400, 0x07], [0x4800, 0x06], [0x01A0, 0x0B],
[0x02C0, 0x0A], [0x2600, 0x07], [0x0E00, 0x08],
[0x4C00, 0x06], [0x5000, 0x06], [0x2800, 0x07],
[0x00C0, 0x0C], [0x5400, 0x06], [0x2A00, 0x07],
[0x9800, 0x05], [0x0800, 0x09], [0x0880, 0x09],
[0x0F00, 0x08], [0x00D0, 0x0C], [0x0300, 0x0A],
[0x0900, 0x09], [0x0019, 0x10], [0x001A, 0x10],
[0x001B, 0x10], [0x001C, 0x10], [0x1000, 0x08],
[0x001D, 0x10], [0xA000, 0x05], [0x2C00, 0x07],
[0x5800, 0x06], [0x5C00, 0x06], [0xF000, 0x04],
[0x2E00, 0x07], [0x3000, 0x07], [0x6000, 0x06],
[0xA800, 0x05], [0x01C0, 0x0B], [0x1100, 0x08],
[0x6400, 0x06], [0x6800, 0x06], [0xB000, 0x05],
[0xB800, 0x05], [0xC000, 0x05], [0x01E0, 0x0B],
[0xC800, 0x05], [0xD000, 0x05], [0xD800, 0x05],
[0x3200, 0x07], [0x1200, 0x08], [0x6C00, 0x06],
[0x0980, 0x09], [0x1300, 0x08], [0x0340, 0x0A],
[0x00E0, 0x0C], [0x00F0, 0x0C], [0x0100, 0x0C],
[0x0110, 0x0C], [0x001E, 0x10], [0x001F, 0x10],
[0x0020, 0x10], [0x0021, 0x10], [0x0022, 0x10],
[0x0023, 0x10], [0x0024, 0x10], [0x0025, 0x10],
[0x0026, 0x10], [0x0027, 0x10], [0x0028, 0x10],
[0x0029, 0x10], [0x002A, 0x10], [0x002B, 0x10],
[0x002C, 0x10], [0x002D, 0x10], [0x002E, 0x10],
[0x002F, 0x10], [0x00A4, 0x0F], [0x00A6, 0x0F],
[0x00A8, 0x0F], [0x0030, 0x10], [0x0031, 0x10],
[0x0032, 0x10], [0x0033, 0x10], [0x0034, 0x10],
[0x0035, 0x10], [0x0036, 0x10], [0x0037, 0x10],
[0x0038, 0x10], [0x0039, 0x10], [0x003A, 0x10],
[0x003B, 0x10], [0x003C, 0x10], [0x003D, 0x10],
[0x003E, 0x10], [0x003F, 0x10], [0x0040, 0x10],
[0x0041, 0x10], [0x0042, 0x10], [0x0043, 0x10],
[0x0044, 0x10], [0x0045, 0x10], [0x0046, 0x10],
[0x0047, 0x10], [0x0048, 0x10], [0x0049, 0x10],
[0x004A, 0x10], [0x004B, 0x10], [0x004C, 0x10],
[0x004D, 0x10], [0x004E, 0x10], [0x004F, 0x10],
[0x0050, 0x10], [0x0051, 0x10], [0x0052, 0x10],
[0x0053, 0x10], [0x0054, 0x10], [0x0055, 0x10],
[0x0056, 0x10], [0x0057, 0x10], [0x0058, 0x10],
[0x0059, 0x10], [0x005A, 0x10], [0x005B, 0x10],
[0x005C, 0x10], [0x005D, 0x10], [0x005E, 0x10],
[0x005F, 0x10], [0x0060, 0x10], [0x0061, 0x10],
[0x0062, 0x10], [0x00AA, 0x0F], [0x0063, 0x10],
[0x0064, 0x10], [0x0065, 0x10], [0x0066, 0x10],
[0x0067, 0x10], [0x0068, 0x10], [0x0069, 0x10],
[0x006A, 0x10], [0x006B, 0x10], [0x006C, 0x10],
[0x006D, 0x10], [0x006E, 0x10], [0x006F, 0x10],
[0x0070, 0x10], [0x0071, 0x10], [0x0072, 0x10],
[0x0073, 0x10], [0x0074, 0x10], [0x0075, 0x10],
[0x0076, 0x10], [0x0077, 0x10], [0x0078, 0x10],
[0x0079, 0x10], [0x007A, 0x10], [0x007B, 0x10],
[0x007C, 0x10], [0x007D, 0x10], [0x007E, 0x10],
[0x007F, 0x10], [0x0080, 0x10], [0x0081, 0x10],
[0x0082, 0x10], [0x0083, 0x10], [0x0084, 0x10],
[0x0085, 0x10], [0x0086, 0x10], [0x0087, 0x10],
[0x0088, 0x10], [0x0089, 0x10], [0x008A, 0x10],
[0x008B, 0x10], [0x008C, 0x10], [0x008D, 0x10],
[0x00AC, 0x0F], [0x008E, 0x10], [0x008F, 0x10],
[0x0090, 0x10], [0x0091, 0x10], [0x0092, 0x10],
[0x0093, 0x10], [0x00AE, 0x0F], [0x0094, 0x10],
[0x0095, 0x10], [0x0096, 0x10], [0x0097, 0x10],
[0x0098, 0x10], [0x0099, 0x10]
/* [FLATTENED HUFFMAN CODE, CODE BIT LENGTH] */
[0x0000, 0x10] /* NUL */, [0x0001, 0x10] /* SOH */, [0x0002, 0x10] /* STX */,
[0x0003, 0x10] /* ETX */, [0x0004, 0x10] /* EOT */, [0x009A, 0x0F] /* ENQ */,
[0x0005, 0x10] /* ACK */, [0x009C, 0x0F] /* BEL */, [0x009E, 0x0F] /* BS */,
[0x3400, 0x06] /* TAB */, [0x7000, 0x05] /* LF */, [0x00A0, 0x0F] /* VT */,
[0x0006, 0x10] /* FF */, [0x0380, 0x09] /* CR */, [0x0007, 0x10] /* SO */,
[0x0008, 0x10] /* SI */, [0x0009, 0x10] /* DLE */, [0x000A, 0x10] /* DC1 */,
[0x000B, 0x10] /* DC2 */, [0x000C, 0x10] /* DC3 */, [0x000D, 0x10] /* DC4 */,
[0x000E, 0x10] /* NAK */, [0x000F, 0x10] /* SYN */, [0x00A2, 0x0F] /* BTB */,
[0x0010, 0x10] /* CAN */, [0x0011, 0x10] /* EM */, [0x0012, 0x10] /* SUB */,
[0x0013, 0x10] /* ESC */, [0x0014, 0x10] /* FS */, [0x0015, 0x10] /* GS */,
[0x0016, 0x10] /* RS */, [0x0017, 0x10] /* US */, [0xE000, 0x04] /* SPACE */,
[0x0200, 0x0A] /* ! */, [0x7800, 0x05] /* " */, [0x0400, 0x09] /* # */,
[0x00B0, 0x0E] /* $ */, [0x0018, 0x10] /* % */, [0x0120, 0x0B] /* & */,
[0x0480, 0x09] /* ' */, [0x0140, 0x0B] /* ( */, [0x0160, 0x0B] /* ) */,
[0x0240, 0x0A] /* * */, [0x00B8, 0x0D] /* + */, [0x1400, 0x07] /* , */,
[0x1600, 0x07] /* - */, [0x3800, 0x06] /* . */, [0x8000, 0x05] /* / */,
[0x0A00, 0x08] /* 0 */, [0x1800, 0x07] /* 1 */, [0x0B00, 0x08] /* 2 */,
[0x0500, 0x09] /* 3 */, [0x0C00, 0x08] /* 4 */, [0x0580, 0x09] /* 5 */,
[0x0600, 0x09] /* 6 */, [0x0680, 0x09] /* 7 */, [0x0700, 0x09] /* 8 */,
[0x0780, 0x09] /* 9 */, [0x0D00, 0x08] /* : */, [0x0180, 0x0B] /* ; */,
[0x8800, 0x05] /* < */, [0x3C00, 0x06] /* = */, [0x9000, 0x05] /* > */,
[0x0280, 0x0A] /* ? */, [0x00B4, 0x0E] /* @ */, [0x4000, 0x06] /* A */,
[0x1A00, 0x07] /* B */, [0x1C00, 0x07] /* C */, [0x1E00, 0x07] /* D */,
[0x4400, 0x06] /* E */, [0x2000, 0x07] /* F */, [0x2200, 0x07] /* G */,
[0x2400, 0x07] /* H */, [0x4800, 0x06] /* I */, [0x01A0, 0x0B] /* J */,
[0x02C0, 0x0A] /* K */, [0x2600, 0x07] /* L */, [0x0E00, 0x08] /* M */,
[0x4C00, 0x06] /* N */, [0x5000, 0x06] /* O */, [0x2800, 0x07] /* P */,
[0x00C0, 0x0C] /* Q */, [0x5400, 0x06] /* R */, [0x2A00, 0x07] /* S */,
[0x9800, 0x05] /* T */, [0x0800, 0x09] /* U */, [0x0880, 0x09] /* V */,
[0x0F00, 0x08] /* W */, [0x00D0, 0x0C] /* X */, [0x0300, 0x0A] /* Y */,
[0x0900, 0x09] /* Z */, [0x0019, 0x10] /* [ */, [0x001A, 0x10] /* \ */,
[0x001B, 0x10] /* ] */, [0x001C, 0x10] /* ^ */, [0x1000, 0x08] /* _ */,
[0x001D, 0x10] /* ` */, [0xA000, 0x05] /* a */, [0x2C00, 0x07] /* b */,
[0x5800, 0x06] /* c */, [0x5C00, 0x06] /* d */, [0xF000, 0x04] /* e */,
[0x2E00, 0x07] /* f */, [0x3000, 0x07] /* g */, [0x6000, 0x06] /* h */,
[0xA800, 0x05] /* i */, [0x01C0, 0x0B] /* j */, [0x1100, 0x08] /* k */,
[0x6400, 0x06] /* l */, [0x6800, 0x06] /* m */, [0xB000, 0x05] /* n */,
[0xB800, 0x05] /* o */, [0xC000, 0x05] /* p */, [0x01E0, 0x0B] /* q */,
[0xC800, 0x05] /* r */, [0xD000, 0x05] /* s */, [0xD800, 0x05] /* t */,
[0x3200, 0x07] /* u */, [0x1200, 0x08] /* v */, [0x6C00, 0x06] /* w */,
[0x0980, 0x09] /* x */, [0x1300, 0x08] /* y */, [0x0340, 0x0A] /* z */,
[0x00E0, 0x0C] /* { */, [0x00F0, 0x0C] /* | */, [0x0100, 0x0C] /* } */,
[0x0110, 0x0C] /* ~ */, [0x001E, 0x10] /* DEL */, [0x001F, 0x10] /* <20> */,
[0x0020, 0x10] /* */, [0x0021, 0x10] /* <20> */, [0x0022, 0x10] /* <20> */,
[0x0023, 0x10] /* <20> */, [0x0024, 0x10] /* <20> */, [0x0025, 0x10] /* <20> */,
[0x0026, 0x10] /* <20> */, [0x0027, 0x10] /* <20> */, [0x0028, 0x10] /* <20> */,
[0x0029, 0x10] /* <20> */, [0x002A, 0x10] /* <20> */, [0x002B, 0x10] /* <20> */,
[0x002C, 0x10] /* */, [0x002D, 0x10] /* <20> */, [0x002E, 0x10] /* */,
[0x002F, 0x10] /* */, [0x00A4, 0x0F] /* <20> */, [0x00A6, 0x0F] /* <20> */,
[0x00A8, 0x0F] /* <20> */, [0x0030, 0x10] /* <20> */, [0x0031, 0x10] /* <20> */,
[0x0032, 0x10] /* <20> */, [0x0033, 0x10] /* <20> */, [0x0034, 0x10] /* <20> */,
[0x0035, 0x10] /* <20> */, [0x0036, 0x10] /* <20> */, [0x0037, 0x10] /* <20> */,
[0x0038, 0x10] /* <20> */, [0x0039, 0x10] /* */, [0x003A, 0x10] /* <20> */,
[0x003B, 0x10] /* <20> */, [0x003C, 0x10] /* NBSP*/, [0x003D, 0x10] /* <20> */,
[0x003E, 0x10] /* <20> */, [0x003F, 0x10] /* <20> */, [0x0040, 0x10] /* <20> */,
[0x0041, 0x10] /* <20> */, [0x0042, 0x10] /* <20> */, [0x0043, 0x10] /* <20> */,
[0x0044, 0x10] /* <20> */, [0x0045, 0x10] /* <20> */, [0x0046, 0x10] /* <20> */,
[0x0047, 0x10] /* <20> */, [0x0048, 0x10] /* <20> */, [0x0049, 0x10] /* SHY */,
[0x004A, 0x10] /* <20> */, [0x004B, 0x10] /* <20> */, [0x004C, 0x10] /* <20> */,
[0x004D, 0x10] /* <20> */, [0x004E, 0x10] /* <20> */, [0x004F, 0x10] /* <20> */,
[0x0050, 0x10] /* <20> */, [0x0051, 0x10] /* <20> */, [0x0052, 0x10] /* <20> */,
[0x0053, 0x10] /* <20> */, [0x0054, 0x10] /* <20> */, [0x0055, 0x10] /* <20> */,
[0x0056, 0x10] /* <20> */, [0x0057, 0x10] /* <20> */, [0x0058, 0x10] /* <20> */,
[0x0059, 0x10] /* <20> */, [0x005A, 0x10] /* <20> */, [0x005B, 0x10] /* <20> */,
[0x005C, 0x10] /* <20> */, [0x005D, 0x10] /* <20> */, [0x005E, 0x10] /* <20> */,
[0x005F, 0x10] /* <20> */, [0x0060, 0x10] /* <20> */, [0x0061, 0x10] /* <20> */,
[0x0062, 0x10] /* <20> */, [0x00AA, 0x0F] /* <20> */, [0x0063, 0x10] /* <20> */,
[0x0064, 0x10] /* <20> */, [0x0065, 0x10] /* <20> */, [0x0066, 0x10] /* <20> */,
[0x0067, 0x10] /* <20> */, [0x0068, 0x10] /* <20> */, [0x0069, 0x10] /* <20> */,
[0x006A, 0x10] /* <20> */, [0x006B, 0x10] /* <20> */, [0x006C, 0x10] /* <20> */,
[0x006D, 0x10] /* <20> */, [0x006E, 0x10] /* <20> */, [0x006F, 0x10] /* <20> */,
[0x0070, 0x10] /* <20> */, [0x0071, 0x10] /* <20> */, [0x0072, 0x10] /* <20> */,
[0x0073, 0x10] /* <20> */, [0x0074, 0x10] /* <20> */, [0x0075, 0x10] /* <20> */,
[0x0076, 0x10] /* <20> */, [0x0077, 0x10] /* <20> */, [0x0078, 0x10] /* <20> */,
[0x0079, 0x10] /* <20> */, [0x007A, 0x10] /* <20> */, [0x007B, 0x10] /* <20> */,
[0x007C, 0x10] /* <20> */, [0x007D, 0x10] /* <20> */, [0x007E, 0x10] /* <20> */,
[0x007F, 0x10] /* <20> */, [0x0080, 0x10] /* <20> */, [0x0081, 0x10] /* <20> */,
[0x0082, 0x10] /* <20> */, [0x0083, 0x10] /* <20> */, [0x0084, 0x10] /* <20> */,
[0x0085, 0x10] /* <20> */, [0x0086, 0x10] /* <20> */, [0x0087, 0x10] /* <20> */,
[0x0088, 0x10] /* <20> */, [0x0089, 0x10] /* <20> */, [0x008A, 0x10] /* <20> */,
[0x008B, 0x10] /* <20> */, [0x008C, 0x10] /* <20> */, [0x008D, 0x10] /* <20> */,
[0x00AC, 0x0F] /* <20> */, [0x008E, 0x10] /* <20> */, [0x008F, 0x10] /* <20> */,
[0x0090, 0x10] /* <20> */, [0x0091, 0x10] /* <20> */, [0x0092, 0x10] /* <20> */,
[0x0093, 0x10] /* <20> */, [0x00AE, 0x0F] /* <20> */, [0x0094, 0x10] /* <20> */,
[0x0095, 0x10] /* <20> */, [0x0096, 0x10] /* <20> */, [0x0097, 0x10] /* <20> */,
[0x0098, 0x10] /* <20> */, [0x0099, 0x10]
];
/**
* This is the table that reduces the size based on repeated patterns in the file.
*
* When we find a byte match in the ring buffer we use this table to encode the length of the matched bytes.
*
* - These are intentionally 32-bit. The leftmost flag bit is 1 in each of these to tell the decoder to use match decoding.
* - LZP hash bits are used to encode the position where the matched bytes start.
* - We're allowed to match up to 298 bytes before we can't encode more (we need an entry in this table for each byte more).
* - We can reach for matches 65KB behind the current LZ cursor (65KB is the ring buffer size and highest a 16-bit hash can reach).
**/
matchEncode = [
/* [MATCH CODE, MATCH CODE BIT LENGTH] */
[0x80000000, 0x01], [0x80000000, 0x03],
[0xA0000000, 0x03], [0xC0000000, 0x03],
[0xE0000000, 0x06], [0xE4000000, 0x06],
@@ -262,6 +298,7 @@ class WTVLzpf {
[0xFFFF4000, 0x13], [0xFFFF6000, 0x13],
[0xFFFF8000, 0x13], [0xFFFFA000, 0x13],
[0xFFFFC000, 0x13], [0xFFFFE000, 0x13],
// We never should select these. These were in the original executable so including them here.
[0x00000000, 0x00], [0x00000000, 0x00]
];
@@ -271,7 +308,7 @@ class WTVLzpf {
* @returns {undefined}
*/
constructor() {
this.clear();
this.reset();
}
/**
@@ -279,16 +316,16 @@ class WTVLzpf {
*
* @returns {undefined}
*/
clear() {
this.current_length = 0;
this.current_literal = 0;
this.flag = 0xFFFF;
reset() {
this.current_bit_length = 0;
this.current_bits = 0;
this.ring_bufer_index = 0xFFFF;
this.working_data = 0;
this.match_index = 0;
this.type_index = 0;
this.compression_mode = 0;
this.checksum = 0;
this.ring_buffer.fill(0x00, 0, 0x2000)
this.flag_table.fill(0xFFFF, 0, 0x1000);
this.ring_buffer.fill(this.filler_byte, 0, 0x2000)
this.hash_table.fill(0xFFFF, 0, 0x1000);
this.encoded_data = [];
}
@@ -304,24 +341,24 @@ class WTVLzpf {
}
/**
* Encodes a literal onto the compressed byte array.
* Add bits onto the compressed bit stream.
*
* @param code_length {Number} bit length of the code
* @param code {Number} code to be encoded
* When we reach 8 bits we push a byte onto the compressed byte array.
*
* @param bits {Number} bits to add
* @param bit_length {Number} bit length
*
* @returns {undefined}
*/
EncodeLiteral(code_length, code) {
// Using >>> to stick with unsigned integers without making a mess with casting.
AddBits(bits, bit_length) {
this.current_bits |= bits >>> (this.current_bit_length & 0x1F);
this.current_bit_length += bit_length;
this.current_literal |= code >>> (this.current_length & 0x1F);
this.current_length += code_length;
while (this.current_bit_length > 7) {
this.AddByte((this.current_bits >>> 0x18) & 0xFF);
while (this.current_length > 7) {
this.AddByte((this.current_literal >>> 0x18) & 0xFF);
this.current_length -= 8;
this.current_literal = (this.current_literal << 8) & 0xFFFFFFFF;
this.current_bit_length -= 8;
this.current_bits = (this.current_bits << 8) & 0xFFFFFFFF;
}
}
@@ -331,7 +368,7 @@ class WTVLzpf {
* @returns {undefined} Lzpf compression data
*/
Begin() {
this.clear();
this.reset();
}
/**
@@ -347,7 +384,7 @@ class WTVLzpf {
var uncompressed_len = unencoded_data.byteLength;
var i = 0;
var flags_index = 0;
var hash_index = 0;
while (i < uncompressed_len) {
var code_length = -1;
var code = -1;
@@ -356,48 +393,58 @@ class WTVLzpf {
this.ring_buffer[i & 0x1FFF] = byte;
if (this.match_index > 0) {
if (byte != this.ring_buffer[this.flag] || this.match_index > 0x0127) {
// Cozy time
if (byte != this.ring_buffer[this.ring_bufer_index] || this.match_index > 0x0127) {
// End of matching. Either we no longer match or we reached out limit.
code_length = this.matchEncode[this.match_index][1];
code = this.matchEncode[this.match_index][0];
this.match_index = 0;
this.type_index = 3;
this.compression_mode = 3;
} else {
// Previous iteration found a match so we continue matching until we can't.
this.match_index = (this.match_index + 1) & 0x1FFF;
this.flag = (this.flag + 1) & 0x1FFF;
this.ring_bufer_index = (this.ring_bufer_index + 1) & 0x1FFF;
this.checksum = (this.checksum + byte) & 0xFFFF;
this.working_data = ((this.working_data * 0x0100) + byte) & 0xFFFFFFFF;
i++;
}
} else {
this.flag = 0xFFFF;
this.ring_bufer_index = 0xFFFF;
if (i >= 3) {
flags_index = (this.working_data >>> 0x0B ^ this.working_data) & 0x0FFF;
this.flag = this.flag_table[flags_index];
this.flag_table[flags_index] = i & 0x1FFF;
// Start recoding data so we can lookup matches.
hash_index = (this.working_data >>> 0x0B ^ this.working_data) & 0x0FFF;
this.ring_bufer_index = this.hash_table[hash_index];
this.hash_table[hash_index] = i & 0x1FFF;
} else {
this.type_index++;
// The first three uncompressed bytes aren't used for the matching algorithm.
this.compression_mode++;
}
if (this.flag == 0xFFFF) {
if (this.ring_bufer_index == 0xFFFF) {
// We never seen this byte before so we encode it with our Huffman table.
code_length = this.nomatchEncode[byte][1];
code = this.nomatchEncode[byte][0] << 0x10;
} else if (byte == this.ring_buffer[this.flag] && compress_data) {
} else if (byte == this.ring_buffer[this.ring_bufer_index] && compress_data) {
// Wow dude, a match has been found. Let's switch get our own room in the next iteration to see if we match further.
this.match_index = 1;
this.flag = (this.flag + 1) & 0x1FFF;
this.type_index = 4;
this.ring_bufer_index = (this.ring_bufer_index + 1) & 0x1FFF;
this.compression_mode = 4;
} else {
// We've seen these bytes before but the index in the ring buffer doesn't match so we revert to our neat Huffman table
// We add 1 flag bit of 0 to account for the fact we've had a hash table hit but no hit in the ring buffer.
code_length = this.nomatchEncode[byte][1] + 1;
code = this.nomatchEncode[byte][0] << 0x0F;
}
this.checksum = (this.checksum + byte) & 0xFFFF;
// We work on a 2-byte context so we store the last two bytes so we can do cool lookups with it
this.working_data = ((this.working_data * 0x0100) + byte) & 0xFFFFFFFF;
i++;
}
if (code_length > 0) {
this.EncodeLiteral(code_length, code);
this.AddBits(code, code_length);
}
}
}
@@ -405,7 +452,7 @@ class WTVLzpf {
/**
* Ends a compression stream.
*
* @param type_index {Number} the end type used to finalize
* @param compression_mode {Number} the end type used to finalize
*
* @returns {Buffer} Lzpf compression data
*/
@@ -413,33 +460,34 @@ class WTVLzpf {
var code_length = -1;
var code = -1;
if (this.type_index == 2) {
this.EncodeLiteral(0x10, 0x00990000);
} else if (this.type_index >= 3) {
if (this.type_index == 4) {
if (this.compression_mode == 2) {
this.AddBits(0x00990000, 0x10);
} else if (this.compression_mode >= 3) {
if (this.compression_mode == 4) {
code_length = this.matchEncode[this.match_index][1];
code = this.matchEncode[this.match_index][0];
this.EncodeLiteral(code_length, code);
this.AddBits(code, code_length);
}
var flags_index = (this.working_data >>> 0x0B ^ this.working_data) & 0x0FFF;
var flag = this.flag_table[flags_index];
if (flag == 0xFFFF) {
this.EncodeLiteral(0x10, 0x00990000);
var hash_index = (this.working_data >>> 0x0B ^ this.working_data) & 0x0FFF;
var ring_bufer_index = this.hash_table[hash_index];
if (ring_bufer_index == 0xFFFF) {
this.AddBits(0x00990000, 0x10);
} else {
this.EncodeLiteral(0x11, 0x004c8000);
this.AddBits(0x004C8000, 0x11);
}
}
// Below is just metadata. The compressed block is complete.
// Add checksum bits
this.AddBits((this.checksum << 0x10) & 0xFFFFFFFF, 0x08);
this.AddBits((this.checksum << 0x18) & 0xFFFFFFFF, 0x08);
// Encode checksum
this.EncodeLiteral(0x08, (this.checksum << 0x10) & 0xFFFFFFFF);
this.EncodeLiteral(0x08, (this.checksum << 0x18) & 0xFFFFFFFF);
// If we have leftover bits then add it.
if (this.current_bit_length > 0) {
this.AddByte((this.current_bits >>> 0x18) & 0xFF);
}
// End
this.AddByte((this.current_literal >>> 0x18) & 0xFF);
this.AddByte(0x20);
this.AddByte(this.filler_byte);
return Buffer.from(this.encoded_data);
}

View File

@@ -75,6 +75,7 @@ class WTVMime {
// gzip only
if (content_type.match(/^audio\/(x-)?(s3m|mod|xm)$/)) compress_data = true; // s3m, mod, xm
if (content_type.match(/^audio\/(x-)?(midi|wav|wave|aif(f)?)$/)) compress_data = true; // midi & wav
if (content_type.match(/^application\/karaoke$/)) compress_data = true; // midi karaoke
if (content_type.match(/^binary\/x-wtv-approm$/)) compress_data = true; // approms
if (content_type.match(/^binary\/doom-data$/)) compress_data = true; // DOOM WADs
if (content_type.match(/^wtv\/download-list$/)) compress_data = true; // WebTV Download List
@@ -179,6 +180,9 @@ class WTVMime {
case "wad":
wtv_mime_type = "binary/doom-data";
break;
case "kar":
wtv_mime_type = "application/karaoke";
break;
case "mp2":
case "hsb":
case "rmf":
@@ -190,6 +194,7 @@ class WTVMime {
}
modern_mime_type = this.mime.lookup(path);
if (modern_mime_type === false) modern_mime_type = "application/octet-stream";
if (wtv_mime_type == "") wtv_mime_type = modern_mime_type;
return new Array(wtv_mime_type, modern_mime_type);
}

View File

@@ -199,6 +199,68 @@ class WTVShared {
return path.reverse().split(".")[0].reverse();
}
getLineFromFile(filename, line_no, callback) {
var stream = this.fs.createReadStream(filename, {
flags: 'r',
encoding: 'utf-8',
fd: null,
bufferSize: 64 * 1024
});
var fileData = '';
stream.on('data', function (data) {
fileData += data;
// The next lines should be improved
var lines = fileData.split("\n");
if (lines.length >= +line_no) {
stream.destroy();
callback(null, lines[+line_no]);
}
});
stream.on('error', function () {
callback('Error', null);
});
stream.on('end', function () {
callback('File end reached without finding line', null);
});
}
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.";
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.";
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:
headers = code + " " + data + "\n";
headers += "Content-Type: text/html\n";
break;
}
console.error(" * doErrorPage Called:", code, data);
return new Array(headers, data);
}
/**
* Strips bad things from paths
* @param {string} base Base path

View File

@@ -71,47 +71,31 @@ function getServiceString(service, overrides = {}) {
}
}
// passthrough for old scripts
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.";
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.";
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:
headers = code + " " + data + "\n";
headers += "Content-Type: text/html\n";
break;
}
console.error(" * doErrorPage Called:", code, data);
return new Array(headers, data);
return wtvshared.doErrorPage(code, data, pc_mode);
}
async function sendRawFile(socket, path) {
if (!minisrv_config.config.debug_flags.quiet) console.log(" * Found " + path + " to handle request (Direct File Mode) [Socket " + socket.id + "]");
var contypes = wtvmime.getContentType(path);
var headers = "200 OK\n"
headers += "Content-Type: " + contypes[0] + "\n";
headers += "wtv-modern-content-type" + contypes[1];
fs.readFile(path, null, function (err, data) {
sendToClient(socket, headers, data);
});
}
async function processPath(socket, service_vault_file_path, request_headers = new Array(), service_name) {
var headers, data = null;
var request_is_async = false;
var service_vault_found = false;
var service_path = service_vault_file_path;
var service_path = unescape(service_vault_file_path);
try {
service_vaults.forEach(function (service_vault_dir) {
if (service_vault_found) return;
service_vault_file_path = wtvshared.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];
@@ -121,28 +105,66 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
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];
request_is_async = true;
var errpage = wtvshared.doErrorPage(401, "Access Denied");
sendToClient(socket, errpage[0], errpage[1]);
return;
}
}
}
var is_dir = false;
var file_exists = false;
minisrv_catchall, service_path_split, service_path_request_file = null;
if (fs.existsSync(service_vault_file_path)) {
file_exists = true;
is_dir = fs.lstatSync(service_vault_file_path).isDirectory()
}
if (file_exists && !is_dir) {
// file exists, read it and return it
service_vault_found = true;
request_is_async = true;
if (!minisrv_config.config.debug_flags.quiet) console.log(" * Found " + service_vault_file_path + " to handle request (Direct File Mode) [Socket " + socket.id + "]");
request_headers.service_file_path = service_vault_file_path;
var contypes = wtvmime.getContentType(service_vault_file_path);
headers = "200 OK\n"
headers += "Content-Type: " + contypes[0] + "\n";
headers += "wtv-modern-content-type" + contypes[1];
fs.readFile(service_vault_file_path, null, function (err, data) {
sendToClient(socket, headers, data);
});
request_headers.raw_file = true;
// service parsed files, we might not want to expose our service source files so we can protect them with a flag on the first line
if (wtvshared.getFileExt(service_vault_file_path).toLowerCase() == "js" || wtvshared.getFileExt(service_vault_file_path).toLowerCase() == "txt") {
if (wtvshared.getFileExt(service_vault_file_path).toLowerCase() == "js") {
wtvshared.getLineFromFile(service_vault_file_path, 0, function (status, line) {
if (!status) {
if (line.match(/minisrv\_service\_file.*true/i)) {
var errpage = wtvshared.doErrorPage(403, "Access Denied");
sendToClient(socket, errpage[0], errpage[1]);
} else {
sendRawFile(socket, service_vault_file_path);
}
} else {
var errpage = wtvshared.doErrorPage(400);
sendToClient(socket, errpage[0], errpage[1]);
}
});
}
if (wtvshared.getFileExt(service_vault_file_path).toLowerCase() == "txt") {
wtvshared.getLineFromFile(service_vault_file_path, 0, function (status, line) {
if (!status) {
if (line.match(/^#!minisrv/i)) {
var errpage = wtvshared.doErrorPage(403, "Access Denied");
sendToClient(socket, errpage[0], errpage[1]);
} else {
sendRawFile(socket, service_vault_file_path);
}
} else {
var errpage = wtvshared.doErrorPage(400);
sendToClient(socket, errpage[0], errpage[1]);
}
});
}
} else {
// not a potential service file, so save to send
sendRawFile(socket, service_vault_file_path);
}
} else if (fs.existsSync(service_vault_file_path + ".txt")) {
// raw text format, entire payload expected (headers and content)
service_vault_found = true;
@@ -211,6 +233,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
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)) {
service_vault_found = true;
if (!minisrv_config.config.debug_flags.quiet) console.log(" * Found catchall at " + catchall_file + " to handle request (JS Interpreter Mode) [Socket " + socket.id + "]");
request_headers.service_file_path = catchall_file;
var jscript_eval = fs.readFileSync(catchall_file).toString();
@@ -228,7 +251,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
// either `request_is_async`, or `headers` and `data` MUST be defined by this point!
});
} catch (e) {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1] + "<br><br>The interpreter said:<br><pre>" + e.toString() + "</pre>";
console.error(" * Scripting error:",e);
@@ -236,12 +259,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, null, socket.minisrv_pc_mode);
var errpage = wtvshared.doErrorPage(404, null, socket.minisrv_pc_mode);
headers = errpage[0];
data = errpage[1];
}
if (headers == null && !request_is_async) {
var errpage = doErrorPage(400, null, socket.minisrv_pc_mode);
var errpage = wtvshared.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:")
@@ -316,7 +339,7 @@ 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) {
} else if (shortURL.indexOf(":") == -1 && request_headers.request.indexOf("HTTP/1") > 0 && socket.ssid == null) {
if (request_headers.Host) {
if (minisrv_config.config.pc_server_hidden_service_enabled) {
// browsers typically send a Host header
@@ -329,7 +352,7 @@ async function processURL(socket, request_headers) {
} 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);
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;
@@ -356,7 +379,7 @@ async function processURL(socket, request_headers) {
}
// assume webtv since there is a :/ in the GET
var service_name = shortURL.split(':/')[0];
var urlToPath = service_name + path.sep + shortURL.split(':/')[1];
var urlToPath = wtvshared.fixPathSlashes(service_name + path.sep + shortURL.split(':/')[1]);
if (minisrv_config.config.debug_flags.show_headers) console.log(" * Incoming headers on socket ID", socket.id, (await wtvshared.filterSSID(request_headers)));
socket_sessions[socket.id].request_headers = request_headers;
processPath(socket, urlToPath, request_headers, service_name);
@@ -364,7 +387,7 @@ async function processURL(socket, request_headers) {
doHTTPProxy(socket, request_headers);
} else {
// error reading headers (no request_url provided)
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1]
socket_sessions[socket.id].close_me = true;
@@ -465,11 +488,11 @@ async function doHTTPProxy(socket, request_headers) {
});
}).on('error', function (err) {
var errpage, headers, data = null;
if (err.code == "ENOTFOUND") errpage = doErrorPage(400, `The publisher ${request_data.host} is unknown.`);
else if (err.message.indexOf("HostUnreachable") > 0) errpage = doErrorPage(400, `The publisher ${request_data.host} could not be reached.`);
if (err.code == "ENOTFOUND") errpage = wtvshared.doErrorPage(400, `The publisher ${request_data.host} is unknown.`);
else if (err.message.indexOf("HostUnreachable") > 0) errpage = wtvshared.doErrorPage(400, `The publisher ${request_data.host} could not be reached.`);
else {
console.log(" * Unhandled Proxy Request Error:", err);
errpage = doErrorPage(400);
errpage = wtvshared.doErrorPage(400);
}
headers = errpage[0];
data = errpage[1];
@@ -521,7 +544,18 @@ function headerStringToObj(headers, response = false) {
headers_obj.http_response = d.replace("\r", "");
} else if (/^(GET |PUT |POST)$/.test(d.substring(0, 4)) && !response) {
headers_obj.request = d.replace("\r", "");
headers_obj.request_url = decodeURI(d.split(' ')[1]).replace("\r", "");
var request_url = d.split(' ');
if (request_url.length > 2) {
request_url.shift();
request_url = request_url.join(" ");
if (request_url.indexOf("HTTP/") > 0) {
var index = request_url.indexOf(" HTTP/");
request_url = request_url.substring(0, index);
}
} else {
request_url = request_url[1];
}
headers_obj.request_url = decodeURI(request_url).replace("\r", "");
} else if (d.indexOf(":") > 0) {
var d_split = d.split(':');
var header_name = d_split[0];
@@ -578,7 +612,7 @@ async function sendToClient(socket, headers_obj, data) {
if (socket_sessions[socket.id]) {
if (socket_sessions[socket.id].request_headers) {
if (socket_sessions[socket.id].request_headers.service_file_path) {
if (wtvshared.getFileExt(socket_sessions[socket.id].request_headers.service_file_path).toLowerCase() !== "js") {
if (wtvshared.getFileExt(socket_sessions[socket.id].request_headers.service_file_path).toLowerCase() !== "js" || socket_sessions[socket.id].request_headers.raw_file === true) {
var last_modified = wtvshared.getFileLastModifiedUTCString(socket_sessions[socket.id].request_headers.service_file_path);
if (last_modified) headers_obj["Last-Modified"] = last_modified;
}
@@ -836,7 +870,7 @@ function checkSecurity(socket) {
if (blacklist) console.log(" * Request from SSID", wtvshared.filterSSID(ssid), "(" + socket.remoteAddr + "), but that SSID is in the blacklist, rejecting.");
else console.log(" * Request from SSID", wtvshared.filterSSID(socket.ssid), "(" + socket.remoteAddress + "), but that SSID is not in the whitelist, rejecting.");
var errpage = doErrorPage(401, "Access to this service is denied.");
var errpage = wtvshared.doErrorPage(401, "Access to this service is denied.");
out = errpage;
}
@@ -942,7 +976,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
var enc_data = CryptoJS.enc.Hex.parse(data_hex.substring(header_length * 2));
if (enc_data.sigBytes > 0) {
if (!socket_sessions[socket.id].wtvsec) {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
headers += "wtv-visit: client:relog\n";
data = errpage[1];
@@ -1010,7 +1044,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
if (!ssid_sessions[socket.ssid].data_store.wtvsec_login) {
ssid_sessions[socket.ssid].data_store.wtvsec_login = new WTVSec(minisrv_config);
ssid_sessions[socket.ssid].data_store.wtvsec_login.IssueChallenge();
ssid_sessions[socket.ssid].data_store.wtvsec_login.set_incarnation(headers["wtv-incarnation"]);
if (headers["wtv-incarnation"]) ssid_sessions[socket.ssid].data_store.wtvsec_login.set_incarnation(headers["wtv-incarnation"]);
ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64 = headers["wtv-ticket"];
ssid_sessions[socket.ssid].data_store.wtvsec_login.DecodeTicket(ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64);
} else {
@@ -1018,7 +1052,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
if (minisrv_config.config.debug_flags.debug) console.log(" # New ticket from client");
ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64 = headers["wtv-ticket"];
ssid_sessions[socket.ssid].data_store.wtvsec_login.DecodeTicket(ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64);
ssid_sessions[socket.ssid].data_store.wtvsec_login.set_incarnation(headers["wtv-incarnation"]);
if (headers["wtv-incarnation"]) ssid_sessions[socket.ssid].data_store.wtvsec_login.set_incarnation(headers["wtv-incarnation"]);
}
}
}
@@ -1077,7 +1111,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
if (minisrv_config.config.debug_flags.show_headers) console.log(secure_headers);
if (!secure_headers.request) {
socket_sessions[socket.id].secure = false;
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
headers = errpage[0];
data = errpage[1];
sendToClient(socket, headers, data);
@@ -1138,7 +1172,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
processURL(socket, headers);
} else if (socket_sessions[socket.id].post_data.length > (socket_sessions[socket.id].post_data_length * 2)) {
// got too much data ? ... should not ever reach this code (section 2)
var errpage = 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) + " (2)";
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) + " (2)";
headers = errpage[0];
data = errpage[1];
sendToClient(socket, headers, data);
@@ -1220,7 +1254,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
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 = 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);
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);
@@ -1238,7 +1272,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
var enc_data = CryptoJS.enc.Hex.parse(data_hex);
if (enc_data.sigBytes > 0) {
if (!socket_sessions[socket.id].wtvsec) {
var errpage = doErrorPage(400);
var errpage = wtvshared.doErrorPage(400);
var headers = errpage[0];
headers += "wtv-visit: client:relog\n";
data = errpage[1];
@@ -1330,6 +1364,7 @@ async function cleanupSocket(socket) {
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.ssid = null;
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))

View File

@@ -1,6 +1,6 @@
{
"config": {
"service_ip": "0.0.0.0",
"service_ip": "127.0.0.1",
"ServiceVaults": [
"UserServiceVault",
"ServiceVault"
@@ -17,7 +17,7 @@
"post_data_socket_timeout": 30,
"error_log_file": "errors.log",
"catchall_file_name": "catchall.js",
"enable_lzpf_compression": false,
"enable_lzpf_compression": true,
"enable_gzip_compression": true,
"pc_server_hidden_service": "http_pc",
"pc_server_hidden_service_enabled": false,
@@ -59,7 +59,7 @@
"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/build7588/daily-nondebug/bf0app-part000.rom",
"catchall_file_name": "content-serve.js"
},
"wtv-setup": {

View File

@@ -1,6 +1,6 @@
{
"name": "zefie_wtvp_minisrv",
"version": "0.9.18",
"version": "0.9.20",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -36,9 +36,9 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"crypto-js": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
"integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg=="
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
},
"data-uri-to-buffer": {
"version": "3.0.1",
@@ -444,9 +444,9 @@
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"vm2": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.3.tgz",
"integrity": "sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q=="
"version": "3.9.5",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.5.tgz",
"integrity": "sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng=="
},
"word-wrap": {
"version": "1.2.3",

View File

@@ -1,6 +1,6 @@
{
"name": "zefie_wtvp_minisrv",
"version": "0.9.19",
"version": "0.9.21",
"description": "WebTV Service (WTVP) Emulation Server",
"main": "app.js",
"homepage": "https://github.com/zefie/zefie_wtvp_minisrv",
@@ -26,7 +26,7 @@
"url": "https://github.com/zefie/zefie_wtvp_minisrv.git"
},
"dependencies": {
"crypto-js": "^4.0.0",
"crypto-js": "^4.1.1",
"easy-crc": "0.0.2",
"endianness": "^8.0.2",
"mime-types": "^2.1.31",