too much to remember

- rewrote sync system yet again
- more classes
    - WTVShared class for shared functions
    - clientShowAlert class for easy client:showalert urls
- User File Store
    - Can upload with PUT commands in wtv-disk
    - Programmically access files with new functions in WTVClientSessionData
    - TODO: file browser
- other stuff I can't remember
This commit is contained in:
zefie
2021-08-10 18:30:59 -04:00
parent 2f19b16454
commit e9c883d85a
11 changed files with 546 additions and 261 deletions

View File

@@ -2,7 +2,7 @@
if (socket.ssid) { if (socket.ssid) {
if (ssid_sessions[socket.ssid].loadSessionData() == true) { if (ssid_sessions[socket.ssid].loadSessionData() == true) {
console.log(" * Loaded session data from disk for", filterSSID(socket.ssid)) console.log(" * Loaded session data from disk for", wtvshared.filterSSID(socket.ssid))
ssid_sessions[socket.ssid].setSessionData("registered", (ssid_sessions[socket.ssid].getSessionData("registered") == true) ? true : false); ssid_sessions[socket.ssid].setSessionData("registered", (ssid_sessions[socket.ssid].getSessionData("registered") == true) ? true : false);
} else { } else {
ssid_sessions[socket.ssid].session_data = {}; ssid_sessions[socket.ssid].session_data = {};
@@ -20,11 +20,11 @@
} }
} }
}); });
if (i > 0 && zdebug) console.log(" # Closed", i, "previous sockets for", filterSSID(socket.ssid)); if (i > 0 && zdebug) console.log(" # Closed", i, "previous sockets for", wtvshared.filterSSID(socket.ssid));
} }
} }
if (ssid_sessions[socket.ssid].data_store.wtvsec_login) { if (ssid_sessions[socket.ssid].data_store.wtvsec_login) {
if (zdebug) console.log(" # Recreating primary WTVSec login instance for", filterSSID(socket.ssid)); if (zdebug) console.log(" # Recreating primary WTVSec login instance for", wtvshared.filterSSID(socket.ssid));
delete ssid_sessions[socket.ssid].data_store.wtvsec_login; delete ssid_sessions[socket.ssid].data_store.wtvsec_login;
} }

View File

@@ -1,10 +1,8 @@
// todo: async const WTVDownloadList = require("./WTVDownloadList.js");
var wtvdl = new WTVDownloadList(service_name);
var force_update = (request_headers.query.force == "true") ? true : false; var force_update = (request_headers.query.force == "true") ? true : false;
if (request_headers['wtv-request-type'] == 'download') { if (request_headers['wtv-request-type'] == 'download') {
const WTVDownloadList = require("./WTVDownloadList.js");
var wtvdl = new WTVDownloadList();
var content_dir = "content/" var content_dir = "content/"
var diskmap_dir = content_dir + "diskmaps/"; var diskmap_dir = content_dir + "diskmaps/";
@@ -273,58 +271,6 @@ if (request_headers['wtv-request-type'] == 'download') {
} else if (request_headers.query.group && request_headers.query.diskmap) { } else if (request_headers.query.group && request_headers.query.diskmap) {
var message = request_headers.query.message || "Retrieving files..."; var message = request_headers.query.message || "Retrieving files...";
var main_message = request_headers.query.main_message || "Your receiver is downloading files."; var main_message = request_headers.query.main_message || "Your receiver is downloading files.";
headers = `200 OK headers = "200 OK\nContent-Type: text/html";
Content-Type: text/html`; data = wtvdl.getSyncPage(minisrv_config, message, request_headers.query.group, request_headers.query.diskmap, main_message, message, force_update)
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,21 @@
if (request_headers.post_data) {
if (request_headers.query.partialPath) {
if (socket.ssid) {
if (ssid_sessions[socket.ssid]) {
if (ssid_sessions[socket.ssid].isRegistered()) {
var result = ssid_sessions[socket.ssid].storeUserStoreFile(request_headers.query.partialPath, new Buffer.from(request_headers.post_data.toString(CryptoJS.enc.Hex), 'hex'), request_headers.query['last-modified-seconds'] || null, (request_headers.query.no_overwrite) ? false : true);
if (result) {
headers = "200 OK\n";
headers += "Content-Type: text/plain";
}
}
}
}
}
}
if (!headers) {
var errpage = doErrorPage(400)
headers = errpage[0];
data = errpage[1];
}

View File

@@ -16,11 +16,11 @@ if (socket.ssid !== null) {
var client_challenge_response = request_headers["wtv-challenge-response"] || null; var client_challenge_response = request_headers["wtv-challenge-response"] || null;
if (challenge_response && client_challenge_response) { if (challenge_response && client_challenge_response) {
if (challenge_response.toString(CryptoJS.enc.Base64) == client_challenge_response) { if (challenge_response.toString(CryptoJS.enc.Base64) == client_challenge_response) {
console.log(" * wtv-challenge-response success for " + filterSSID(socket.ssid)); console.log(" * wtv-challenge-response success for " + wtvshared.filterSSID(socket.ssid));
wtvsec_login.PrepareTicket(); wtvsec_login.PrepareTicket();
} else { } else {
console.log(" * wtv-challenge-response FAILED for " + filterSSID(socket.ssid)); console.log(" * wtv-challenge-response FAILED for " + wtvshared.filterSSID(socket.ssid));
if (zdebug) console.log("Response Expected:", challenge_response.toString(CryptoJS.enc.Base64)); if (zdebug) console.log("Response Expected:", challenge_response.toString(CryptoJS.enc.Base64));
if (zdebug) console.log("Response Received:", client_challenge_response) if (zdebug) console.log("Response Received:", client_challenge_response)
gourl = "wtv-head-waiter:/login?reissue_challenge=true"; gourl = "wtv-head-waiter:/login?reissue_challenge=true";

View File

@@ -22,7 +22,7 @@ Content-length: 0`;
logdata_outstring_hex += request_headers.post_data.toString(CryptoJS.enc.Hex); logdata_outstring_hex += request_headers.post_data.toString(CryptoJS.enc.Hex);
if (minisrv_config.services[service_name].write_logs_to_disk) { if (minisrv_config.services[service_name].write_logs_to_disk) {
fs.writeFile(fullpath, logdata_outstring_hex, "Hex", function () { fs.writeFile(fullpath, logdata_outstring_hex, "Hex", function () {
if (!zquiet) console.log(" * Wrote POST log data from", filterSSID(socket.ssid), "for", socket.id); if (!zquiet) console.log(" * Wrote POST log data from", wtvshared.filterSSID(socket.ssid), "for", socket.id);
sendToClient(socket, headers, data); sendToClient(socket, headers, data);
}); });
} else { } else {
@@ -41,7 +41,7 @@ Content-length: 0`;
var logdata_outstring_hex = Buffer.from(logdata_outstring, 'utf8').toString('hex'); var logdata_outstring_hex = Buffer.from(logdata_outstring, 'utf8').toString('hex');
if (minisrv_config.services[service_name].write_logs_to_disk) { if (minisrv_config.services[service_name].write_logs_to_disk) {
fs.writeFile(fullpath, logdata_outstring_hex, "Hex", function () { fs.writeFile(fullpath, logdata_outstring_hex, "Hex", function () {
if (!zquiet) console.log(" * Wrote GET log data from", filterSSID(socket.ssid), "for", socket.id); if (!zquiet) console.log(" * Wrote GET log data from", wtvshared.filterSSID(socket.ssid), "for", socket.id);
sendToClient(socket, headers, data); sendToClient(socket, headers, data);
}); });
} else { } else {

View File

@@ -18,7 +18,7 @@ Content-Type: text/html`
var wtv_system_version = ssid_sessions[socket.ssid].get("wtv-system-version"); var wtv_system_version = ssid_sessions[socket.ssid].get("wtv-system-version");
var wtv_client_bootrom_version = ssid_sessions[socket.ssid].get("wtv-client-bootrom-version"); var wtv_client_bootrom_version = ssid_sessions[socket.ssid].get("wtv-client-bootrom-version");
var wtv_client_serial_number = filterSSID(ssid_sessions[socket.ssid].get("wtv-client-serial-number")); var wtv_client_serial_number = wtvshared.filterSSID(ssid_sessions[socket.ssid].get("wtv-client-serial-number"));
var wtv_client_rom_type = ssid_sessions[socket.ssid].get("wtv-client-rom-type"); var wtv_client_rom_type = ssid_sessions[socket.ssid].get("wtv-client-rom-type");
var wtv_system_chipversion_str = ssid_sessions[socket.ssid].get("wtv-system-chipversion"); var wtv_system_chipversion_str = ssid_sessions[socket.ssid].get("wtv-system-chipversion");
var wtv_system_sysconfig_hex = parseInt(ssid_sessions[socket.ssid].get("wtv-system-sysconfig")).toString(16); var wtv_system_sysconfig_hex = parseInt(ssid_sessions[socket.ssid].get("wtv-system-sysconfig")).toString(16);

View File

@@ -4,6 +4,7 @@ class WTVClientSessionData {
fs = require('fs'); fs = require('fs');
path = require('path'); path = require('path');
ssid = null; ssid = null;
data_store = null; data_store = null;
session_store = null; session_store = null;
@@ -11,48 +12,107 @@ class WTVClientSessionData {
capabilities = null; capabilities = null;
session_storage = ""; session_storage = "";
hide_ssid_in_logs = true; hide_ssid_in_logs = true;
wtvshared = null;
filterSSID(obj) { wtvmime = null;
if (this.hide_ssid_in_logs === true) {
if (typeof (obj) == "string") {
if (obj.substr(0, 8) == "MSTVSIMU") {
return obj.substr(0, 10) + ('*').repeat(10) + obj.substr(20);
} else if (obj.substr(0, 5) == "1SEGA") {
return obj.substr(0, 6) + ('*').repeat(6) + obj.substr(13);
} else {
return obj.substr(0, 6) + ('*').repeat(9);
}
} else {
if (obj["wtv-client-serial-number"]) {
var ssid = 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") {
obj["wtv-client-serial-number"] = ssid.substr(0, 6) + ('*').repeat(6) + ssid.substr(13);
} else {
obj["wtv-client-serial-number"] = ssid.substr(0, 6) + ('*').repeat(9);
}
}
return obj;
}
} else {
return obj;
}
}
constructor(ssid, hide_ssid_in_logs, session_storage_directory) { constructor(ssid, hide_ssid_in_logs, session_storage_directory) {
var { WTVShared, clientShowAlert } = require('./WTVShared.js');
var WTVMimeTypes = require('./WTVMimeTypes.js');
this.wtvshared = new WTVShared();
this.wtvmime = new WTVMimeTypes();
this.ssid = ssid; this.ssid = ssid;
if (hide_ssid_in_logs) this.hide_ssid_in_logs = hide_ssid_in_logs; if (hide_ssid_in_logs) this.hide_ssid_in_logs = hide_ssid_in_logs;
if (!session_storage_directory) session_storage_directory = __dirname + "/SessionStore"; if (!session_storage_directory) session_storage_directory = __dirname + this.path.sep + "SessionStore";
this.session_storage = session_storage_directory; this.session_storage = session_storage_directory;
this.data_store = new Array(); this.data_store = new Array();
this.session_store = {}; this.session_store = {};
} }
/**
* Returns the absolute path to the user's file store
*/
getUserStoreDirectory() {
return this.session_storage + this.path.sep + this.ssid + this.path.sep;
}
/**
* Store a file in the user's file store
* @param {string} path Relative path to User's file store
* @param {Buffer} data File data
* @param {number|null} last_modified Unix timestamp to set last modified date to
* @param {boolean} overwrite Overwrite if file exists
* @returns {boolean} Whether or not the file was written
*/
storeUserStoreFile(path, data, last_modified = null, overwrite = true) {
var result = false;
var store_dir = this.getUserStoreDirectory();
var path_split = path.split('/');
var file_name = path_split.pop();
var store_dir_path = this.wtvshared.makeSafePath(store_dir, path_split.join('/').replace('/', this.path.sep));
var store_full_path = this.wtvshared.makeSafePath(store_dir_path, file_name);
try {
if (!this.fs.existsSync(store_dir_path)) this.fs.mkdirSync(store_dir_path, { recursive: true });
var file_exists = this.fs.existsSync(store_full_path);
if (!file_exists || (file_exists && overwrite)) result = this.fs.writeFileSync(store_full_path, data);
if (result !== false && last_modified) {
var file_timestamp = new Date(last_modified * 1000);
fs.utimesSync(store_full_path, Date.now(), file_timestamp)
}
} catch (e) {
console.error(" # User File Store failed", e);
}
return (result === false) ? false : true;
}
/**
* Retrieves a file from the user store
* @param {string} path Path relative to the User File Store
* @returns {Buffer|false} Buffer data, or false if could not open file
*/
getUserStoreFile(path) {
var store_dir = this.getUserStoreDirectory();
var store_dir_path = this.wtvshared.makeSafePath(store_dir, path.replace('/', this.path.sep));
if (this.fs.existsSync(store_dir_path)) return this.fs.readFileSync(store_dir_path);
else return false;
}
/**
* Retrieves a file from the user store with a file://Disk/ url
* @param {string} url file://Disk/ base url
* @returns {Buffer|false} Buffer data, or false if could not open file
*/
getUserStoreFileByURL(url) {
var path_split = url.split('/');
path_split.shift();
path_split.shift();
var store_dir_path = path_split.join('/').replace('/', this.path.sep);
return this.getUserStoreFile(store_dir_path);
}
/**
* Retrieves the Content-Type of a User Store File
* @param {string} path Path relative to the User File Store
* @returns {string|false} Content-Type, or false if could not open file
*/
getUserStoreContentType(path) {
return this.wtvmime.getSimpleContentType(path);
}
/**
* Returns a RFC7231 compliant UTC Date String from the current time
* @param {Number} offset Offset from current time (+/-)
* @returns {string} A RFC7231 compliant UTC Date String from the current time
*/
getUTCTime(offset = 0) { getUTCTime(offset = 0) {
return new Date((new Date).getTime() + offset).toUTCString(); return new Date((new Date).getTime() + offset).toUTCString();
} }
/**
* Returns the number of user cookies
* @returns {number} Number of cookies
*/
countCookies() { countCookies() {
return Object.keys(this.session_store.cookies).length || 0; return Object.keys(this.session_store.cookies).length || 0;
} }
@@ -184,7 +244,7 @@ class WTVClientSessionData {
} }
} catch (e) { } catch (e) {
// Don't log error 'file not found', it just means the client isn't registered yet // Don't log error 'file not found', it just means the client isn't registered yet
if (e.code != "ENOENT") console.error(" # Error loading session data for", this.filterSSID(this.ssid), e); if (e.code != "ENOENT") console.error(" # Error loading session data for", this.wtvshared.filterSSID(this.ssid), e);
return false; return false;
} }
} }
@@ -209,7 +269,7 @@ class WTVClientSessionData {
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"); 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; return true;
} catch (e) { } catch (e) {
console.error(" # Error saving session data for", this.filterSSID(this.ssid), e); console.error(" # Error saving session data for", this.wtvshared.filterSSID(this.ssid), e);
return false; return false;
} }
} }
@@ -249,7 +309,7 @@ class WTVClientSessionData {
} }
} catch (e) { } catch (e) {
// Don't log error 'file not found', it just means the client isn't registered yet // Don't log error 'file not found', it just means the client isn't registered yet
console.error(" # Error deleting session data for", this.filterSSID(this.ssid), e); console.error(" # Error deleting session data for", this.wtvshared.filterSSID(this.ssid), e);
return false; return false;
} }
} }

View File

@@ -3,10 +3,22 @@
* By: zefie * By: zefie
*/ */
class WTVDownloadList { class WTVDownloadList {
download_list = "";
content_type = "wtv/download-list";
constructor() { download_list = "";
service_name = "";
content_type = "wtv/download-list";
wtvshared = null;
clientShowAlert = null;
/**
* Constructs the WTVDownloadList Class
* @param {string} service_name Service name to use in wtv-urls
*/
constructor(service_name = "wtv-disk") {
var { WTVShared, clientShowAlert } = require('./WTVShared.js');
this.wtvshared = new WTVShared();
this.clientShowAlert = clientShowAlert;
this.service_name = service_name
this.clear(); this.clear();
} }
@@ -98,13 +110,31 @@ class WTVDownloadList {
* Adds a PUT command to the download list * Adds a PUT command to the download list
* @param {string} path Absolute file://Disk/ path of a file to upload to the service * @param {string} path Absolute file://Disk/ path of a file to upload to the service
* @param {string} destination Destination address (wtv url on service) in which to POST upload the file to * @param {string} destination Destination address (wtv url on service) in which to POST upload the file to
* @param {string} display Message to display while working on this file
*/ */
put(path, destination) { put(path, destination) {
this.download_list += "PUT " + path + "\n"; this.download_list += "PUT " + path + "\n";
this.download_list += "location: " + destination + "\n"; this.download_list += "location: " + destination + "\n";
} }
/**
* Alias to put() for User Store
* @param {string} path Absolute file://Disk/ path of a file to upload to the service
* @param {string} destination Destination file path in the User Store
*/
putUserStoreDest(path, destination) {
this.download_list += "PUT " + path + "\n";
this.download_list += "location: " + this.service_name + ":/userstore?partialPath="+escape(destination) + "\n";
}
/**
* Alias to putUserStoreDest() that generates the destination
* @param {any} path
*/
putUserStore(path) {
var destination = path.replace("file://", "");
this.download_list += "PUT " + path + "\n";
this.download_list += "location: " + this.service_name + ":/userstore?partialPath=" + escape(destination) + "\n";
}
/** /**
* Adds a GET command to the download list * Adds a GET command to the download list
* @param {string} file Non-absolute path of client destination file (relative to group base) * @param {string} file Non-absolute path of client destination file (relative to group base)
@@ -169,6 +199,99 @@ class WTVDownloadList {
this.deleteGroup(group + "-UPDATE"); this.deleteGroup(group + "-UPDATE");
this.delete(path + ".GROUP-UPDATE/"); this.delete(path + ".GROUP-UPDATE/");
} }
/**
* Generates the Download page
* @param {object} minisrv_config minisrv config object
* @param {string} title Page title
* @param {string} group
* @param {string|null} diskmap
* @param {string|null} main_message Message displayed in the center of the page
* @param {string|null} message Initial progress bar message
* @param {boolean|null} force_update Force this update even if the client reports the files are synced
* @param {string|null} success_url Where the client goes when the process succeeds
* @param {string|null} fail_url Where the client goes when the process fails.
* @param {string|null} url Use your own URL for client:fetch?source= instead of our generated one
* @returns {string} HTML Download Page
*/
getSyncPage(minisrv_config, title, group, diskmap = null, main_message = null, message = null, force_update = null, success_url = null, fail_url = null, url = null) {
// Begin Set defaults
if (main_message === null) main_message = "Your receiver is downloading files.";
if (message === null) message = "Retrieving files";
if (force_update === null) force_update = false;
if (url === null) url = this.service_name + ":/sync?diskmap=" + escape(diskmap) + "&force=" + force_update;
if (success_url === null) success_url = new this.clientShowAlert({
'image': minisrv_config.config.service_logo,
'message': "Download successful!",
'buttonlabel1': "Okay",
'buttonaction1': "client:goback",
'noback': true,
}).getURL();
if (fail_url === null) fail_url = new this.clientShowAlert({
'image': minisrv_config.config.service_logo,
'message': "Download failed...",
'buttonlabel1': "Okay...",
'buttonaction1': "client:goback",
'noback': true,
}).getURL();
// End set defaults
return `<html>
<head>
<meta
http-equiv=refresh
content="0;url=client:Fetch?group=${escape(group)}&source=${escape(url)}&message=${escape(message)}"
>
<display downloadsuccess="${success_url}" downloadfail="${fail_url}">
<title>${title}</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>
`
}
} }
module.exports = WTVDownloadList; module.exports = WTVDownloadList;

View File

@@ -0,0 +1,133 @@
/**
* Simple class for WebTV Mime Types and overrides
*/
class WTVMimeTypes {
mime = require('mime-types');
wtvshared = null;
constructor() {
var { WTVShared, clientShowAlert } = require('./WTVShared.js');
this.wtvshared = new WTVShared();
if (!String.prototype.reverse) {
String.prototype.reverse = function () {
var splitString = this.split("");
var reverseArray = splitString.reverse();
var joinArray = reverseArray.join("");
return joinArray;
}
}
}
/**
* Gets the WebTV Content-Type
* @param {string} path Path to a file
* @returns {string} Content-Type
*/
getSimpleContentType(path) {
return this.getContentType(path)[0];
}
/**
* Gets both the WebTV Content-Type and the Modern Content-Type
* @param {string} path Path to a file
* @returns {Array} (WebTV Content-Type, Modern Content-Type)
*/
getContentType(path) {
var file_ext = this.wtvshared.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":
wtv_mime_type = "audio/x-aif";
break;
case "aifc":
wtv_mime_type = "audio/x-aifc";
break;
case "aiff":
wtv_mime_type = "audio/x-aiff";
break;
case "ani":
wtv_mime_type = "x-wtv-animation";
break;
case "brom":
wtv_mime_type = "binary/x-wtv-bootrom";
break;
case "cdf":
wtv_mime_type = "application/netcdf";
break;
case "dat":
wtv_mime_type = "binary/cache-data";
break;
case "dl":
wtv_mime_type = "wtv/download-list";
break;
case "gsm":
wtv_mime_type = "audio/x-gsm";
break;
case "gz":
wtv_mime_type = "application/gzip";
break;
case "ini":
wtv_mime_type = "wtv/jack-configuration";
break;
case "mips-code":
wtv_mime_type = "code/x-wtv-code-mips";
break;
case "o":
wtv_mime_type = "binary/x-wtv-approm";
break;
case "ram":
wtv_mime_type = "audio/x-pn-realaudio";
break;
case "rom":
wtv_mime_type = "binary/x-wtv-flashblock";
break;
case "rsp":
wtv_mime_type = "wtv/jack-response";
break;
case "swa":
case "swf":
wtv_mime_type = "application/x-shockwave-flash";
break;
case "srf":
case "spl":
wtv_mime_type = "wtv/jack-data";
break;
case "ttf":
wtv_mime_type = "wtv/jack-fonts";
break;
case "tvch":
wtv_mime_type = "wtv/tv-channels";
break;
case "tvl":
wtv_mime_type = "wtv/tv-listings";
break;
case "tvsl":
wtv_mime_type = "wtv/tv-smartlinks";
break;
case "wad":
wtv_mime_type = "binary/doom-data";
break;
case "mp2":
case "hsb":
case "rmf":
case "s3m":
case "mod":
case "xm":
wtv_mime_type = "application/Music";
break;
}
modern_mime_type = this.mime.lookup(path);
if (wtv_mime_type == "") wtv_mime_type = modern_mime_type;
return new Array(wtv_mime_type, modern_mime_type);
}
}
module.exports = WTVMimeTypes;

View File

@@ -0,0 +1,141 @@
/**
* Shared functions across all classes and apps
*/
class WTVShared {
path = require('path');
constructor() {
if (!String.prototype.reverse) {
String.prototype.reverse = function () {
var splitString = this.split("");
var reverseArray = splitString.reverse();
var joinArray = reverseArray.join("");
return joinArray;
}
}
}
/**
* Returns a censored SSID
* @param {string|Array} obj SSID String or Headers Object
*/
filterSSID(obj) {
if (this.hide_ssid_in_logs === true) {
if (typeof (obj) == "string") {
if (obj.substr(0, 8) == "MSTVSIMU") {
return obj.substr(0, 10) + ('*').repeat(10) + obj.substr(20);
} else if (obj.substr(0, 5) == "1SEGA") {
return obj.substr(0, 6) + ('*').repeat(6) + obj.substr(13);
} else {
return obj.substr(0, 6) + ('*').repeat(9);
}
} else {
if (obj["wtv-client-serial-number"]) {
var ssid = 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") {
obj["wtv-client-serial-number"] = ssid.substr(0, 6) + ('*').repeat(6) + ssid.substr(13);
} else {
obj["wtv-client-serial-number"] = ssid.substr(0, 6) + ('*').repeat(9);
}
}
return obj;
}
} else {
return obj;
}
}
/**
* Returns an absolute path
* @param {string} path
* @param {string} directory Root directory
*/
getAbsolutePath(path, directory = __dirname) {
if (path.substring(0, 1) != this.path.sep && path.substring(1, 1) != ":") {
// non-absolute path, so use current directory as base
path = (directory + this.path.sep + path);
} else {
// already absolute path
}
return path;
}
/**
* Gets the file extension from a path
* @param {string} path
* @returns {String} File Extension (without dot)
*/
getFileExt(path) {
return path.reverse().split(".")[0].reverse();
}
/**
* Strips bad things from paths
* @param {string} base Base path
* @param {string} target Sub path
*/
makeSafePath(base, target) {
target.replace(/[\|\&\;\$\%\@\"\<\>\+\,\\]/g, "");
if (this.path.sep != "/") target = target.replace(/\//g, this.path.sep);
var targetPath = this.path.posix.normalize(target)
return base + this.path.sep + targetPath;
}
/**
* Makes sure an SSID is clean, and doesn't contain any exploitable characters
* @param {string} ssid
* @returns {string} Sanitized SSID
*/
makeSafeSSID(ssid = "") {
ssid = ssid.replace(/[^a-zA-Z0-9]/g, "");
if (ssid.length == 0) ssid = null;
return ssid;
}
}
class clientShowAlert {
message = null;
buttonlabel1 = null;
buttonlabel2 = null;
buttonaction1 = null;
buttonaction2 = null;
noback = null;
image = null;
constructor(image = null, message = null, buttonlabel1 = null, buttonaction1 = null, buttonlabel2 = null, buttonaction2 = null, noback = null) {
this.message = message;
this.buttonlabel1 = buttonlabel1;
this.buttonlabel2 = buttonlabel2;
this.buttonaction1 = buttonaction1;
this.buttonaction2 = buttonaction2;
this.message = message;
this.noback = noback;
if (typeof image === 'object') {
this.image = null;
Object.keys(image).forEach(function (k) {
if (this[k] === null) this[k] = image[k];
}, this);
} else {
this.image = image;
}
}
getURL() {
var url = "client:ShowAlert?";
if (this.message) url += "message=" + escape(this.message) + "&";
if (this.buttonlabel1) url += "buttonlabel1=" + escape(this.buttonlabel1) + "&";
if (this.buttonaction1) url += "buttonaction1=" + escape(this.buttonaction1) + "&";
if (this.buttonlabel2) url += "buttonlabel2=" + escape(this.buttonlabel2) + "&";
if (this.buttonaction2) url += "buttonaction2=" + escape(this.buttonaction2) + "&";
if (this.image) url += "image=" + escape(this.image) + "&";
if (this.noback) url += "noback=true&";
return url.substring(0, url.length - 1);
}
}
module.exports.WTVShared = WTVShared;
module.exports.clientShowAlert = clientShowAlert;

View File

@@ -8,13 +8,17 @@ const https = require('https');
const strftime = require('strftime'); // used externally by service scripts const strftime = require('strftime'); // used externally by service scripts
const net = require('net'); const net = require('net');
const CryptoJS = require('crypto-js'); const CryptoJS = require('crypto-js');
const mime = require('mime-types');
const { crc16 } = require('easy-crc'); const { crc16 } = require('easy-crc');
const process = require('process'); const process = require('process');
var WTVSec = require('./WTVSec.js'); var WTVSec = require('./WTVSec.js');
var WTVLzpf = require('./WTVLzpf.js'); var WTVLzpf = require('./WTVLzpf.js');
var WTVClientCapabilities = require('./WTVClientCapabilities.js'); var WTVClientCapabilities = require('./WTVClientCapabilities.js');
var WTVClientSessionData = require('./WTVClientSessionData.js'); var WTVClientSessionData = require('./WTVClientSessionData.js');
var WTVMimeTypes = require("./WTVMimeTypes.js");
var { WTVShared, clientShowAlert } = require("./WTVShared.js");
const wtvshared = new WTVShared();
const wtvmime = new WTVMimeTypes();
process process
.on('SIGTERM', shutdown('SIGTERM')) .on('SIGTERM', shutdown('SIGTERM'))
@@ -70,10 +74,6 @@ function getServiceString(service, overrides = {}) {
} }
} }
function getFileExt(path) {
return path.reverse().split(".")[0].reverse();
}
function doErrorPage(code, data = null, pc_mode = false) { function doErrorPage(code, data = null, pc_mode = false) {
var headers = null; var headers = null;
switch (code) { switch (code) {
@@ -105,99 +105,6 @@ function doErrorPage(code, data = null, pc_mode = false) {
return new Array(headers, data); return new Array(headers, data);
} }
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":
wtv_mime_type = "audio/x-aif";
break;
case "aifc":
wtv_mime_type = "audio/x-aifc";
break;
case "aiff":
wtv_mime_type = "audio/x-aiff";
break;
case "ani":
wtv_mime_type = "x-wtv-animation";
break;
case "brom":
wtv_mime_type = "binary/x-wtv-bootrom";
break;
case "cdf":
wtv_mime_type = "application/netcdf";
break;
case "dat":
wtv_mime_type = "binary/cache-data";
break;
case "dl":
wtv_mime_type = "wtv/download-list";
break;
case "gsm":
wtv_mime_type = "audio/x-gsm";
break;
case "gz":
wtv_mime_type = "application/gzip";
break;
case "ini":
wtv_mime_type = "wtv/jack-configuration";
break;
case "mips-code":
wtv_mime_type = "code/x-wtv-code-mips";
break;
case "o":
wtv_mime_type = "binary/x-wtv-approm";
break;
case "ram":
wtv_mime_type = "audio/x-pn-realaudio";
break;
case "rom":
wtv_mime_type = "binary/x-wtv-flashblock";
break;
case "rsp":
wtv_mime_type = "wtv/jack-response";
break;
case "swa":
case "swf":
wtv_mime_type = "application/x-shockwave-flash";
break;
case "srf":
case "spl":
wtv_mime_type = "wtv/jack-data";
break;
case "ttf":
wtv_mime_type = "wtv/jack-fonts";
break;
case "tvch":
wtv_mime_type = "wtv/tv-channels";
break;
case "tvl":
wtv_mime_type = "wtv/tv-listings";
break;
case "tvsl":
wtv_mime_type = "wtv/tv-smartlinks";
break;
case "wad":
wtv_mime_type = "binary/doom-data";
break;
case "mp2":
case "hsb":
case "rmf":
case "s3m":
case "mod":
case "xm":
wtv_mime_type = "application/Music";
break;
}
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) { async function processPath(socket, service_vault_file_path, request_headers = new Array(), service_name) {
var headers, data = null; var headers, data = null;
var request_is_async = false; var request_is_async = false;
@@ -206,7 +113,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
try { try {
service_vaults.forEach(function (service_vault_dir) { service_vaults.forEach(function (service_vault_dir) {
if (service_vault_found) return; if (service_vault_found) return;
service_vault_file_path = makeSafePath(service_vault_dir, service_path); service_vault_file_path = wtvshared.makeSafePath(service_vault_dir, service_path);
// deny access to catchall file name directly // deny access to catchall file name directly
var service_path_split = service_path.split("/"); var service_path_split = service_path.split("/");
@@ -231,7 +138,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
service_vault_found = true; service_vault_found = true;
request_is_async = true; request_is_async = true;
if (!zquiet) console.log(" * Found " + service_vault_file_path + " to handle request (Direct File Mode) [Socket " + socket.id + "]"); if (!zquiet) console.log(" * Found " + service_vault_file_path + " to handle request (Direct File Mode) [Socket " + socket.id + "]");
var contypes = getConType(service_vault_file_path); var contypes = wtvmime.getContentType(service_vault_file_path);
headers = "200 OK\n" headers = "200 OK\n"
headers += "Content-Type: " + contypes[0] + "\n"; headers += "Content-Type: " + contypes[0] + "\n";
headers += "wtv-modern-content-type" + contypes[1]; headers += "wtv-modern-content-type" + contypes[1];
@@ -345,47 +252,6 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
} }
} }
function filterSSID(obj) {
if (minisrv_config.config.hide_ssid_in_logs === true) {
if (typeof (obj) == "string") {
if (obj.substr(0, 8) == "MSTVSIMU") {
return obj.substr(0, 10) + ('*').repeat(10) + obj.substr(20);
} else if (obj.substr(0, 5) == "1SEGA") {
return obj.substr(0, 6) + ('*').repeat(6) + obj.substr(13);
} else {
return obj.substr(0, 6) + ('*').repeat(9);
}
} else {
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") {
obj["wtv-client-serial-number"] = ssid.substr(0, 6) + ('*').repeat(6) + ssid.substr(13);
} else {
obj["wtv-client-serial-number"] = ssid.substr(0, 6) + ('*').repeat(9);
}
}
return obj;
}
} else {
return 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);
var targetPath = path.posix.normalize(target)
return base + path.sep + targetPath;
}
async function processURL(socket, request_headers) { async function processURL(socket, request_headers) {
var shortURL, headers, data = ""; var shortURL, headers, data = "";
request_headers.query = new Array(); request_headers.query = new Array();
@@ -469,7 +335,7 @@ async function processURL(socket, request_headers) {
var ssid = socket.ssid; var ssid = socket.ssid;
if (ssid == null) { if (ssid == null) {
// prevent possible injection attacks via SSID and filesystem SessionStore // prevent possible injection attacks via SSID and filesystem SessionStore
ssid = makeSafeSSID(request_headers["wtv-client-serial-number"]); ssid = wtvshared.makeSafeSSID(request_headers["wtv-client-serial-number"]);
if (ssid == "") ssid = null; if (ssid == "") ssid = null;
} }
@@ -481,14 +347,14 @@ async function processURL(socket, request_headers) {
reqverb = "Psuedo-encrypted " + reqverb; reqverb = "Psuedo-encrypted " + reqverb;
} }
if (ssid != null) { if (ssid != null) {
console.log(" * " + reqverb + " for " + request_headers.request_url + " from WebTV SSID " + (await filterSSID(ssid)), 'on', socket.id); console.log(" * " + reqverb + " for " + request_headers.request_url + " from WebTV SSID " + (await wtvshared.filterSSID(ssid)), 'on', socket.id);
} else { } else {
console.log(" * " + reqverb + " for " + request_headers.request_url, 'on', socket.id); console.log(" * " + reqverb + " for " + request_headers.request_url, 'on', socket.id);
} }
// assume webtv since there is a :/ in the GET // assume webtv since there is a :/ in the GET
var service_name = shortURL.split(':/')[0]; var service_name = shortURL.split(':/')[0];
var urlToPath = service_name + path.sep + shortURL.split(':/')[1]; var urlToPath = service_name + path.sep + shortURL.split(':/')[1];
if (zshowheaders) console.log(" * Incoming headers on socket ID", socket.id, (await filterSSID(request_headers))); if (zshowheaders) console.log(" * Incoming headers on socket ID", socket.id, (await wtvshared.filterSSID(request_headers)));
processPath(socket, urlToPath, request_headers, service_name); processPath(socket, urlToPath, request_headers, service_name);
} else if (shortURL.indexOf('http://') >= 0 || shortURL.indexOf('https://') >= 0) { } else if (shortURL.indexOf('http://') >= 0 || shortURL.indexOf('https://') >= 0) {
doHTTPProxy(socket, request_headers); doHTTPProxy(socket, request_headers);
@@ -505,7 +371,7 @@ async function processURL(socket, request_headers) {
async function doHTTPProxy(socket, request_headers) { async function doHTTPProxy(socket, request_headers) {
var request_type = (request_headers.request_url.substring(0, 5) == "https") ? "https" : "http"; var request_type = (request_headers.request_url.substring(0, 5) == "https") ? "https" : "http";
if (zshowheaders) console.log(request_type.toUpperCase() +" Proxy: Client Request Headers on socket ID", socket.id, (await filterSSID(request_headers))); if (zshowheaders) console.log(request_type.toUpperCase() +" Proxy: Client Request Headers on socket ID", socket.id, (await wtvshared.filterSSID(request_headers)));
switch (request_type) { switch (request_type) {
case "https": case "https":
var proxy_agent = https; var proxy_agent = https;
@@ -841,7 +707,7 @@ async function sendToClient(socket, headers_obj, data) {
} }
// header object to string // header object to string
if (zshowheaders) console.log(" * Outgoing headers on socket ID", socket.id, (await filterSSID(headers_obj))); if (zshowheaders) console.log(" * Outgoing headers on socket ID", socket.id, (await wtvshared.filterSSID(headers_obj)));
Object.keys(headers_obj).forEach(function (k) { Object.keys(headers_obj).forEach(function (k) {
if (k == "http_response") { if (k == "http_response") {
headers += headers_obj[k] + end_of_line; headers += headers_obj[k] + end_of_line;
@@ -930,11 +796,6 @@ function isUnencryptedString(string, verbose = false) {
return /^([A-Za-z0-9\+\/\=\-\.\,\ \"\;\:\?\&\r\n\(\)\%\<\>\_\~\*\@\#\\]{8,})$/.test(string); return /^([A-Za-z0-9\+\/\=\-\.\,\ \"\;\:\?\&\r\n\(\)\%\<\>\_\~\*\@\#\\]{8,})$/.test(string);
} }
function filterSSID(ssid) {
var WTVCSD = new WTVClientSessionData(null,minisrv_config.config.hide_ssid_in_logs);
return WTVCSD.filterSSID(ssid);
}
async function processRequest(socket, data_hex, skipSecure = false, encryptedRequest = false) { async function processRequest(socket, data_hex, skipSecure = false, encryptedRequest = false) {
// This function sucks and needs to be rewritten // This function sucks and needs to be rewritten
@@ -1002,7 +863,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
if (!headers) return; if (!headers) return;
if (headers["wtv-client-serial-number"] != null && socket.ssid == null) { if (headers["wtv-client-serial-number"] != null && socket.ssid == null) {
socket.ssid = makeSafeSSID(headers["wtv-client-serial-number"]); socket.ssid = wtvshared.makeSafeSSID(headers["wtv-client-serial-number"]);
if (socket.ssid != null) { if (socket.ssid != null) {
if (!ssid_sessions[socket.ssid]) { if (!ssid_sessions[socket.ssid]) {
ssid_sessions[socket.ssid] = new WTVClientSessionData(socket.ssid,minisrv_config.config.hide_ssid_in_logs); ssid_sessions[socket.ssid] = new WTVClientSessionData(socket.ssid,minisrv_config.config.hide_ssid_in_logs);
@@ -1039,8 +900,8 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
}; };
var rejectSSIDConnection = function (ssid, blacklist) { var rejectSSIDConnection = function (ssid, blacklist) {
if (blacklist) console.log(" * Request from SSID", filterSSID(ssid), "(" + socket.remoteAddr + "), but that SSID is in the blacklist, rejecting."); if (blacklist) console.log(" * Request from SSID", wtvshared.filterSSID(ssid), "(" + socket.remoteAddr + "), but that SSID is in the blacklist, rejecting.");
else console.log(" * Request from SSID", filterSSID(socket.ssid), "(" + socket.remoteAddress + "), but that SSID is not in the whitelist, rejecting."); else console.log(" * Request from SSID", wtvshared.filterSSID(socket.ssid), "(" + socket.remoteAddress + "), but that SSID is not in the whitelist, rejecting.");
var errpage = doErrorPage(401, "Access to this service is denied."); var errpage = doErrorPage(401, "Access to this service is denied.");
headers = errpage[0]; headers = errpage[0];
@@ -1072,7 +933,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
} else { } else {
rejectSSIDConnection(socket.ssid, blacklist); rejectSSIDConnection(socket.ssid, blacklist);
} }
if (ssid_access_list_ip_override && zdebug) console.log(" * Request from disallowed SSID", filterSSID(ssid), "was allowed due to IP address whitelist"); if (ssid_access_list_ip_override && zdebug) console.log(" * Request from disallowed SSID", wtvshared.filterSSID(ssid), "was allowed due to IP address whitelist");
} }
// process whitelist first // process whitelist first
@@ -1252,14 +1113,14 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
if (socket_sessions[socket.id].post_data.length == (socket_sessions[socket.id].post_data_length * 2)) { if (socket_sessions[socket.id].post_data.length == (socket_sessions[socket.id].post_data_length * 2)) {
// got all expected data // got all expected data
if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data; if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data;
console.log(" * Incoming", post_string, "request on", socket.id, "from", filterSSID(socket.ssid), "to", headers['request_url'], "(got all expected", socket_sessions[socket.id].post_data_length, "bytes of data from client already)"); console.log(" * Incoming", post_string, "request on", socket.id, "from", wtvshared.filterSSID(socket.ssid), "to", headers['request_url'], "(got all expected", socket_sessions[socket.id].post_data_length, "bytes of data from client already)");
headers.post_data = CryptoJS.enc.Hex.parse(socket_sessions[socket.id].post_data); headers.post_data = CryptoJS.enc.Hex.parse(socket_sessions[socket.id].post_data);
if (socket_sessions[socket.id].headers) delete socket_sessions[socket.id].headers; if (socket_sessions[socket.id].headers) delete socket_sessions[socket.id].headers;
processURL(socket, headers); processURL(socket, headers);
} else { } else {
// expecting more data (see below) // expecting more data (see below)
socket_sessions[socket.id].expecting_post_data = true; socket_sessions[socket.id].expecting_post_data = true;
console.log(" * Incoming", post_string, "request on", socket.id, "from", filterSSID(socket.ssid), "to", headers['request_url'], "(expecting", socket_sessions[socket.id].post_data_length, "bytes of data from client...)"); console.log(" * Incoming", post_string, "request on", socket.id, "from", wtvshared.filterSSID(socket.ssid), "to", headers['request_url'], "(expecting", socket_sessions[socket.id].post_data_length, "bytes of data from client...)");
} }
if (socket_sessions[socket.id].post_data.length > (socket_sessions[socket.id].post_data_length * 2)) { if (socket_sessions[socket.id].post_data.length > (socket_sessions[socket.id].post_data_length * 2)) {
// got too much data ? ... should not ever reach this code // got too much data ? ... should not ever reach this code
@@ -1313,7 +1174,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
if (minisrv_config.config.post_percentages.includes(postPercent)) { if (minisrv_config.config.post_percentages.includes(postPercent)) {
if (!socket_sessions[socket.id].post_data_percents_shown) socket_sessions[socket.id].post_data_percents_shown = new Array(); if (!socket_sessions[socket.id].post_data_percents_shown) socket_sessions[socket.id].post_data_percents_shown = new Array();
if (!socket_sessions[socket.id].post_data_percents_shown[postPercent]) { if (!socket_sessions[socket.id].post_data_percents_shown[postPercent]) {
console.log(" * Received", postPercent, "% of", socket_sessions[socket.id].post_data_length, "bytes on", socket.id, "from", filterSSID(socket.ssid)); console.log(" * Received", postPercent, "% of", socket_sessions[socket.id].post_data_length, "bytes on", socket.id, "from", wtvshared.filterSSID(socket.ssid));
socket_sessions[socket.id].post_data_percents_shown[postPercent] = true; socket_sessions[socket.id].post_data_percents_shown[postPercent] = true;
} }
if (postPercent == 100) delete socket_sessions[socket.id].post_data_percents_shown; if (postPercent == 100) delete socket_sessions[socket.id].post_data_percents_shown;
@@ -1433,7 +1294,7 @@ async function cleanupSocket(socket) {
// set timeout to check // set timeout to check
ssid_sessions[socket.ssid].data_store.socket_check = setTimeout(function (ssid) { ssid_sessions[socket.ssid].data_store.socket_check = setTimeout(function (ssid) {
if (ssid_sessions[ssid].currentConnections() === 0) { 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"); if (!zquiet) console.log(" * WebTV SSID", wtvshared.filterSSID(ssid), " has not been seen in", (timeout / 1000), "seconds, cleaning up session data for this SSID");
delete ssid_sessions[ssid]; delete ssid_sessions[ssid];
} }
}, timeout, socket.ssid); }, timeout, socket.ssid);