- numerous bug fixes - improve session retention - use wtv-head-waiter:/relogin for boot url - viewer seems to retain only wtv-* and wtv-head-waiter, so lets try to be closer to protocol and boot with a wtv-head-waiter address instead of wtv-1800 - we still handle via wtv-1800 but we accept wtv-head-waiter:/relogin and send the client on its way to the relogin path - update wtv-home:/home - remove spacing in favor of right alignment - add compression status - guest mode session store update - allow calls to saveSessionData() but do not actually write if user is guest - saveSessionData() returns true even if guest, because false is meant to define an error - You can also use SaveIfRegistered(), this will return false on both saveSessionData() errors AND guest mode; - if you want to block guests, check for isRegistered() and block the request if it is false - otherwise this update will allow all tools (including any logins) to work with guest mode, but the stored SessionData will not be persistently saved, and lost when the cleanup timeout hits (default 3 min), or the server is restarted. - more accurately mimic WTVP by accepting URLs without / - use service-style cookie links on tricks - add catchall system & http pc server - define a catchall name to run globally or per service - catchall must be javascript, but not necessarily a .js file - catchall can request async mode - catchall will catch any non-existing requests under its directory - see wtv-flashrom:/content/content-serve.js as an example, which will catch wtv-flashrom:/content/ URLs. - http pc: sends HTTP/1.0 to PC clients - can be disabled with `pc_server_hidden_service_enabled`: false - can change servicevault path by changing string of pc_server_hidden_service - get.js in default PC service vault to get any WTV Url on the service - flashrom system updates - fix bugs - more WNI-like flow path - make scripts use `service_name` variable so that they should work in a renamed service (eg not wtv-flashrom, untested) - rewrite wtv-disk system - move wtv-update to wtv-disk - allow accessing wtv-disk:/sync?group=&diskmap= - rewrite Download List generation to be more proper - only send files if diskmap has changed - allow force redownload with &force=true
341 lines
12 KiB
JavaScript
341 lines
12 KiB
JavaScript
const { lib } = require('crypto-js');
|
|
|
|
class WTVClientSessionData {
|
|
|
|
fs = require('fs');
|
|
path = require('path');
|
|
ssid = null;
|
|
data_store = null;
|
|
session_store = null;
|
|
login_security = null;
|
|
capabilities = null;
|
|
session_storage = "";
|
|
hide_ssid_in_logs = true;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
this.data_store = new Array();
|
|
this.session_store = {};
|
|
}
|
|
|
|
getUTCTime(offset = 0) {
|
|
return new Date((new Date).getTime() + offset).toUTCString();
|
|
}
|
|
|
|
countCookies() {
|
|
return Object.keys(this.session_store.cookies).length || 0;
|
|
}
|
|
|
|
resetCookies() {
|
|
this.session_store.cookies = {};
|
|
// webtv likes to have at least one cookie in the list, set a dummy cookie for zefie's site expiring in 1 year.
|
|
this.addCookie("wtv.zefie.com", "/", this.getUTCTime(365 * 86400000), "cookie_type=chocolatechip");
|
|
}
|
|
|
|
addCookie(domain, path = null, expires = null, data = null) {
|
|
if (!this.checkCookies()) this.resetCookies();
|
|
if (!domain) return false;
|
|
else if (typeof (domain) == 'object') {
|
|
// accept array as first argument
|
|
if (domain.domain && domain.path && domain.expires && domain.data) var cookie_data = domain;
|
|
else return false;
|
|
} else {
|
|
if (path && expires && data) {
|
|
var cookie_data = new Array();
|
|
cookie_data['cookie'] = unescape(data);
|
|
cookie_data['expires'] = unescape(expires);
|
|
cookie_data['path'] = unescape(path);
|
|
cookie_data['domain'] = unescape(domain);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
var self = this;
|
|
var cookie_index = -1;
|
|
// see if we have a cookie for this domain/path
|
|
Object.keys(this.session_store.cookies).forEach(function (k) {
|
|
if (cookie_index >= 0) return;
|
|
if (domain == self.session_store.cookies[k].domain && path == self.session_store.cookies[k].path) cookie_index = k;
|
|
});
|
|
// otherwise add a new one
|
|
if (cookie_index == -1) cookie_index = this.countCookies();
|
|
|
|
this.session_store.cookies[cookie_index] = Object.assign({}, cookie_data);
|
|
|
|
return true;
|
|
}
|
|
|
|
getCookie(domain, path) {
|
|
if (!this.checkCookies()) this.resetCookies();
|
|
var self = this;
|
|
var result = false;
|
|
Object.keys(this.session_store['cookies']).forEach(function (k) {
|
|
if (result != false) return;
|
|
if (self.session_store['cookies'][k].domain == domain &&
|
|
self.session_store['cookies'][k].path == path) {
|
|
|
|
var current_epoch_utc = Date.parse((new Date()).toUTCString());
|
|
var cookie_expires_epoch_utc = Date.parse(new Date(Date.parse(self.session_store['cookies'][k].expires)).toUTCString());
|
|
if (cookie_expires_epoch_utc <= current_epoch_utc) self.deleteCookie(self.session_store['cookies'][k]);
|
|
else result = self.session_store['cookies'][k];
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
getCookieString(domain, path) {
|
|
var cookie_data = this.getCookie(domain, path);
|
|
/*
|
|
var outstring = "";
|
|
Object.keys(cookie_data).forEach(function (k) {
|
|
outstring += k + "=" + escape(cookie_data[k]) + "&";
|
|
});
|
|
return outstring.substring(0, outstring.length - 1);
|
|
*/
|
|
return cookie_data.cookie;
|
|
}
|
|
|
|
deleteCookie(domain, path = null) {
|
|
var result = false;
|
|
if (!this.checkCookies()) {
|
|
this.resetCookies();
|
|
return true;
|
|
}
|
|
if (!domain) return false;
|
|
else if (typeof (domain) == 'object') {
|
|
// accept array as first argument
|
|
if (domain.domain && domain.path) {
|
|
path = domain.path;
|
|
domain = domain.domain;
|
|
}
|
|
} else if (!path) {
|
|
return false;
|
|
}
|
|
|
|
var self = this;
|
|
Object.keys(this.session_store['cookies']).forEach(function (k) {
|
|
if (self.session_store['cookies'][k].domain == domain && self.session_store['cookies'][k].path == path) {
|
|
delete self.session_store['cookies'][k];
|
|
self.storeSessionData();
|
|
result = true;
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
checkCookies() {
|
|
if (!this.session_store.cookies) return false;
|
|
else if (this.session_store.cookies == []) return false;
|
|
return true;
|
|
}
|
|
|
|
listCookies() {
|
|
if (!this.checkCookies()) this.resetCookies();
|
|
var outstring = "";
|
|
var self = this;
|
|
Object.keys(this.session_store.cookies).forEach(function (k) {
|
|
outstring += self.session_store.cookies[k].domain + "\0" + self.session_store.cookies[k].path + "\0";
|
|
});
|
|
return outstring;
|
|
}
|
|
|
|
loadSessionData(raw_data = false) {
|
|
try {
|
|
if (this.fs.lstatSync(this.session_storage + this.path.sep + this.ssid + ".json")) {
|
|
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;
|
|
}
|
|
} 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 loading session data for", this.filterSSID(this.ssid), e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
saveSessionData() {
|
|
if (this.isRegistered()) {
|
|
// load data from disk and merge new data
|
|
var temp_store = this.session_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 {
|
|
// 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);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
retrieveSessionData() {
|
|
// alias
|
|
return this.loadSessionData();
|
|
}
|
|
|
|
storeSessionData() {
|
|
// alias
|
|
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")) {
|
|
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
|
|
console.error(" # Error deleting session data for", this.filterSSID(this.ssid), e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
hasCap(cap) {
|
|
if (this.capabilities) {
|
|
return this.capabilities[cap] || false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
getMaxUsernameLength() {
|
|
if (parseInt(this.data_store['wtv-system-version'] < 4000)) {
|
|
// older builds may crash with nicknames longer than 16 chars.
|
|
// actual build where support started is yet unknown
|
|
return 16;
|
|
} else {
|
|
// newer builds supported up to 32 chars, I think
|
|
return 32;
|
|
}
|
|
}
|
|
|
|
setIRCNick(nick) {
|
|
// strip out unsupported chars
|
|
nick = nick.replace(/[^a-zA-Z0-9\-\_\`\^]/g, "");
|
|
|
|
// limit nick length based on build support
|
|
nick = nick.substring(0, this.getMaxUsernameLength());
|
|
|
|
// returns headers to send to client, while storing the new data in our session data.
|
|
this.data_store['wtv-user-name'] = nick;
|
|
this.data_store['wtv-irc-nick'] = nick;
|
|
this.session_store.subscriber_irc_nick = nick;
|
|
return "wtv-irc-nick: " + nick + "\nwtv-user-nick: " + nick;
|
|
}
|
|
|
|
isMiniBrowser() {
|
|
if (this.data_store['wtv-need-upgrade'] || this.data_store['wtv-used-8675309']) return true;
|
|
return false;
|
|
}
|
|
|
|
currentConnections() {
|
|
if (this.data_store) {
|
|
if (this.data_store.sockets) {
|
|
return this.data_store.sockets.size;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
getSessionData(key = null) {
|
|
if (typeof (this.data_store) === 'session_store') return null;
|
|
else if (key === null) return this.data_store;
|
|
else if (this.session_store[key]) return this.session_store[key];
|
|
else return null;
|
|
}
|
|
|
|
setSessionData(key, value) {
|
|
if (key === null) throw ("ClientSessionData.set(): invalid key provided");
|
|
if (typeof (this.session_store) === 'undefined') this.session_store = new Array();
|
|
this.session_store[key] = value;
|
|
}
|
|
|
|
deleteSessionData(key) {
|
|
if (key === null) throw ("ClientSessionData.delete(): invalid key provided");
|
|
delete this.session_store[key];
|
|
}
|
|
|
|
|
|
get(key = null) {
|
|
if (typeof (this.data_store) === 'undefined') return null;
|
|
else if (key === null) return this.data_store;
|
|
else if (this.data_store[key]) return this.data_store[key];
|
|
else return null;
|
|
}
|
|
|
|
set(key, value) {
|
|
if (key === null) throw ("ClientSessionData.set(): invalid key provided");
|
|
if (typeof (this.data_store) === 'undefined') this.data_store = new Array();
|
|
this.data_store[key] = value;
|
|
}
|
|
|
|
delete(key) {
|
|
if (key === null) throw ("ClientSessionData.delete(): invalid key provided");
|
|
delete this.data_store[key];
|
|
}
|
|
}
|
|
|
|
|
|
module.exports = WTVClientSessionData; |