- numerous bug fixes
 - improve session retention
 - use wtv-head-waiter:/relogin for boot url
   - viewer seems to retain only wtv-* and wtv-head-waiter, so lets try to be closer to protocol and boot with a wtv-head-waiter address instead of wtv-1800
   - we still handle via wtv-1800 but we accept wtv-head-waiter:/relogin and send the client on its way to the relogin path
 - update wtv-home:/home
   - remove spacing in favor of right alignment
   - add compression status
 - guest mode session store update
   - allow calls to saveSessionData() but do not actually write if user is guest
   - saveSessionData() returns true even if guest, because false is meant to define an error
   - You can also use SaveIfRegistered(), this will return false on both saveSessionData() errors AND guest mode;
   - if you want to block guests, check for isRegistered() and block the request if it is false
   - otherwise this update will allow all tools (including any logins) to work with guest mode, but the stored SessionData will not be persistently saved, and lost when the cleanup timeout hits (default 3 min), or the server is restarted.
 - more accurately mimic WTVP by accepting URLs without /
 - use service-style cookie links on tricks
 - 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
 - flashrom system updates
   - fix bugs
   - more WNI-like flow path
   - make scripts use `service_name` variable so that they should work in a renamed service (eg not wtv-flashrom, untested)
 - rewrite wtv-disk system
   - move wtv-update to wtv-disk
   - allow accessing wtv-disk:/sync?group=&diskmap=
   - rewrite Download List generation to be more proper
   - only send files if diskmap has changed
   - allow force redownload with &force=true
This commit is contained in:
zefie
2021-08-08 18:19:56 -04:00
parent df472ab91f
commit 6f2fa1d510
53 changed files with 863 additions and 445 deletions

View File

@@ -11,7 +11,7 @@ This open source server is in alpha status. Use at your own risk.
- Suports `.js` service files with synchronous or asynchronous requests
- Supports multiple simultaneous users
- WebTV-compatible HTTP(S) Proxy (via minisrv, or using an external proxy for enhanced features (such as [WebOne](https://github.com/atauenis/webone))
- WebTV Cookie (wtv-cookie) support for HTTP(s)
- 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
@@ -31,7 +31,7 @@ This open source server is in alpha status. Use at your own risk.
### Feature Todo:
- wtv-lzpf support *(Milestone v1.0)*
- 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)***
- ~~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)***
- ~~SSID/IP black/whitelisting (including tying SSID to an IP or multiple IPs)~~ ***Done [v0.9.4](https://github.com/zefie/zefie_wtvp_minisrv/releases/tag/v0.9.4)***
- ~~Flashrom flashing functionality (at least for LC2 and higher)~~ ***Done [v0.8.0](https://github.com/zefie/zefie_wtvp_minisrv/releases/tag/v0.8.0)***

View File

@@ -11,12 +11,15 @@ services:
minisrv:
build: ./minisrv
restart: unless-stopped
stop_signal: SIGINT
links:
- webone
ports:
- "1600-1699:1600-1699"
volumes:
- /home/zefie/docker/wtvminisrv/user_config.json:/opt/minisrv/zefie_wtvp_minisrv/user_config.json:ro
- /home/zefie/docker/wtvminisrv/UserServiceVault:/opt/minisrv/zefie_wtvp_minisrv/UserServiceVault
- /home/zefie/docker/wtvminisrv/UserServiceVault:/opt/minisrv/zefie_wtvp_minisrv/UserServiceVault:ro
- /home/zefie/docker/wtvminisrv/SessionStore:/opt/minisrv/zefie_wtvp_minisrv/SessionStore
- /home/zefie/docker/wtvminisrv/ServiceLogPost:/opt/minisrv/zefie_wtvp_minisrv/ServiceLogPost

View File

@@ -1,6 +1,7 @@
FROM node:current-alpine
RUN apk add git
RUN npm install -g npm@latest 2>/dev/null > /dev/null
RUN cd /opt && git clone --depth=1 https://github.com/zefie/zefie_wtvp_minisrv.git minisrv
RUN cd /opt/minisrv && git config pull.ff only
RUN cd /opt/minisrv/zefie_wtvp_minisrv && npm install
@@ -10,3 +11,4 @@ RUN chmod +x /opt/minisrv/zefie_wtvp_minisrv/run.sh
WORKDIR /opt/minisrv/zefie_wtvp_minisrv
CMD ./run.sh

View File

@@ -22,6 +22,11 @@ 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_enabled": false,
"pc_server_hidden_service": "http_pc"
```
Set `pc_server_hidden_service_enabled` option to `true` to enable the HTTP Server for Browsers. Set `pc_server_hidden_service` to a directory under the ServiceVaults to use solely for PC requests. See `ServiceVault/http_pc` for some example code.
```
"post_percentages": [ 0, 25, 50, 100]
```

View File

@@ -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];
}

View File

@@ -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 = `<html>
<head>
<display hideoptions nostatus showwhencomplete skipback clearback fontsize=medium>
<title>zefie minisrv ${minisrv_config.version}</title>
<meta http-equiv=Refresh content="4; url=wtv-home:/home?">
</head>
<body bgcolor="#000000" text="#449944">
<center>
<spacer type=block height=88 width=21>
<img src="/get?url=${escape('wtv-star:/ROMCache/spacer.gif')}" height=4><br>
<img src="/get?url=${escape(splash_logo)}">
<br><br><br>
<p><br>
<p><br>
<table border>
<tr><td width=150>
Mini service
<tr><td>
zefie minisrv v${minisrv_config.version}`;
if (minisrv_config.config.git_commit) data += " (git " + minisrv_config.config.git_commit + ")";
data += `
</table>
</center>
</body>
</html>`;

View File

@@ -144,7 +144,7 @@ if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
headers += getServiceString('wtv-flashrom') + "\n";
if (bf0app_update) headers += "wtv-boot-url: " + gourl + "\n";
else {
headers += "wtv-boot-url: wtv-1800:/preregister?relogin=true";
headers += "wtv-boot-url: wtv-head-waiter:/relogin?relogin=true";
if (request_headers.query.guest_login) headers += "&guest_login=true";
headers += "\n";
}

View File

@@ -0,0 +1,311 @@
// todo: async
var force_update = (request_headers.query.force == "true") ? true : false;
console.log(force_update);
if (request_headers['wtv-request-type'] == 'download') {
var path = require("path");
var content_dir = "content/"
var diskmap_dir = content_dir + "diskmaps/";
function generateDownloadList(diskmap_group_data, update_list, diskmap_data) {
// create WebTV Download List
var newest_file_epoch = 0;
var download_list = '';
if (diskmap_data.execute && diskmap_data.execute_when == "atStart") {
download_list += "EXECUTE " + diskmap_data.execute + "\n\n";
}
if (diskmap_data.partition_size) {
download_list += "CREATE " + diskmap_data.base + "\n";
download_list += "partition-size: " + diskmap_data.partition_size + "\n\n";
}
download_list += "CREATE-GROUP " + diskmap_group_data + "-UPDATE\n";
download_list += "state: invalid\n";
download_list += "base: " + diskmap_data.base + ".GROUP-UPDATE/\n\n";
download_list += "CREATE-GROUP " + diskmap_group_data + "\n";
download_list += "state: invalid\n";
download_list += "service-owned: " + (diskmap_data.service_owned || false) + "\n";
download_list += "base: " + diskmap_data.base + "\n\n";
Object.keys(update_list).forEach(function (k) {
if (!update_list[k].invalid) return;
download_list += "DELETE " + update_list[k].file.replace(diskmap_data.base, "") + "\n";
download_list += "group: " + diskmap_group_data + "\n\n";
});
Object.keys(update_list).forEach(function (k) {
if (update_list[k].checksum_match && !force_update) return;
if (!update_list[k].invalid && !force_update) return;
download_list += "DISPLAY " + update_list[k].display + "\n\n";
download_list += "GET " + update_list[k].file.replace(diskmap_data.base, "") + "\n";
download_list += "group: " + diskmap_group_data + "-UPDATE\n";
download_list += "location: " + service_name + ":/" + update_list[k].location + "\n";
download_list += "file-permission: r\n"
download_list += "wtv-checksum: " + update_list[k].checksum + "\n";
download_list += "service-source-location: /webtv/content/" + service_name.replace("wtv-", "") + "d/" + update_list[k].location + "\n";
download_list += "client-dest-location: " + update_list[k].file + "\n\n";
});
download_list += "CREATE-GROUP " + diskmap_group_data + "\n";
download_list += "state: invalid\n";
download_list += "service-owned: " + (diskmap_data.service_owned || false) + "\n";
download_list += "base: " + diskmap_data.base + "\n\n";
Object.keys(update_list).forEach(function (k) {
if (!update_list[k].invalid) return;
download_list += "RENAME " + update_list[k].file.replace(diskmap_data.base, "") + "\n";
download_list += "group: " + diskmap_group_data + "-UPDATE\n";
download_list += "destination-group: " + diskmap_group_data + "\n";
download_list += "location: " + update_list[k].file.replace(diskmap_data.base, "") + "\n\n";
});
download_list += "SET-GROUP " + diskmap_group_data + "\n";
download_list += "state: ok\n";
download_list += "version: " + diskmap_data.version + "\n";
download_list += "last-checkup-time: " + new Date().toUTCString().replace("GMT", "+0000") + "\n\n";
if (diskmap_data.execute && diskmap_data.execute_when == "atEnd") {
download_list += "EXECUTE " + diskmap_data.execute + "\n\n";
}
download_list += "DELETE-GROUP " + diskmap_group_data + "-UPDATE\n\n";
download_list += "DELETE " + diskmap_data.base + ".GROUP-UPDATE/\n\n";
console.log(download_list);
return download_list;
}
function processGroup(diskmap_primary_group, diskmap_group_data, diskmap_subgroup = null) {
// parse webtv post
var output_data = '';
var post_data = request_headers.post_data.toString(CryptoJS.enc.Latin1).split("\n");
var post_data_current_directory = '';
var post_data_current_file = false;
var post_data_current_group = '';
var post_data_last_modified = false;
var post_data_content_length = false;
var post_data_current_group_state = false;
var post_data_fileinfo = new Array();
var entry_type = false;
var post_data_current_version = false;
var post_data_current_checksum = false;
var post_data_last_checkup_time = 0;
Object.keys(post_data).forEach(function (k) {
if (post_data[k].substring(0, 7) == "file://") {
entry_type = "folder";
post_data_current_file = false;
post_data_current_version = false;
post_data_last_checkup_time = 0;
post_data_current_group = '';
post_data_current_group_state = false;
post_data_last_modified = false;
post_data_content_length = false;
post_data_current_checksum = false;
post_data_current_directory = post_data[k];
} else {
if (post_data[k].indexOf(":") > 0) {
var post_data_line = post_data[k].split(": ")
var post_data_line_name = post_data_line[0];
post_data_line.shift();
var post_data_line_data = post_data_line.join(": ");
switch (post_data_line_name.toLowerCase()) {
case "last-modified":
post_data_last_modified = (new Date(new Date(Date.parse(post_data_line_data)).toUTCString()) / 1000);
break;
case "content-length":
post_data_content_length = parseInt(post_data_line_data);
break;
case "version":
post_data_current_version = parseInt(post_data_line_data);
break;
case "group":
post_data_current_group = post_data_line_data;
break;
case "state":
post_data_current_group_state = post_data_line_data;
break;
case "wtv-checksum":
post_data_current_checksum = post_data_line_data;
break;
case "last-checkup-time":
post_data_last_checkup_time = (new Date(new Date(Date.parse(post_data_line_data)).toUTCString()) / 1000);
break;
}
} else {
if (!entry_type && post_data[k] != "") {
entry_type = "file";
post_data_current_file = post_data[k];
}
if (post_data[k] == "" && entry_type) {
var post_data_current_path = ((entry_type == "file") ? (post_data_current_directory + post_data_current_file) : post_data_current_directory);
var index = post_data_current_path.replace(/[\:\/]/g, "_").toLowerCase() + "_" + post_data_current_group;
if (index.match(/\/$/)) entry_type = "folder";
if (!post_data_fileinfo[index]) post_data_fileinfo[index] = new Array();
post_data_fileinfo[index].entry_type = entry_type;
post_data_fileinfo[index].file = post_data_current_path;
post_data_fileinfo[index].group = post_data_current_group;
post_data_fileinfo[index].version = post_data_current_version || 0;
if (post_data_current_checksum) post_data_fileinfo[index].checksum = post_data_current_checksum;
if (post_data_current_group_state) post_data_fileinfo[index].state = post_data_current_group_state;
if (post_data_last_checkup_time) post_data_fileinfo[index].last_checkup = post_data_last_checkup_time;
if (post_data_last_modified) post_data_fileinfo[index].last_modified = post_data_last_modified;
if (post_data_content_length) post_data_fileinfo[index].content_length = post_data_content_length;
entry_type = false;
}
}
}
});
var wtv_download_list = new Array();
var newest_file_epoch = 0;
Object.keys(diskmap_group_data.files).forEach(function (k) {
if (!diskmap_group_data.files[k].location) diskmap_group_data.files[k].location = diskmap_group_data.location + diskmap_group_data.files[k].file.replace(diskmap_group_data.base, "");
var post_match_file = null;
Object.keys(service_vaults).forEach(function (g) {
if (post_match_file != null) return;
post_match_file = service_vaults[g] + "/" + service_name + "/" + diskmap_group_data.files[k].location;
if (!fs.existsSync(post_match_file)) post_match_file = null;
});
var post_match_file_lstat = fs.lstatSync(post_match_file);
var post_match_file_data = new Buffer.from(fs.readFileSync(post_match_file, {
encoding: null,
flags: 'r'
}));
diskmap_group_data.files[k].base = diskmap_group_data.base;
diskmap_group_data.files[k].last_modified = (new Date(new Date(post_match_file_lstat.mtime).toUTCString()) / 1000);
diskmap_group_data.files[k].content_length = post_match_file_lstat.size;
diskmap_group_data.files[k].checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(post_match_file_data)).toString(CryptoJS.enc.Hex).toLowerCase();
if (parseInt(diskmap_group_data.files[k].last_modified) > newest_file_epoch) newest_file_epoch = parseInt(diskmap_group_data.files[k].last_modified);
if (!diskmap_group_data.files[k].display) diskmap_group_data.files[k].display = diskmap_group_data.display;
diskmap_group_data.files[k].invalid = true;
wtv_download_list.push(diskmap_group_data.files[k]);
});
// check to see if client says they have this version
diskmap_group_data.version = newest_file_epoch;
Object.keys(wtv_download_list).forEach(function (k) {
wtv_download_list[k].version = newest_file_epoch;
Object.keys(post_data_fileinfo).forEach(function (g) {
if (post_data_fileinfo[g].file == wtv_download_list[k] || post_data_fileinfo[g].file == wtv_download_list[k].base) {
diskmap_group_data.group_exists = true;
if (diskmap_group_data.files[k].checksum.toLowerCase() == post_data_fileinfo[g].checksum) wtv_download_list[k].invalid = false;
else if (post_data_fileinfo[g].version == wtv_download_list[k].version && post_data_fileinfo[g].state != "invalid") wtv_download_list[k].invalid = false;
}
});
});
var diskmap_group_name = (diskmap_subgroup == null) ? diskmap_primary_group : diskmap_primary_group + "-" + diskmap_subgroup;
output_data = generateDownloadList(diskmap_group_name, wtv_download_list, diskmap_group_data);
return output_data;
}
if (request_headers.query.diskmap && request_headers.query.group && request_headers.post_data) {
var diskmap_json_file = null;
Object.keys(service_vaults).forEach(function (g) {
if (diskmap_json_file != null) return;
diskmap_json_file = service_vaults[g] + "/" + service_name + "/" + diskmap_dir + request_headers.query.diskmap + ".json";
if (!fs.existsSync(diskmap_json_file)) diskmap_json_file = null;
});
if (diskmap_json_file != null) {
if (fs.lstatSync(diskmap_json_file)) {
try {
// read diskmap
var diskmap_data = JSON.parse(fs.readFileSync(diskmap_json_file).toString());
if (!diskmap_data[request_headers.query.group]) {
throw ("Invalid diskmap data (group does not match)");
}
data = '';
diskmap_data = diskmap_data[request_headers.query.group];
if (!diskmap_data.location) {
Object.keys(diskmap_data).forEach(function (k) {
if (diskmap_data[k]) data += processGroup(request_headers.query.group, diskmap_data[k], k);
});
} else {
data = processGroup(request_headers.query.group, diskmap_data);
}
headers = "200 OK\nContent-Type: wtv/download-list";
} catch (e) {
var errpage = 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.");
headers = errpage[0];
data = errpage[1];
if (zdebug) console.error(" # " + service_name +":/sync error", "could not find diskmap");
}
} else {
var errpage = doErrorPage(400);
headers = errpage[0];
data = errpage[1];
if (zdebug) console.error(" # " + service_name + ":/sync error", "missing query arguments");
}
} else if (request_headers.query.group && request_headers.query.diskmap) {
var message = request_headers.query.message || "Retrieving files...";
var main_message = request_headers.query.main_message || "Your receiver is downloading files.";
headers = `200 OK
Content-Type: text/html`;
data = `
<html>
<head>
<meta
http-equiv=refresh
content="0;url=client:Fetch?group=${escape(request_headers.query.group)}&source=${service_name}:/sync%3Fdiskmap%3D${escape(escape(request_headers.query.diskmap))}%26force%3D${force_update}&message=${escape(message)}"
>
<display downloadsuccess="client:ShowAlert?message=Download%20successful%21&buttonlabel1=Okay&buttonaction1=client:goback&image=${minisrv_config.config.service_logo}&noback=true" downloadfail="client:ShowAlert?message=Download%20failed...&buttonlabel1=Okay...&buttonaction1=client:goback&image=${minisrv_config.config.service_logo}&noback=true">
<title>Retrieving files...</title>
</head>
<body bgcolor=#0 text=#42CC55 fontsize=large hspace=0 vspace=0>
<table cellspacing=0 cellpadding=0>
<tr>
<td width=104 height=74 valign=middle align=center bgcolor=3B3A4D>
<img src="${minisrv_config.config.service_logo}" width=86 height=64>
<td width=20 valign=top align=left bgcolor=3B3A4D>
<spacer>
<td colspan=2 width=436 valign=middle align=left bgcolor=3B3A4D>
<font color=D6DFD0 size=+2><blackface><shadow>
<spacer type=block width=1 height=4>
<br>
${message}
</shadow>
</blackface>
</font>
<tr>
<td width=104 height=20>
<td width=20>
<td width=416>
<td width=20>
<tr>
<td colspan=2>
<td>
<font size=+1>
${main_message}
<p>This may take a while.
</font>
<tr>
<td colspan=2>
<td>
<br><br>
<font color=white>
<progressindicator name="downloadprogress"
message="Preparing..."
height=40 width=250>
</font>
</table>
</body>
</html>
`;
}

View File

@@ -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);
});

View File

@@ -22,10 +22,10 @@ if (request_headers.query.raw || bf0app_update) {
sendToClient(socket, headers, data);
});
} else {
headers = "200 OK\n"
if (request_headers.query.path) {
headers += "Content-type: text/html\n"
headers += "wtv-visit: wtv-flashrom:/initiate-lc2-download?path=" + request_headers.query.path;
headers = "200 OK\n"
headers += "wtv-visit: " + service_name + ":/initiate-lc2-download?path=" + request_headers.query.path + "\n";
headers += "Content-type: text/html"
data = '';
} else {
var errpage = doErrorPage(404)

View File

@@ -20,11 +20,10 @@ if (!request_headers.query.path) {
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.is_last_part) {
flashrom_info.next_rompath = flashrom_info.next_rompath.replace("get-by-path", "get-lc2-page").replace("&raw=true", "&numparts=" + flashrom_info.part_count);
}
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) {
flashrom_info.next_rompath = request_headers.request_url.replace(escape(request_headers.query.path), escape(flashrom_info.next_rompath.replace(service_name+":/","")));
}
headers = `200 OK
Content-type: text/html`
@@ -43,12 +42,12 @@ hspace=0 vspace=0 fontsize="large">
<td width=104 height=74 valign=middle align=center bgcolor="3B3A4D">
<img src="${minisrv_config.config.service_logo}" width=87 height=67>
<td width=20 valign=top align=left bgcolor="3B3A4D">
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=1 height=1>
<img src="${service_name}:/ROMCache/Spacer.gif" width=1 height=1>
<td colspan=10 width=436 valign=middle align=left bgcolor="3B3A4D">
<font color="D6DFD0" size="+2">
<blackface>
<shadow>
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=1 height=4>
<img src="${service_name}:/ROMCache/Spacer.gif" width=1 height=4>
<br>
Updating now
</shadow>
@@ -56,7 +55,7 @@ Updating now
</font>
<tr>
<td colspan=12 width=560 height=10 valign=top align=left>
<img src="wtv-flashrom:/ROMCache/S40H1.gif" width=560 height=6>
<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>
@@ -92,7 +91,7 @@ data += `
<br><br><br><br><br>
<upgradeblock width=280 height=15
nexturl="${flashrom_info.next_rompath}"
errorurl="wtv-flashrom:/lc2-download-failed?"
errorurl="${service_name}:/lc2-download-failed?"
blockurl="${flashrom_info.rompath}"
lastblock="${flashrom_info.is_last_part}"
curblock="` + (flashrom_info.part_number + 1) + `"
@@ -103,14 +102,14 @@ curblock="` + (flashrom_info.part_number + 1) + `"
data += `>
<font size="-1" color="#D6DFD0">
<br>
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=2 height=10><br>
<img src="${service_name}:/ROMCache/Spacer.gif" width=2 height=10><br>
${flashrom_info.message}
<br><br>
<tr>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>
<td colspan=10 height=2 valign=middle align=center bgcolor="#191919">
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=436 height=1>
<img src="${service_name}:/ROMCache/Spacer.gif" width=436 height=1>
<tr>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>
@@ -119,7 +118,7 @@ ${flashrom_info.message}
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>
<td colspan=10 height=2 valign=top align=left bgcolor="#191919">
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=436 height=1>
<img src="${service_name}:/ROMCache/Spacer.gif" width=436 height=1>
<tr>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>

View File

@@ -1,13 +1,12 @@
if (request_headers.query.path) {
var url = "wtv-flashrom:/get-lc2-page?path=" + 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(url.replace("get-lc2-page", "get-by-path"));
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);
}
headers = "300 OK\n";
headers = "200 OK\n";
headers += "wtv-visit: " + url + "\n";
headers += "Location: " + url + "\n";
headers += "Content-type: text/html";
data = '';
} else {

View File

@@ -25,12 +25,12 @@ hspace=0 vspace=0 fontsize="large">
<td width=104 height=74 valign=middle align=center bgcolor="3B3A4D">
<img src="${minisrv_config.config.service_logo}" width=87 height=67>
<td width=20 valign=top align=left bgcolor="3B3A4D">
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=1 height=1>
<img src="${service_name}:/ROMCache/Spacer.gif" width=1 height=1>
<td colspan=10 width=436 valign=middle align=left bgcolor="3B3A4D">
<font color="D6DFD0" size="+2">
<blackface>
<shadow>
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=1 height=4>
<img src="${service_name}:/ROMCache/Spacer.gif" width=1 height=4>
<br>
Updating complete
</shadow>
@@ -38,7 +38,7 @@ Updating complete
</font>
<tr>
<td colspan=12 width=560 height=10 valign=top align=left>
<img src="wtv-flashrom:/ROMCache/S40H1.gif" width=560 height=6>
<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>
@@ -65,7 +65,7 @@ The update is complete.<br>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>
<td colspan=10 height=2 valign=middle align=center bgcolor="2B2B2B">
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=436 height=1>
<img src="${service_name}:/ROMCache/Spacer.gif" width=436 height=1>
<tr>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>
@@ -74,7 +74,7 @@ The update is complete.<br>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>
<td colspan=10 height=2 valign=top align=left bgcolor="0D0D0D">
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=436 height=1>
<img src="${service_name}:/ROMCache/Spacer.gif" width=436 height=1>
<tr>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>

View File

@@ -32,7 +32,7 @@ if (request_headers.query.error) {
}
var try_again_url = 'wtv-flashrom:/willie';
var try_again_url = service_name + ":/willie";
var try_again_url_path = ''
var try_again_url_start_time = parseInt(new Date().toUTCString()) / 1000;
@@ -53,12 +53,12 @@ data = `<html>
<td width=104 height=74 valign=middle align=center bgcolor="3B3A4D">
<img src="${minisrv_config.config.service_logo}" width=87 height=67>
<td width=20 valign=top align=left bgcolor="3B3A4D">
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=1 height=1>
<img src="${service_name}:/ROMCache/Spacer.gif" width=1 height=1>
<td colspan=10 width=436 valign=middle align=left bgcolor="3B3A4D">
<font color="D6DFD0" size="+2">
<blackface>
<shadow>
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=1 height=4>
<img src="${service_name}:/ROMCache/Spacer.gif" width=1 height=4>
<br>
Updating failed
</shadow>
@@ -66,7 +66,7 @@ data = `<html>
</font>
<tr>
<td colspan=12 width=560 height=10 valign=top align=left>
<img src="wtv-flashrom:/ROMCache/S40H1.gif" width=560 height=6>
<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>
@@ -96,7 +96,7 @@ data = `<html>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>
<td colspan=10 height=2 valign=middle align=center bgcolor="2B2B2B">
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=436 height=1>
<img src="${service_name}:/ROMCache/Spacer.gif" width=436 height=1>
<tr>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>
@@ -105,7 +105,7 @@ data = `<html>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>
<td colspan=10 height=2 valign=top align=left bgcolor="0D0D0D">
<img src="wtv-flashrom:/ROMCache/Spacer.gif" width=436 height=1>
<img src="${service_name}:/ROMCache/Spacer.gif" width=436 height=1>
<tr>
<td width=104 valign=middle align=center>
<td width=20 valign=middle align=center>

View File

@@ -9,7 +9,7 @@ if (request_headers.query.vflash) delete request_headers.query.vflash;
if (request_headers.query.pflash) delete request_headers.query.pflash;
for (const [key, value] of Object.entries(request_headers.query)) {
proxy_query += "&" + key + "=" + value;
proxy_query += "&" + key + "=" + escape(value);
}
if (!minisrv_config.services[service_name].use_zefie_server) {
@@ -18,7 +18,7 @@ if (!minisrv_config.services[service_name].use_zefie_server) {
var options = {
host: "wtv.zefie.com",
path: "/willie.php?minisrv=true&pflash=" + ssid_sessions[socket.ssid].get("wtv-client-rom-type") + proxy_query,
path: "/willie.php?minisrv=true&service_name="+escape(service_name)+"&pflash=" + ssid_sessions[socket.ssid].get("wtv-client-rom-type") + proxy_query,
timeout: 5000,
method: 'GET'
}
@@ -42,7 +42,7 @@ const req = https.request(options, function (res) {
res.on('end', function () {
if (!zquiet) console.log(" * Upstream Ultra Willies HTTP Response:", res.statusCode, res.statusMessage);
if (request_headers.query.clear_cache) {
headers += "\nwtv-expire-all: wtv-flashrom";
headers += "\nwtv-expire-all: "+service_name;
}
sendToClient(socket, headers, data);
});

View File

@@ -31,11 +31,10 @@ if (socket.ssid !== null) {
}
}
if (!ssid_sessions[socket.ssid].getSessionData("registered") && (!request_headers.query.guest_login || !minisrv_config.config.allow_guests)) gourl = "wtv-register:/splash";
if (!ssid_sessions[socket.ssid].getSessionData("registered") && (!request_headers.query.guest_login || !minisrv_config.config.allow_guests)) gourl = "wtv-register:/splash?";
if (gourl) {
headers = `200 OK
Connection: Close
wtv-open-isp-disabled: false
`;
if (!ssid_sessions[socket.ssid].getSessionData("registered") && (!request_headers.query.guest_login || !minisrv_config.config.allow_guests)) {
@@ -44,7 +43,7 @@ wtv-ticket: ${wtvsec_login.ticket_b64}
${getServiceString('wtv-register')}
${getServiceString('wtv-head-waiter')}
${getServiceString('wtv-star')}
wtv-boot-url: wtv-register:/splash
wtv-boot-url: wtv-head-waiter:/relogin?
`
}
headers += `wtv-visit: ${gourl}
@@ -127,11 +126,11 @@ wtv-connection-timeout: 90
wtv-show-time-enabled: true
wtv-fader-timeout: 900
wtv-tourist-enabled: true`
headers += "\nwtv-relogin-url: wtv-1800:/preregister?relogin=true";
headers += "\nwtv-relogin-url: wtv-head-waiter:/relogin?relogin=true";
if (request_headers.query.guest_login) headers += "&guest_login=true";
headers += "\nwtv-reconnect-url: wtv-1800:/preregister?reconnect=true";
headers += "\nwtv-reconnect-url: wtv-head-waiter:/relogin?reconnect=true";
if (request_headers.query.guest_login) headers += "&guest_login=true";
headers += "\nwtv-boot-url: wtv-1800:/preregister?relogin=true";
headers += "\nwtv-boot-url: wtv-head-waiter:/relogin?relogin=true";
if (request_headers.query.guest_login) headers += "&guest_login=true";
headers += "\nwtv-allow-dsc: true";
headers += "\nwtv-home-url: wtv-home:/home?";

View File

@@ -53,8 +53,8 @@ wtv-expire-all: wtv-head-waiter:
wtv-log-url: wtv-log:/log`;
if (challenge_header != "") headers += "\n" + challenge_header;
headers += `
wtv-relogin-url: wtv-1800:/preregister?relogin=true
wtv-reconnect-url: wtv-1800:/preregister?reconnect=true
wtv-relogin-url: wtv-head-waiter:/relogin?relogin=true
wtv-reconnect-url: wwtv-head-waiter:/relogin?reconnect=true
wtv-visit: ${gourl}
Content-type: text/html`;
data = '';
@@ -66,7 +66,7 @@ Connection: Keep-Alive
Expires: Wed, 09 Oct 1991 22:00:00 GMT
wtv-expire-all: wtv-head-waiter:
wtv-expire-all: wtv-1800:
wtv-visit: wtv-1800:/preregister?relogin=true
wtv-visit: wtv-head-waiter:/relogin?relogin=true
Content-type: text/html`;
data = '';
}

View File

@@ -0,0 +1,20 @@
var gourl = "wtv-1800:/preregister?";
if (request_headers.query.relogin) gourl += "relogin=true";
else if (request_headers.query.reconnect) gourl += "reconnect=true";
if (request_headers.query.guest_login) {
if (request_headers.query.relogin || request_headers.query.reconnect) gourl += "&";
gourl += "guest_login=true";
if (request_headers.query.skip_splash) gourl += "&skip_splash=true";
}
headers = `200 OK
Connection: Keep-Alive
Expires: Wed, 09 Oct 1991 22:00:00 GMT
wtv-expire-all: wtv-head-waiter:
wtv-expire-all: wtv-1800:
wtv-service: reset
${getServiceString('wtv-1800')}
wtv-visit: ${gourl}
Content-type: text/html`;
data = '';

View File

@@ -12,6 +12,16 @@ if (ssid_sessions[socket.ssid].get('box-does-psuedo-encryption')) {
var cryptstatus = ((socket_sessions[socket.id].secure === true) ? "Encrypted" : "Not Encrypted")
}
var comp_type = shouldWeCompress(socket.ssid,'text/html');
var compstatus = "uncompressed";
switch (comp_type) {
case 1:
compstatus = "wtv-lzpf";
break;
case 2:
compstatus = "gzip (level 9)";
break;
}
data = `<html>
<head>
@@ -24,13 +34,16 @@ function go() {
location.href=document.access.url.value;
}
</script>
<b>Welcome to `+ z_title + `</b><br>
`;
if (minisrv_config.config.git_commit) data += "<small><i>" + "&nbsp; ".repeat(32) + "git revision " + minisrv_config.config.git_commit + "</i></small><br>";
<b>Welcome to ${z_title}`;
if (ssid_sessions[socket.ssid].getSessionData("registered")) data += ", " + ssid_sessions[socket.ssid].getSessionData("subscriber_username") + "!";
data += "</b><br>";
if (minisrv_config.config.git_commit) data += `<div width="540" align="right"><font size="-4"><i>git revision ${minisrv_config.config.git_commit}</i></small></font></div><br>`;
data += `
<b>Encryption Status</b>: ${cryptstatus}<br>
<hr>
<b>Status</b>: ${cryptstatus} (${compstatus})<br>
<b>Connection Speed</b>: &rate;
<p>
<hr>
<form name=access onsubmit="go()">
<ul>
<li><a href="client:relog">client:relog (direct)</a></li>
@@ -45,7 +58,7 @@ if (ssid_sessions[socket.ssid].hasCap("client-has-disk")) {
data += "<li><a href=\"client:diskhax\">DiskHax</a> ~ <a href=\"client:vfathax\">VFatHax</a></li>\n";
if (ssid_sessions[socket.ssid].hasCap("client-can-do-macromedia-flash2")) {
// only show demo if client can do flash2
data += "<li>Old MSNTV DealerDemo: <a href=\"wtv-update:/DealerDemo\">Download</a> ~ <a href=\"file://Disk/Demo/index.html\"> Access (after Download)</a></li>\n";
data += "<li>Old MSNTV DealerDemo: <a href=\"wtv-disk:/sync?group=DealerDemo&diskmap=DealerDemo\">Download</a> ~ <a href=\"file://Disk/Demo/index.html\"> Access (after Download)</a></li>\n";
}
}

View File

@@ -249,17 +249,17 @@ WARRANTY OF ANY KIND. THE INFORMATION, SOFTWARE, PRODUCTS, AND SERVICES INCLUDED
THROUGH THE ${minisrv_config.config.service_name.toUpperCase()} SERVICE MAY INCLUDE INACCURACIES OR TYPOGRAPHICAL ERRORS.
ADVICE RECEIVED VIA THE ${minisrv_config.config.service_name.toUpperCase()} SERVICE SHOULD NOT BE RELIED UPON FOR PERSONAL,
MEDICAL, LEGAL OR FINANCIAL DECISIONS AND YOU SHOULD CONSULT AN APPROPRIATE
PROFESSIONAL FOR SPECIFIC ADVICE TAILORED TO YOUR SITUATION. MICROSOFT,
PROFESSIONAL FOR SPECIFIC ADVICE TAILORED TO YOUR SITUATION. ${WTVRegister.getServiceOperator().toUpperCase()},
ITS RESELLERS, DISTRIBUTORS AND/OR SUPPLIERS DO NOT WARRANT THAT ACCESS TO
OR USE OF THE ${minisrv_config.config.service_name.toUpperCase()} SERVICE WILL BE UNINTERRUPTED OR ERROR-FREE, THAT MEMBERS
WILL BE ABLE TO ACCESS THE ${minisrv_config.config.service_name.toUpperCase()} SERVICE AT ANY TIME OR IN ANY GEOGRAPHIC AREA,
OR THAT THE ${minisrv_config.config.service_name.toUpperCase()} SERVICE OR ${WTVRegister.getServiceOperator().toUpperCase()} SOFTWARE OR SERVICES WILL MEET ANY
PARTICULAR CRITERIA OF PERFORMANCE OR QUALITY. MICROSOFT, ITS RESELLERS,
PARTICULAR CRITERIA OF PERFORMANCE OR QUALITY. ${WTVRegister.getServiceOperator().toUpperCase()}, ITS RESELLERS,
DISTRIBUTORS AND/OR SUPPLIERS HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS
WITH REGARD TO THE ${minisrv_config.config.service_name.toUpperCase()} SERVICE AND ALL RELATED SOFTWARE, INFORMATION,
PRODUCTS, SERVICES AND GRAPHICS, INCLUDING ALL IMPLIED WARRANTIES AND
CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, WORKMANLIKE
EFFORT, TITLE, AND NON-INFRINGEMENT. IN NO EVENT SHALL MICROSOFT, ITS
EFFORT, TITLE, AND NON-INFRINGEMENT. IN NO EVENT SHALL ${WTVRegister.getServiceOperator().toUpperCase()}, ITS
RESELLERS, DISTRIBUTORS AND/OR SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT,
PUNITIVE, INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
WHATSOEVER, INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF USE, DATA, OR
@@ -281,7 +281,7 @@ DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR
INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU. IF YOU ARE
DISSATISFIED WITH ANY PORTION OF THE ${minisrv_config.config.service_name.toUpperCase()} SERVICE, OR WITH ANY OF THESE
TERMS OF USE, YOUR SOLE AND EXCLUSIVE REMEDY IS TO NOT REGISTER FOR A ${minisrv_config.config.service_name.toUpperCase()}
SERVICE ACCOUNT OR TO TERMINATE YOUR ${minisrv_config.config.service_name.toUpperCase()} SERVICE ACCOUNT. MICROSOFT MAY,
SERVICE ACCOUNT OR TO TERMINATE YOUR ${minisrv_config.config.service_name.toUpperCase()} SERVICE ACCOUNT. ${WTVRegister.getServiceOperator().toUpperCase()} MAY,
IN ITS SOLE DISCRETION AND WITHOUT PRIOR NOTICE (I) RESTRICT OR LIMIT ACCESS
TO THE ${minisrv_config.config.service_name.toUpperCase()} SERVICE; (II) TERMINATE A USER ACCOUNT OR USER SESSIONS AT ANY
TIME; OR (III) DISCONTINUE OR MODIFY ANY OR ALL ASPECTS OF THE ${minisrv_config.config.service_name.toUpperCase()} SERVICE OR ITS SERVICES.

View File

@@ -40,7 +40,7 @@ Welcome
ENCTYPE="x-www-form-encoded" METHOD="POST">
<input type=hidden name=registering value="true">
<td height=230 width= 300 bgcolor="#171726" colspan=5 valign=top align=left>
Welcome to the ${minisrv_config.config.service_name} Mini Service, operated by ${minisrv_config.config.service_owner}
Welcome to the ${minisrv_config.config.service_name} Mini Service, operated by ${minisrv_config.config.service_owner}.
The next screens will lead you through a quick setup process for using this service.<p> Press the "Continue" button below to begin setup.<p>
<td abswidth=20 bgcolor=#171726 >
</tr>

View File

@@ -20,13 +20,13 @@ data = `<html>
<tr>
<td><a href="wtv-tricks:/info">Info</a>
<td width = 25>
<td><a href="wtv-cookie:/list">List Cookies</a>
<td><a href="wtv-cookie:list">List Cookies</a>
<tr>
<td colspan=3 height=6>
<tr>
<td><a href="wtv-flashrom:/willie">Visit Ultra Willie's!</a>
<td width = 25>
<td><a href="wtv-cookie:/reset">Clear Cookies</a>
<td><a href="wtv-cookie:reset">Clear Cookies</a>
<tr>
<td colspan=3 height=6>
<tr>

View File

@@ -1,51 +0,0 @@
headers = `200 OK
Content-Type: text/html`
data = `<html>
<head>
<meta
http-equiv=refresh
content="0;url=client:Fetch?group=DealerDemo&source=wtv-update:/sync%3Fdiskmap%3DDealerDemo&message=Retrieving%20Files..."
>
<display downloadsuccess="client:goback" downloadfail="client:ShowAlert?message=Download%20failed...&buttonlabel1=Okay...&buttonaction1=client:goback&noback=true">
<title>Retrieving Files</title>
</head>
<body bgcolor=#0 text=#42CC55 fontsize=large hspace=0 vspace=0>
<table cellspacing=0 cellpadding=0>
<tr>
<td width=104 height=74 valign=middle align=center bgcolor=3B3A4D>
<img src="`+minisrv_config.config.service_logo+`" width=86 height=64>
<td width=20 valign=top align=left bgcolor=3B3A4D>
<spacer>
<td colspan=2 width=436 valign=middle align=left bgcolor=3B3A4D>
<font color=D6DFD0 size=+2><blackface><shadow>
<spacer type=block width=1 height=4>
<br>
Retrieving Files
</shadow>
</blackface>
</font>
<tr>
<td width=104 height=20>
<td width=20>
<td width=416>
<td width=20>
<tr>
<td colspan=2>
<td>
<font size=+1>
Your Internet terminal is retrieving some files.
<p>This may take a while.
</font>
<tr>
<td colspan=2>
<td>
<br><br>
<font color=white>
<progressindicator name="downloadprogress"
message="Retrieving Files..."
height=40 width=250>
</font>
</table>
</body>
</html>`

View File

@@ -1,187 +0,0 @@
// todo: async
var path = require("path");
var content_dir = "content/"
var diskmap_dir = content_dir + "diskmaps/";
function generateDownloadList(diskmap_group_data, update_list, diskmap_data) {
// create WebTV Download List
var newest_file_epoch = 0;
var download_list = '';
if (diskmap_data.partition_size) {
download_list += "CREATE " + diskmap_data.base + "\n";
download_list += "partition-size: " + diskmap_data.partition_size + "\n\n";
}
download_list += "CREATE-GROUP " + diskmap_group_data + "-UPDATE\n";
download_list += "state: invalid\n";
download_list += "base: " + diskmap_data.base + ".GROUP-UPDATE/\n\n";
Object.keys(update_list).forEach(function (k) {
if (parseInt(update_list[k]["Last-modified"]) > newest_file_epoch) newest_file_epoch = parseInt(update_list[k]["Last-modified"]);
download_list += "DISPLAY " + update_list[k].display + "\n\n";
download_list += "GET " + update_list[k].file.replace(diskmap_data.base, "") + "\n";
download_list += "group: " + diskmap_group_data + "-UPDATE\n";
download_list += "location: " + service_name + ":/" + update_list[k].location + "\n";
download_list += "file-permission: r\n"
download_list += "wtv-checksum: " + update_list[k]["wtv-checksum"] + "\n";
download_list += "service-source-location: /webtv/content/" + service_name.replace("wtv-","") + "d/" + update_list[k].location + "\n";
download_list += "client-dest-location: " + update_list[k].file + "\n\n";
});
download_list += "CREATE-GROUP " + diskmap_group_data + "\n";
download_list += "state: invalid\n";
download_list += "service-owned: " + (diskmap_data.service_owned || false) + "\n";
download_list += "base: " + diskmap_data.base + "\n\n";
Object.keys(update_list).forEach(function (k) {
download_list += "DELETE " + update_list[k].file.replace(diskmap_data.base, "") + "\n";
download_list += "group: " + diskmap_group_data + "\n\n";
});
Object.keys(update_list).forEach(function (k) {
download_list += "RENAME " + update_list[k].file.replace(diskmap_data.base, "") + "\n";
download_list += "group: " + diskmap_group_data + "-UPDATE\n";
download_list += "destination-group: " + diskmap_group_data + "\n";
download_list += "location: " + update_list[k].file.replace(diskmap_data.base, "") + "\n\n";
});
download_list += "DELETE-GROUP " + diskmap_group_data + "-UPDATE\n\n";
download_list += "SET-GROUP " + diskmap_group_data + "\n";
download_list += "state: ok\n";
download_list += "version: " + newest_file_epoch + "\n";
download_list += "last-checkup-time: " + new Date().toUTCString().replace("GMT", "+0000") + "\n\n";
return download_list;
}
function processGroup(diskmap_primary_group, diskmap_group_data, diskmap_subgroup = null) {
// parse webtv post
var output_data = '';
var post_data = request_headers.post_data.toString(CryptoJS.enc.Latin1).split("\n");
var post_data_current_directory = '';
var post_data_current_file = '';
var post_data_fileinfo = new Array();
var post_data_filecount = -1;
Object.keys(post_data).forEach(function (k) {
if (post_data[k] == "") return;
if (post_data[k].substring(0, 7) == "file://") {
post_data_current_directory = post_data[k];
post_data_current_file = post_data[k];
}
if (post_data[k].indexOf(":") > 0) {
var post_data_line = post_data[k].split(": ")
var post_data_line_name = post_data_line[0];
post_data_line.shift();
var post_data_line_data = post_data_line.join(": ");
if (!post_data_fileinfo[post_data_filecount]) post_data_fileinfo[post_data_filecount] = new Array();
if (post_data_line_name == "Last-modified") {
post_data_fileinfo[post_data_filecount][post_data_line_name] = (new Date(new Date(Date.parse(post_data_line_data)).toUTCString()) / 1000);
} else if (post_data_line_name == "Content-length") {
post_data_fileinfo[post_data_filecount][post_data_line_name] = parseInt(post_data_line_data);
}
else {
post_data_fileinfo[post_data_filecount][post_data_line_name] = post_data_line_data;
}
} else {
post_data_filecount++;
post_data_current_file = post_data_current_directory + post_data[k];
post_data_fileinfo[post_data_filecount] = new Array();
post_data_fileinfo[post_data_filecount].file = post_data_current_file
}
});
var wtv_download_list = new Array();
Object.keys(diskmap_group_data.files).forEach(function (k) {
if (!diskmap_group_data.files[k].location) diskmap_group_data.files[k].location = diskmap_group_data.location + diskmap_group_data.files[k].file.replace(diskmap_group_data.base, "");
var post_match_file = null;
Object.keys(service_vaults).forEach(function (g) {
if (post_match_file != null) return;
post_match_file = service_vaults[g] + "/" + service_name + "/" + diskmap_group_data.files[k].location;
if (!fs.existsSync(post_match_file)) post_match_file = null;
});
var file_in_postdata = function (post_file) {
return post_file.file === diskmap_group_data.files[k].file
}
var post_match_file_lstat = fs.lstatSync(post_match_file);
var post_match_result = post_data_fileinfo.find(file_in_postdata) || null;
var post_match_file_data = new Buffer.from(fs.readFileSync(post_match_file, {
encoding: null,
flags: 'r'
}));
diskmap_group_data.files[k]["Last-modified"] = (new Date(new Date(post_match_file_lstat.mtime).toUTCString()) / 1000);
diskmap_group_data.files[k]["Content-length"] = post_match_file_lstat.size;
diskmap_group_data.files[k]["wtv-checksum"] = CryptoJS.MD5(CryptoJS.lib.WordArray.create(post_match_file_data)).toString(CryptoJS.enc.Hex).toLowerCase();
if (!diskmap_group_data.files[k].display) diskmap_group_data.files[k].display = diskmap_group_data.display;
if (post_match_result) {
// md5s match, so client doesn't need file
if (diskmap_group_data.files[k]['wtv-checksum'].toLowerCase() == post_match_result["wtv-checksum"]) return;
// last modified is equal to or newer than the last update, and file size match, so assume same file and client does not need it
else if ((post_match_result["Last-modified"] >= diskmap_group_data.files[k]["Last-modified"]) && (post_match_result["Content-length"] == diskmap_group_data.files[k]["Content-length"])) return;
// otherwise send to client
else wtv_download_list.push(diskmap_group_data.files[k]);
} else {
wtv_download_list.push(diskmap_group_data.files[k]);
}
var diskmap_group_name = (diskmap_subgroup == null) ? diskmap_primary_group : diskmap_primary_group + "-" + diskmap_subgroup;
output_data = generateDownloadList(diskmap_group_name, wtv_download_list, diskmap_group_data)
});
return output_data;
}
if (request_headers.query.diskmap && request_headers.query.group && request_headers.post_data) {
var diskmap_json_file = null;
Object.keys(service_vaults).forEach(function (g) {
if (diskmap_json_file != null) return;
diskmap_json_file = service_vaults[g] + "/" + service_name + "/" + diskmap_dir + request_headers.query.diskmap + ".json";
if (!fs.existsSync(diskmap_json_file)) diskmap_json_file = null;
});
if (diskmap_json_file != null) {
if (fs.lstatSync(diskmap_json_file)) {
try {
// read diskmap
var diskmap_data = JSON.parse(fs.readFileSync(diskmap_json_file).toString());
if (!diskmap_data[request_headers.query.group]) {
throw ("Invalid diskmap data (group does not match)");
}
data = '';
diskmap_data = diskmap_data[request_headers.query.group];
if (!diskmap_data.display) {
Object.keys(diskmap_data).forEach(function (k) {
if (diskmap_data[k]) data += processGroup(request_headers.query.group,diskmap_data[k],k);
});
} else {
data = processGroup(request_headers.query.group, diskmap_data);
}
headers = "200 OK\nContent-Type: wtv/download-list";
} catch (e) {
var errpage = doErrorPage(400);
headers = errpage[0];
data = errpage[1];
console.log("wtv-update:/sync error", e);
}
}
} else {
var errpage = doErrorPage(404,"The requested DiskMap does not exist.");
headers = errpage[0];
data = errpage[1];
if (zdebug) console.log(" # wtv-update:/sync error", "could not find diskmap");
}
} else {
var errpage = doErrorPage(400);
headers = errpage[0];
data = errpage[1];
if (zdebug) console.log(" # wtv-update:/sync error", "missing query arguments");
}

View File

@@ -1,4 +0,0 @@
200 OK
Content-Type: text/url
client:ShowAlert?message=HackTV%20Update%20was%20successful%21&buttonlabel2=Go%20to%20HackTV&action2=file%3A%2F%2FDisk%2FBrowser%2FGames%2FGames.html&buttonlabel1=Okay&buttonaction1=client:goback&image=file://disk/browser/Games/hacktv2.gif&noback=true

View File

@@ -1,3 +1,5 @@
const { lib } = require('crypto-js');
class WTVClientSessionData {
fs = require('fs');
@@ -38,7 +40,8 @@ class WTVClientSessionData {
}
}
constructor(hide_ssid_in_logs, session_storage_directory) {
constructor(ssid, hide_ssid_in_logs, session_storage_directory) {
this.ssid = ssid;
if (hide_ssid_in_logs) this.hide_ssid_in_logs = hide_ssid_in_logs;
if (!session_storage_directory) session_storage_directory = __dirname + "/SessionStore";
this.session_storage = session_storage_directory;
@@ -91,9 +94,6 @@ class WTVClientSessionData {
this.session_store.cookies[cookie_index] = Object.assign({}, cookie_data);
// do not write file if user is not registered
if (getSessionData('registered')) this.storeSessionData();
return true;
}
@@ -172,11 +172,13 @@ class WTVClientSessionData {
return outstring;
}
loadSessionData() {
loadSessionData(raw_data = false) {
try {
if (this.fs.lstatSync(this.session_storage + this.path.sep + this.ssid + ".json")) {
var session_data_file = this.fs.readFileSync(this.session_storage + this.path.sep + this.ssid + ".json", 'Utf8');
var session_data = JSON.parse(session_data_file);
var json_data = this.fs.readFileSync(this.session_storage + this.path.sep + this.ssid + ".json", 'Utf8')
if (raw_data) return json_data;
var session_data = JSON.parse(json_data);
this.session_store = session_data;
return true;
}
@@ -188,14 +190,22 @@ class WTVClientSessionData {
}
saveSessionData() {
if (!this.session_store.registered) {
if (this.isRegistered()) {
// load data from disk and merge new data
var temp_store = this.session_store;
this.loadSessionData();
this.session_store = Object.assign(this.session_store, temp_store);
if (this.loadSessionData()) this.session_store = Object.assign(this.session_store, temp_store);
else this.session_store = temp_store;
temp_store = null;
} else {
// do not write file if user is not registered, return true because this is not an error
return true;
}
try {
var store_data = JSON.stringify(this.session_store);
this.fs.writeFileSync(this.session_storage + this.path.sep + this.ssid + ".json", store_data, "Utf8");
// only save if file has changed
var json_save_data = JSON.stringify(this.session_store);
var json_load_data = this.loadSessionData(true);
if (json_save_data != json_load_data) this.fs.writeFileSync(this.session_storage + this.path.sep + this.ssid + ".json", JSON.stringify(this.session_store), "Utf8");
return true;
} catch (e) {
console.error(" # Error saving session data for", this.filterSSID(this.ssid), e);
@@ -205,7 +215,7 @@ class WTVClientSessionData {
retrieveSessionData() {
// alias
this.loadSessionData();
return this.loadSessionData();
}
storeSessionData() {
@@ -213,16 +223,32 @@ class WTVClientSessionData {
return this.saveSessionData();
}
SaveIfRegistered() {
if (this.isRegistered()) return this.saveSessionData();
return false;
}
isRegistered() {
var self = this;
var ssid_match = false;
this.fs.readdirSync(this.session_storage).forEach(file => {
if (!file.match(/.*\.json/ig)) return;
if (ssid_match) return;
if (file.split('.')[0] == self.ssid) ssid_match = true;
});
return ssid_match;
}
unregisterBox() {
try {
if (this.fs.lstatSync(this.session_storage + this.path.sep + this.ssid + ".json")) {
return this.fs.unlinkSync(this.session_storage + this.path.sep + this.ssid + ".json");
this.fs.unlinkSync(this.session_storage + this.path.sep + this.ssid + ".json");
this.session_store = {};
return true;
}
} catch (e) {
// Don't log error 'file not found', it just means the client isn't registered yet
if (e.code != "ENOENT") console.error(" # Error deleting session data for", this.filterSSID(this.ssid), e);
console.error(" # Error deleting session data for", this.filterSSID(this.ssid), e);
return false;
}
}

View File

@@ -43,7 +43,7 @@ class WTVFlashrom {
}
async doLocalFlashROM(flashrom_file_path, callback, info_only = false) {
async doLocalFlashROM(flashrom_file_path, request_path, callback, info_only = false) {
// use local flashrom files;
console.log(info_only);
var self = this;
@@ -56,7 +56,7 @@ class WTVFlashrom {
callback(data, headers);
} else {
if (info_only) {
callback(self.getFlashromData(data, flashrom_file_path));
callback(self.getFlashromInfo(data, request_path));
} else {
self.sendToClient(data, flashrom_file_path, callback);
}
@@ -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);
@@ -202,7 +202,7 @@ class WTVFlashrom {
});
req.end();
} else {
this.doLocalFlashROM(flashrom_file_path, callback, ((length != 0) ? true : false));
this.doLocalFlashROM(flashrom_file_path, request_path, callback, ((length != 0) ? true : false));
}
}
}

View File

@@ -1,5 +1,3 @@
var EventEmitter = require('events').EventEmitter;
/**
* Pure-JS implementation of WebTV's LZPF compression
*
@@ -25,6 +23,7 @@ class WTVLzpf {
encoded_data = [];
nomatchEncode = [
[0x0000, 0x10], [0x0001, 0x10], [0x0002, 0x10],
[0x0003, 0x10], [0x0004, 0x10], [0x009A, 0x0F],
[0x0005, 0x10], [0x009C, 0x0F], [0x009E, 0x0F],
@@ -413,8 +412,8 @@ class WTVLzpf {
* @returns {Buffer} Lzpf compression data
*/
Finish() {
var code_length = -1
var code = -1
var code_length = -1;
var code = -1;
if (this.type_index == 2) {
this.EncodeLiteral(0x10, 0x00990000);
@@ -482,4 +481,4 @@ class WTVLzpf {
}
}
module.exports = WTVLzpf;
module.exports = WTVLzpf;

View File

@@ -2,6 +2,7 @@
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const http = require('http');
const https = require('https');
const strftime = require('strftime'); // used externally by service scripts
@@ -73,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);
@@ -100,68 +108,94 @@ function doErrorPage(code, data = null) {
function getConType(path) {
var file_ext = getFileExt(path).toLowerCase();
var wtv_mime_type = "";
var modern_mime_type = "";
// process WebTV overrides, fall back to generic mime lookup
switch (file_ext) {
case "aif":
return "audio/x-aif";
wtv_mime_type = "audio/x-aif";
break;
case "aifc":
return "audio/x-aifc";
wtv_mime_type = "audio/x-aifc";
break;
case "aiff":
return "audio/x-aiff";
wtv_mime_type = "audio/x-aiff";
break;
case "ani":
return "x-wtv-animation";
wtv_mime_type = "x-wtv-animation";
break;
case "brom":
return "binary/x-wtv-bootrom";
wtv_mime_type = "binary/x-wtv-bootrom";
break;
case "cdf":
return "application/netcdf";
wtv_mime_type = "application/netcdf";
break;
case "dat":
return "binary/cache-data";
wtv_mime_type = "binary/cache-data";
break;
case "dl":
return "wtv/download-list";
wtv_mime_type = "wtv/download-list";
break;
case "gsm":
return "audio/x-gsm";
wtv_mime_type = "audio/x-gsm";
break;
case "gz":
return "application/gzip";
wtv_mime_type = "application/gzip";
break;
case "ini":
return "wtv/jack-configuration";
wtv_mime_type = "wtv/jack-configuration";
break;
case "mips-code":
return "code/x-wtv-code-mips";
wtv_mime_type = "code/x-wtv-code-mips";
break;
case "o":
return "binary/x-wtv-approm";
wtv_mime_type = "binary/x-wtv-approm";
break;
case "ram":
return "audio/x-pn-realaudio";
wtv_mime_type = "audio/x-pn-realaudio";
break;
case "rom":
return "binary/x-wtv-flashblock";
wtv_mime_type = "binary/x-wtv-flashblock";
break;
case "rsp":
return "wtv/jack-response";
wtv_mime_type = "wtv/jack-response";
break;
case "swa":
case "swf":
return "application/x-shockwave-flash";
wtv_mime_type = "application/x-shockwave-flash";
break;
case "srf":
case "spl":
return "wtv/jack-data";
wtv_mime_type = "wtv/jack-data";
break;
case "ttf":
return "wtv/jack-fonts";
wtv_mime_type = "wtv/jack-fonts";
break;
case "tvch":
return "wtv/tv-channels";
wtv_mime_type = "wtv/tv-channels";
break;
case "tvl":
return "wtv/tv-listings";
wtv_mime_type = "wtv/tv-listings";
break;
case "tvsl":
return "wtv/tv-smartlinks";
wtv_mime_type = "wtv/tv-smartlinks";
break;
case "wad":
return "binary/doom-data";
wtv_mime_type = "binary/doom-data";
break;
case "mp2":
case "hsb":
case "rmf":
case "s3m":
case "mod":
case "xm":
return "application/Music";
wtv_mime_type = "application/Music";
break;
}
// if we reach here, its not a WebTV specific override
// or we are not yet aware of said override
return mime.lookup(path);
modern_mime_type = mime.lookup(path);
if (wtv_mime_type == "") wtv_mime_type = modern_mime_type;
return new Array(wtv_mime_type, modern_mime_type);
}
async function processPath(socket, service_vault_file_path, request_headers = new Array(), service_name) {
@@ -172,17 +206,35 @@ 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
service_vault_found = true;
request_is_async = true;
if (!zquiet) console.log(" * Found " + service_vault_file_path + " to handle request (Direct File Mode) [Socket " + socket.id + "]");
var contype = getConType(service_vault_file_path);
var contypes = getConType(service_vault_file_path);
headers = "200 OK\n"
headers += "Content-Type: " + contype;
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);
});
@@ -238,6 +290,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!
});
@@ -250,12 +327,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:")
@@ -279,8 +356,8 @@ function filterSSID(obj) {
return obj.substr(0, 6) + ('*').repeat(9);
}
} else {
if (obj["wtv-client-serial-number"]) {
var ssid = obj["wtv-client-serial-number"];
if (makeSafeSSID(obj["wtv-client-serial-number"])) {
var ssid = makeSafeSSID(obj["wtv-client-serial-number"]);
if (ssid.substr(0, 8) == "MSTVSIMU") {
obj["wtv-client-serial-number"] = ssid.substr(0, 10) + ('*').repeat(10) + ssid.substr(20);
} else if (ssid.substr(0, 5) == "1SEGA") {
@@ -296,6 +373,12 @@ function filterSSID(obj) {
}
}
function makeSafeSSID(ssid = "") {
ssid = ssid.replace(/[^a-zA-Z0-9]/g, "");
if (ssid.length == 0) ssid = null;
return ssid;
}
function makeSafePath(base, target) {
target.replace(/[\|\&\;\$\%\@\"\<\>\+\,\\]/g, "");
if (path.sep != "/") target = target.replace(/\//g, path.sep);
@@ -304,9 +387,6 @@ function makeSafePath(base, target) {
}
async function processURL(socket, request_headers) {
if (request_headers === null) {
return;
}
var shortURL, headers, data = "";
request_headers.query = new Array();
if (request_headers.request_url) {
@@ -353,11 +433,46 @@ async function processURL(socket, request_headers) {
}
}
if ((shortURL.indexOf("http") != 0 && shortURL.indexOf("ftp") != 0 && shortURL.indexOf(":") > 0 && shortURL.indexOf(":/") == -1)) {
// Apparently it is within WTVP spec to accept urls without a slash (eg wtv-home:home)
// Here, we just reassemble the request URL as if it was a proper URL (eg wtv-home:/home)
// we will allow this on any service except http(s) and ftp
var shortURL_split = shortURL.split(':');
var shortURL_service_name = shortURL_split[0];
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) {
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;
}
}
}
if (shortURL.indexOf(':/') >= 0 && shortURL.indexOf('://') < 0) {
var ssid = socket.ssid;
if (ssid == null) {
ssid = request_headers["wtv-client-serial-number"];
// prevent possible injection attacks via SSID and filesystem SessionStore
ssid = makeSafeSSID(request_headers["wtv-client-serial-number"]);
if (ssid == "") ssid = null;
}
var reqverb = "Request";
if (request_headers.encrypted || request_headers.secure) {
reqverb = "Encrypted " + reqverb;
@@ -475,6 +590,7 @@ async function doHTTPProxy(socket, request_headers) {
]);
if (data_hex.substring(0, 8) == "0d0a0d0a") data_hex = data_hex.substring(8);
if (data_hex.substring(0, 4) == "0a0a") data_hex = data_hex.substring(4);
headers["wtv-http-proxy"] = true;
sendToClient(socket, headers, Buffer.from(data_hex,'hex'));
});
}).on('error', function (err) {
@@ -554,8 +670,69 @@ function headerStringToObj(headers, response = false) {
return headers_obj;
}
async function sendToClient(socket, headers_obj, data) {
function shouldWeCompress(ssid, headers_obj) {
var compress_data = false;
var compression_type = 0; // no compression
if (ssid_sessions[ssid]) {
if (ssid_sessions[ssid].capabilities) {
if (ssid_sessions[ssid].capabilities['client-can-receive-compressed-data']) {
if (minisrv_config.config.enable_lzpf_compression || minisrv_config.config.force_compression_type) {
compression_type = 1; // lzpf
}
if (ssid_sessions[ssid]) {
// if gzip is enabled...
if (minisrv_config.config.enable_gzip_compression || minisrv_config.config.force_compression_type) {
var is_bf0app = ssid_sessions[ssid].get("wtv-client-rom-type") == "bf0app";
var is_minibrowser = (ssid_sessions[ssid].get("wtv-needs-upgrade") || ssid_sessions[ssid].get("wtv-used-8675309"));
var is_softmodem = ssid_sessions[ssid].get("wtv-client-rom-type").match(/softmodem/);
if (!is_bf0app && ((!is_softmodem && !is_minibrowser) || (is_softmodem && !is_minibrowser))) {
// softmodem boxes do not appear to support gzip in the minibrowser
// LC2 appears to support gzip even in the MiniBrowser
// LC2 and newer approms appear to support gzip
// bf0app does not appear to support gzip
compression_type = 2; // gzip
}
}
}
// mostly for debugging
if (minisrv_config.config.force_compression_type == "lzpf") compression_type = 1;
if (minisrv_config.config.force_compression_type == "gzip") compression_type = 2;
// do not compress if already encoded
if (headers_obj["Content-Encoding"]) return 0;
// should we bother to compress?
var content_type = "";
if (typeof (headers_obj) == 'string') content_type = headers_obj;
else content_type = (typeof (headers_obj["wtv-modern-content-type"]) != 'undefined') ? headers_obj["wtv-modern-content-type"] : headers_obj["Content-Type"];
if (content_type) {
// both lzpf and gzip
if (content_type.match(/^text\//) && content_type != "text/tellyscript") compress_data = true;
else if (content_type.match(/^application\/(x-?)javascript$/)) compress_data = true;
else if (content_type == "application/json") compress_data = true;
if (compression_type == 2) {
// 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]$/)) compress_data = true; // midi & wav
if (content_type.match(/^binary\/x-wtv-approm$/)) compress_data = true; // midi & wav
}
}
}
}
}
// return compression_type if compress_data = true
return (compress_data) ? compression_type : 0;
}
async function sendToClient(socket, headers_obj, data) {
var headers = "";
var content_length = 0;
if (typeof (data) === 'undefined') data = '';
@@ -576,11 +753,11 @@ async function sendToClient(socket, headers_obj, data) {
headers_obj = moveObjectElement('Connection', 'http_response', headers_obj);
}
var clen = 0;
var content_length = 0;
if (typeof data.length !== 'undefined') {
clen = data.length;
content_length = data.length;
} else if (typeof data.byteLength !== 'undefined') {
clen = data.byteLength;
content_length = data.byteLength;
}
// fix captialization
@@ -589,31 +766,50 @@ async function sendToClient(socket, headers_obj, data) {
delete headers_obj["Content-type"];
}
// if box can do compression, see if its worth enabling
if (ssid_sessions[socket.ssid].capabilities) {
if (ssid_sessions[socket.ssid].capabilities['client-can-receive-compressed-data'] && minisrv_config.config.enable_lzpf_compression) {
compress_data = shouldWeCompress(headers_obj["Content-Type"]);
}
}
// small files actually get larger, so don't compress them
var compression_type = 0;
if (content_length >= 256) compression_type = shouldWeCompress(socket.ssid, headers_obj);
// compress if needed
if (compress_data && clen > 0) {
content_length = clen;
if (compression_type > 0 && content_length > 0 && headers_obj['http_response'].substring(0,3) == "200") {
var uncompressed_content_length = content_length;
switch (compression_type) {
case 1:
// wtv-lzpf implementation
headers_obj["wtv-lzpf"] = 0;
var wtvcomp = new WTVLzpf();
data = wtvcomp.Compress(data);
wtvcomp = null; // Makes the garbage gods happy so it cleans up our mess
break;
headers_obj["wtv-lzpf"] = 0;
case 2:
// zlib gzip implementation
headers_obj['Content-Encoding'] = 'gzip';
data = zlib.gzipSync(data, {
'level': 9
});
break;
}
var wtvcomp = new WTVLzpf();
data = wtvcomp.Compress(data);
wtvcomp = null; // Makes the garbage gods happy so it cleans up our mess
var compressed_content_length = 0;
if (content_length == 0 || compression_type != 1) {
// ultimately send compressed content length
compressed_content_length = data.byteLength;
content_length = compressed_content_length;
} else {
// ultimately send original content length if lzpf
compressed_content_length = data.byteLength;
}
var compression_percentage = ((compressed_content_length / uncompressed_content_length) * 100).toFixed(1).toString() + "%";
if (uncompressed_content_length != compressed_content_length) if (zdebug) console.log(" # Compression stats: Orig Size:", uncompressed_content_length, "~ Comp Size:", compressed_content_length, "~ Ratio:", compression_percentage);
}
// encrypt if needed
if (socket_sessions[socket.id].secure == true) {
headers_obj["wtv-encrypted"] = 'true';
headers_obj = moveObjectElement('wtv-encrypted', 'Connection', headers_obj);
if (clen > 0 && socket_sessions[socket.id].wtvsec) {
if (content_length > 0 && socket_sessions[socket.id].wtvsec) {
if (!zquiet) console.log(" * Encrypting response to client ...")
var enc_data = socket_sessions[socket.id].wtvsec.Encrypt(1, data);
data = enc_data;
@@ -625,14 +821,6 @@ async function sendToClient(socket, headers_obj, data) {
if (headers_obj["Content-Length"]) delete headers_obj["Content-Length"];
if (headers_obj["Content-length"]) delete headers_obj["Content-length"];
if (content_length == 0) {
if (typeof data.length !== 'undefined') {
content_length = data.length;
} else if (typeof data.byteLength !== 'undefined') {
content_length = data.byteLength;
}
}
headers_obj["Content-length"] = content_length;
if (ssid_sessions[socket.ssid]) {
@@ -648,10 +836,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)));
@@ -684,7 +872,7 @@ async function sendToClient(socket, headers_obj, data) {
} else {
socket.write(new Uint8Array(concatArrayBuffer(Buffer.from(headers + end_of_line), data)));
}
if (zquiet) console.log(" * Sent" + verbosity_mod + " " + headers_obj.http_response + " to client (Content-Type:", headers_obj['Content-Type'], "~", headers_obj['Content-Length'], "bytes)");
if (zquiet) console.log(" * Sent" + verbosity_mod + " " + headers_obj.http_response + " to client (Content-Type:", headers_obj['Content-Type'], "~", headers_obj['Content-length'], "bytes)");
}
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
@@ -698,25 +886,12 @@ async function sendToClient(socket, headers_obj, data) {
if (socket_sessions[socket.id].close_me) socket.end();
if (headers_obj["Connection"]) {
if (headers_obj["Connection"].toLowerCase() == "close" || wtv_connection_close == "true") {
if (headers_obj["Connection"].toLowerCase() == "close" && wtv_connection_close == "true") {
socket.destroy();
}
}
}
function shouldWeCompress(content_type) {
if (typeof (content_type) != 'undefined') {
if ((content_type.match(/^text\//) && content_type != "text/tellyscript") ||
content_type.match(/^application\/(x-?)javascript$/) ||
content_type.match(/^audio\/(x-)?midi/) ||
content_type.match(/^audio\/(x-)?wav/) ||
content_type == "application/json") {
return true;
}
}
return false;
}
function concatArrayBuffer(buffer1, buffer2) {
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
@@ -757,7 +932,7 @@ function isUnencryptedString(string, verbose = false) {
}
function filterSSID(ssid) {
var WTVCSD = new WTVClientSessionData(minisrv_config.config.hide_ssid_in_logs);
var WTVCSD = new WTVClientSessionData(null,minisrv_config.config.hide_ssid_in_logs);
return WTVCSD.filterSSID(ssid);
}
@@ -827,14 +1002,17 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
if (!headers) return;
if (headers["wtv-client-serial-number"] != null) {
socket.ssid = headers["wtv-client-serial-number"];
if (!ssid_sessions[socket.ssid]) {
ssid_sessions[socket.ssid] = new WTVClientSessionData(minisrv_config.config.hide_ssid_in_logs);
if (headers["wtv-client-serial-number"] != null && socket.ssid == null) {
socket.ssid = makeSafeSSID(headers["wtv-client-serial-number"]);
if (socket.ssid != null) {
if (!ssid_sessions[socket.ssid]) {
ssid_sessions[socket.ssid] = new WTVClientSessionData(socket.ssid,minisrv_config.config.hide_ssid_in_logs);
ssid_sessions[socket.ssid].SaveIfRegistered();
}
if (!ssid_sessions[socket.ssid].data_store.sockets) ssid_sessions[socket.ssid].data_store.sockets = new Set();
ssid_sessions[socket.ssid].ssid = socket.ssid;
ssid_sessions[socket.ssid].data_store.sockets.add(socket);
}
if (!ssid_sessions[socket.ssid].data_store.sockets) ssid_sessions[socket.ssid].data_store.sockets = new Set();
ssid_sessions[socket.ssid].ssid = socket.ssid;
ssid_sessions[socket.ssid].data_store.sockets.add(socket);
}
var ip2long = function (ip) {
@@ -920,7 +1098,8 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
if (headers["wtv-capability-flags"] != null) {
if (!ssid_sessions[socket.ssid]) {
ssid_sessions[socket.ssid] = new WTVClientSessionData(minisrv_config.config.hide_ssid_in_logs);
ssid_sessions[socket.ssid] = new WTVClientSessionData(socket.ssid,minisrv_config.config.hide_ssid_in_logs);
ssid_sessions[socket.ssid].SaveIfRegistered();
}
if (!ssid_sessions[socket.ssid].capabilities) ssid_sessions[socket.ssid].capabilities = new WTVClientCapabilities(headers["wtv-capability-flags"]);
}
@@ -1249,14 +1428,16 @@ async function cleanupSocket(socket) {
// set timer to destroy entirety of session data if client does not return in X time
var timeout = 180000; // timeout is in milliseconds, default 180000 (3 min) .. be sure to allow time for dialup reconnections
if (!ssid_sessions[socket.ssid].data_store.socket_check) {
ssid_sessions[socket.ssid].data_store.socket_check = setTimeout(function (ssid) {
if (ssid_sessions[ssid].currentConnections() === 0) {
if (!zquiet) console.log(" * WebTV SSID", filterSSID(ssid), " has not been seen in", (timeout / 1000), "seconds, cleaning up session data for this SSID");
delete ssid_sessions[ssid];
}
}, timeout, socket.ssid);
}
// clear any existing timeout check
if (ssid_sessions[socket.ssid].data_store.socket_check) clearTimeout(ssid_sessions[socket.ssid].data_store.socket_check);
// set timeout to check
ssid_sessions[socket.ssid].data_store.socket_check = setTimeout(function (ssid) {
if (ssid_sessions[ssid].currentConnections() === 0) {
if (!zquiet) console.log(" * WebTV SSID", filterSSID(ssid), " has not been seen in", (timeout / 1000), "seconds, cleaning up session data for this SSID");
delete ssid_sessions[ssid];
}
}, timeout, socket.ssid);
}
}
socket.end();
@@ -1270,6 +1451,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) {

View File

@@ -14,8 +14,12 @@
"post_percentages": [ 0, 25, 50, 100 ],
"verbosity": 2,
"error_log_file": "errors.log",
"allow_guests": true,
"enable_lzpf_compression": true
"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": {
"wtv-head-waiter": {
@@ -52,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,
@@ -67,7 +72,7 @@
"port": 1630,
"connections": 3
},
"wtv-update": {
"wtv-disk": {
"port": 1635,
"connections": 3
},

View File

@@ -1,6 +1,6 @@
{
"name": "zefie_wtvp_minisrv",
"version": "0.9.13",
"version": "0.9.16",
"description": "WebTV Service (WTVP) Emulation Server",
"main": "app.js",
"homepage": "https://github.com/zefie/zefie_wtvp_minisrv",

View File

@@ -32,6 +32,8 @@
<Content Include=".gitignore" />
<Content Include="app.js" />
<Content Include="config.json" />
<Content Include="ServiceVault\http_pc\get.js" />
<Content Include="ServiceVault\http_pc\index.js" />
<Content Include="ServiceVault\wtv-1800\noflash.js" />
<Content Include="ServiceVault\wtv-1800\offer-open-isp-suggest.js" />
<Content Include="ServiceVault\wtv-chat\home.js" />
@@ -50,6 +52,7 @@
<Content Include="ServiceVault\wtv-cookie\reset.js">
<SubType>Code</SubType>
</Content>
<Content Include="ServiceVault\wtv-flashrom\content\content-serve.js" />
<Content Include="ServiceVault\wtv-flashrom\current-noflash.js">
<SubType>Code</SubType>
</Content>
@@ -71,6 +74,9 @@
<Content Include="ServiceVault\wtv-flashrom\ROMCache\up-arrows.swf" />
<Content Include="ServiceVault\wtv-flashrom\ROMCache\WebTVLogoJewel.gif" />
<Content Include="ServiceVault\wtv-flashrom\willie.js" />
<Content Include="ServiceVault\wtv-head-waiter\relogin.js">
<SubType>Code</SubType>
</Content>
<Content Include="ServiceVault\wtv-music\demo\hacktv4.gif" />
<Content Include="ServiceVault\wtv-music\demo\index.html" />
<Content Include="ServiceVault\wtv-music\demo\midi\acey.mid" />
@@ -247,15 +253,13 @@
<Content Include="ServiceVault\wtv-tricks\unregister.js">
<SubType>Code</SubType>
</Content>
<Content Include="ServiceVault\wtv-update\content\diskmaps\DealerDemo.json">
<Content Include="ServiceVault\wtv-disk\content\diskmaps\DealerDemo.json">
<SubType>Code</SubType>
</Content>
<Content Include="ServiceVault\wtv-update\sync.js">
<Content Include="ServiceVault\wtv-disk\sync.js">
<SubType>Code</SubType>
</Content>
<Content Include="ServiceVault\wtv-update\DealerDemo.js" />
<Content Include="ServiceVault\wtv-home\home.js" />
<Content Include="ServiceVault\wtv-update\updatesuccess.txt" />
<Content Include="ServiceVault\wtv-1800\preregister.js" />
<Content Include="ServiceVault\wtv-head-waiter\finalize-security.js" />
<Content Include="ServiceVault\wtv-head-waiter\login-stage-two.js" />
@@ -287,10 +291,12 @@
</ItemGroup>
<ItemGroup>
<Folder Include="ServiceVault\" />
<Folder Include="ServiceVault\http_pc\" />
<Folder Include="ServiceVault\wtv-chat\" />
<Folder Include="ServiceVault\wtv-chat\images\" />
<Folder Include="ServiceVault\wtv-cookie\" />
<Folder Include="ServiceVault\wtv-flashrom\" />
<Folder Include="ServiceVault\wtv-flashrom\content\" />
<Folder Include="ServiceVault\wtv-flashrom\ROMCache\" />
<Folder Include="ServiceVault\wtv-music\" />
<Folder Include="ServiceVault\wtv-music\demo\" />
@@ -300,13 +306,13 @@
<Folder Include="ServiceVault\wtv-star\" />
<Folder Include="ServiceVault\wtv-star\ROMCache\" />
<Folder Include="ServiceVault\wtv-tricks\" />
<Folder Include="ServiceVault\wtv-update\" />
<Folder Include="ServiceVault\wtv-disk\" />
<Folder Include="ServiceVault\wtv-1800\" />
<Folder Include="ServiceVault\wtv-head-waiter\" />
<Folder Include="ServiceVault\wtv-home\" />
<Folder Include="ServiceVault\wtv-log\" />
<Folder Include="ServiceVault\wtv-update\content\" />
<Folder Include="ServiceVault\wtv-update\content\diskmaps\" />
<Folder Include="ServiceVault\wtv-disk\content\" />
<Folder Include="ServiceVault\wtv-disk\content\diskmaps\" />
</ItemGroup>
<Import Project="$(VSToolsPath)\Node.js Tools\Microsoft.NodejsToolsV2.targets" />
</Project>