diff --git a/README.md b/README.md index 9d146267..ea305a88 100644 --- a/README.md +++ b/README.md @@ -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)*** diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index c6ef8b6b..ee9b08bd 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -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 diff --git a/docker-compose/minisrv/Dockerfile b/docker-compose/minisrv/Dockerfile index 447186f9..547cefc5 100644 --- a/docker-compose/minisrv/Dockerfile +++ b/docker-compose/minisrv/Dockerfile @@ -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 + diff --git a/user_config_README.md b/user_config_README.md index 392e5398..8b8c0cfe 100644 --- a/user_config_README.md +++ b/user_config_README.md @@ -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] ``` diff --git a/zefie_wtvp_minisrv/ServiceVault/http_pc/get.js b/zefie_wtvp_minisrv/ServiceVault/http_pc/get.js new file mode 100644 index 00000000..658b975a --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/http_pc/get.js @@ -0,0 +1,37 @@ +if (request_headers.query.url) { + if (request_headers.query.url.indexOf(":/") > 0) { + var service_request = request_headers.query.url.split(":/")[0]; + var service_port = 0; + Object.keys(minisrv_config.services).forEach(function (k) { + if (minisrv_config.services[k].disabled) return; + if (k == service_request) service_port = minisrv_config.services[k].port; + }); + if (service_port > 0) { + request_is_async = true; + var request_headers_out = new Array() + request_headers_out.request = "GET " + request_headers.query.url; + request_headers_out.request_url = request_headers.query.url; + request_headers_out['wtv-client-serial-number'] = socket.id + "HTTPPCReq"; + processURL(socket, request_headers_out); +/* + var s = require('net').Socket(); + var outdata = ""; + s.connect(service_port); + s.setTimeout(1, function () { + outdata = outdata.split() + sendToClient(socket,outdata); + }); + s.on('data', function (data) { + outdata += data; + }); + s.write() +*/ + } + } +} + +if (!headers) { + var errpage = doErrorPage(500) + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/http_pc/index.js b/zefie_wtvp_minisrv/ServiceVault/http_pc/index.js new file mode 100644 index 00000000..802a1f4b --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/http_pc/index.js @@ -0,0 +1,31 @@ +headers = `200 OK +Content-Type: text/html` + +var splash_logo = minisrv_config.config.service_splash_logo; +if (splash_logo.substring(0, 4) == "file") splash_logo = "wtv-star:/ROMCache/splash_logo_hacktv.gif"; + +data = ` + + +zefie minisrv ${minisrv_config.version} + + + +
+ +
+ +


+


+


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

+ +`; \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-1800/preregister.js b/zefie_wtvp_minisrv/ServiceVault/wtv-1800/preregister.js index 49ae3d46..fd2bfc70 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-1800/preregister.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-1800/preregister.js @@ -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"; } diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/allyouneed.html b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/allyouneed.html similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/allyouneed.html rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/allyouneed.html diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/allyouneed.swf b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/allyouneed.swf similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/allyouneed.swf rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/allyouneed.swf diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/attractloop.swf b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/attractloop.swf similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/attractloop.swf rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/attractloop.swf diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/communicate.html b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/communicate.html similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/communicate.html rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/communicate.html diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/communicate.swf b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/communicate.swf similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/communicate.swf rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/communicate.swf diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/buttonsplus_all.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/buttonsplus_all.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/buttonsplus_all.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/buttonsplus_all.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/buttonsplus_comm.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/buttonsplus_comm.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/buttonsplus_comm.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/buttonsplus_comm.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/buttonsplus_interactive.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/buttonsplus_interactive.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/buttonsplus_interactive.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/buttonsplus_interactive.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/buttonsplus_internet.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/buttonsplus_internet.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/buttonsplus_internet.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/buttonsplus_internet.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/buttonsplus_try.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/buttonsplus_try.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/buttonsplus_try.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/buttonsplus_try.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/spacer.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/spacer.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/images/spacer.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/images/spacer.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/index.html b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/index.html similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/index.html rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/index.html diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/interactive.html b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/interactive.html similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/interactive.html rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/interactive.html diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/interactive.swf b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/interactive.swf similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/interactive.swf rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/interactive.swf diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/internet.html b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/internet.html similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/internet.html rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/internet.html diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/internet.swf b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/internet.swf similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/internet.swf rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/internet.swf diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/mainmenu.html b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/mainmenu.html similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/mainmenu.html rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/mainmenu.html diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/mainmenu.swf b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/mainmenu.swf similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/mainmenu.swf rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/mainmenu.swf diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/trymsntv.html b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/trymsntv.html similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/trymsntv.html rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/trymsntv.html diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/trymsntv.swf b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/trymsntv.swf similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/DealerDemo/trymsntv.swf rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/DealerDemo/trymsntv.swf diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/content/diskmaps/DealerDemo.json b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/diskmaps/DealerDemo.json similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-update/content/diskmaps/DealerDemo.json rename to zefie_wtvp_minisrv/ServiceVault/wtv-disk/content/diskmaps/DealerDemo.json diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-disk/sync.js b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/sync.js new file mode 100644 index 00000000..8f8429b6 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-disk/sync.js @@ -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 = ` + + + + + Retrieving files... + + + + + + + +
+ + + + + + +
+ ${message} +
+
+
+
+ + + +
+ + + ${main_message} +

This may take a while. + +

+ +

+ + + +
+ + +`; + +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/content/content-serve.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/content/content-serve.js new file mode 100644 index 00000000..b8209c2a --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/content/content-serve.js @@ -0,0 +1,23 @@ +const WTVFlashrom = require("./WTVFlashrom.js"); +request_is_async = true; +console.log(request_headers); + +var bf0app_update = false; +var request_path = request_headers.request_url.replace(service_name + ":/", ""); +var romtype = ssid_sessions[socket.ssid].get("wtv-client-rom-type"); +var bootver = ssid_sessions[socket.ssid].get("wtv-client-bootrom-version") + +if ((romtype == "bf0app" || !romtype) && (bootver == "105" || !bootver)) { + // assume old classic in flash mode, override user setting and send tellyscript + // because it is required to proceed in flash mode + bf0app_update = true; + ssid_sessions[socket.ssid].set("bf0app_update", bf0app_update); +} + +if (!ssid_sessions[socket.ssid].data_store.WTVFlashrom) { + ssid_sessions[socket.ssid].data_store.WTVFlashrom = new WTVFlashrom(service_vaults, service_name, minisrv_config.services[service_name].use_zefie_server, bf0app_update, minisrv_config.services[service_name].debug); +} + +ssid_sessions[socket.ssid].data_store.WTVFlashrom.getFlashRom(request_path, function (data, headers) { + sendToClient(socket, headers, data); +}); diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-by-path.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-by-path.js index 33f19f0e..a2ad57e0 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-by-path.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-by-path.js @@ -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) diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-lc2-page.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-lc2-page.js index 25a2368b..a7f93034 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-lc2-page.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-lc2-page.js @@ -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"> - + - +
Updating now
@@ -56,7 +55,7 @@ Updating now
- + @@ -92,7 +91,7 @@ data += `





-
+
${flashrom_info.message}

- + @@ -119,7 +118,7 @@ ${flashrom_info.message} - + diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/initiate-lc2-download.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/initiate-lc2-download.js index b22756f3..3a00509a 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/initiate-lc2-download.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/initiate-lc2-download.js @@ -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 { diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-complete.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-complete.js index 61ee429e..b072fe81 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-complete.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-complete.js @@ -25,12 +25,12 @@ hspace=0 vspace=0 fontsize="large"> - + - +
Updating complete
@@ -38,7 +38,7 @@ Updating complete
- + @@ -65,7 +65,7 @@ The update is complete.
- + @@ -74,7 +74,7 @@ The update is complete.
- + diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-failed.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-failed.js index 3cb3fa17..3f6e1b91 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-failed.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-failed.js @@ -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 = ` - + - +
Updating failed
@@ -66,7 +66,7 @@ data = `
- + @@ -96,7 +96,7 @@ data = ` - + @@ -105,7 +105,7 @@ data = ` - + diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/willie.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/willie.js index 2600705c..765de4e3 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/willie.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/willie.js @@ -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); }); diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login-stage-two.js b/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login-stage-two.js index 415f3b64..fde0566b 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login-stage-two.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login-stage-two.js @@ -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?"; diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login.js b/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login.js index ae847962..9f44b498 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login.js @@ -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 = ''; } \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/relogin.js b/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/relogin.js new file mode 100644 index 00000000..52cb2023 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/relogin.js @@ -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 = ''; \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-home/home.js b/zefie_wtvp_minisrv/ServiceVault/wtv-home/home.js index 214d201b..56e1609a 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-home/home.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-home/home.js @@ -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 = ` @@ -24,13 +34,16 @@ function go() { location.href=document.access.url.value; } -Welcome to `+ z_title + `
-`; -if (minisrv_config.config.git_commit) data += "" + "  ".repeat(32) + "git revision " + minisrv_config.config.git_commit + "
"; +Welcome to ${z_title}`; +if (ssid_sessions[socket.ssid].getSessionData("registered")) data += ", " + ssid_sessions[socket.ssid].getSessionData("subscriber_username") + "!"; +data += "
"; +if (minisrv_config.config.git_commit) data += `
git revision ${minisrv_config.config.git_commit}

`; + data += ` -Encryption Status: ${cryptstatus}
+
+Status: ${cryptstatus} (${compstatus})
Connection Speed: &rate; -

+


  • client:relog (direct)
  • @@ -45,7 +58,7 @@ if (ssid_sessions[socket.ssid].hasCap("client-has-disk")) { data += "
  • DiskHax ~ VFatHax
  • \n"; if (ssid_sessions[socket.ssid].hasCap("client-can-do-macromedia-flash2")) { // only show demo if client can do flash2 - data += "
  • Old MSNTV DealerDemo: Download ~ Access (after Download)
  • \n"; + data += "
  • Old MSNTV DealerDemo: Download ~ Access (after Download)
  • \n"; } } diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-register/ServeLegal.js b/zefie_wtvp_minisrv/ServiceVault/wtv-register/ServeLegal.js index c14144b4..44a7168f 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-register/ServeLegal.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-register/ServeLegal.js @@ -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. diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-register/register.js b/zefie_wtvp_minisrv/ServiceVault/wtv-register/register.js index aecdd722..12092688 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-register/register.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-register/register.js @@ -40,7 +40,7 @@ Welcome ENCTYPE="x-www-form-encoded" METHOD="POST"> -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.

    Press the "Continue" button below to begin setup.

    diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-tricks/tricks.js b/zefie_wtvp_minisrv/ServiceVault/wtv-tricks/tricks.js index f994ee0f..acc79f73 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-tricks/tricks.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-tricks/tricks.js @@ -20,13 +20,13 @@ data = ` Info -List Cookies +List Cookies Visit Ultra Willie's! -Clear Cookies +Clear Cookies diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/DealerDemo.js b/zefie_wtvp_minisrv/ServiceVault/wtv-update/DealerDemo.js deleted file mode 100644 index ecf03f93..00000000 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-update/DealerDemo.js +++ /dev/null @@ -1,51 +0,0 @@ -headers = `200 OK -Content-Type: text/html` - -data = ` - - - - Retrieving Files - - - - - - - -
    - - - - - - -
    - Retrieving Files -
    -
    -
    -
    - - - -
    - - - Your Internet terminal is retrieving some files. -

    This may take a while. - -

    - -

    - - - -
    - -` \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/sync.js b/zefie_wtvp_minisrv/ServiceVault/wtv-update/sync.js deleted file mode 100644 index 032a8dc5..00000000 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-update/sync.js +++ /dev/null @@ -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"); -} - diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/updatesuccess.txt b/zefie_wtvp_minisrv/ServiceVault/wtv-update/updatesuccess.txt deleted file mode 100644 index 9655cb3d..00000000 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-update/updatesuccess.txt +++ /dev/null @@ -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 \ No newline at end of file diff --git a/zefie_wtvp_minisrv/WTVClientSessionData.js b/zefie_wtvp_minisrv/WTVClientSessionData.js index bf1099dd..d44c948d 100644 --- a/zefie_wtvp_minisrv/WTVClientSessionData.js +++ b/zefie_wtvp_minisrv/WTVClientSessionData.js @@ -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; } } diff --git a/zefie_wtvp_minisrv/WTVFlashrom.js b/zefie_wtvp_minisrv/WTVFlashrom.js index 478e5629..986c4666 100644 --- a/zefie_wtvp_minisrv/WTVFlashrom.js +++ b/zefie_wtvp_minisrv/WTVFlashrom.js @@ -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)); } } } diff --git a/zefie_wtvp_minisrv/WTVLzpf.js b/zefie_wtvp_minisrv/WTVLzpf.js index 27b09cf8..5bfea2ea 100644 --- a/zefie_wtvp_minisrv/WTVLzpf.js +++ b/zefie_wtvp_minisrv/WTVLzpf.js @@ -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; \ No newline at end of file +module.exports = WTVLzpf; diff --git a/zefie_wtvp_minisrv/app.js b/zefie_wtvp_minisrv/app.js index 95752d97..ac1624bb 100644 --- a/zefie_wtvp_minisrv/app.js +++ b/zefie_wtvp_minisrv/app.js @@ -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) { diff --git a/zefie_wtvp_minisrv/config.json b/zefie_wtvp_minisrv/config.json index 91767a6f..80bd772d 100644 --- a/zefie_wtvp_minisrv/config.json +++ b/zefie_wtvp_minisrv/config.json @@ -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 }, diff --git a/zefie_wtvp_minisrv/package.json b/zefie_wtvp_minisrv/package.json index 6ae035e2..f16d9473 100644 --- a/zefie_wtvp_minisrv/package.json +++ b/zefie_wtvp_minisrv/package.json @@ -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", diff --git a/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj b/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj index 1e623a83..17205247 100644 --- a/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj +++ b/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj @@ -32,6 +32,8 @@ + + @@ -50,6 +52,7 @@ Code + Code @@ -71,6 +74,9 @@ + + Code + @@ -247,15 +253,13 @@ Code - + Code - + Code - - @@ -287,10 +291,12 @@ + + @@ -300,13 +306,13 @@ - + - - + + \ No newline at end of file