- code cleanup and refactoring
- implement max post size on pc services
- add box account transfer system
- create directory indexer for pc services
- fix catchall for pc services
- pc services not respects `disabled: true` if sharing the same port
- new wtv-tricks:/info from WebTV Redialed
- Added missing Pagebuilder themes
- Fixed various PageBuilder bugs, pages should work correctly (republish your page if needed)
- various security and bug fixes
This commit is contained in:
zefie
2025-02-12 14:18:32 -05:00
parent c74e2fb71b
commit 81729b66da
55 changed files with 5446 additions and 84056 deletions

View File

@@ -274,7 +274,7 @@ ${thisblock.title}
block += "<td>"
block += `<CENTER>
<IMG SRC="wtv-author:/${btoa(thisblock.photo)}">
<IMG SRC="wtv-author:/${atob(thisblock.photo)}">
</CENTER>
</TD>
</TR>
@@ -470,8 +470,8 @@ this.fs.writeFile(destDir + this.wtvclient.session_store.subscriber_username + '
break;
case "clipart":
this.fs.mkdirSync(destDir + this.wtvclient.session_store.subscriber_username + '/' + pagedata.publishname + "/" + btoa(thisblock.photo).substr(0, btoa(thisblock.photo).lastIndexOf("/")), { recursive: true })
this.fs.copyFile('includes/ServiceVault/wtv-author/' + btoa(thisblock.photo), destDir + this.wtvclient.session_store.subscriber_username + '/' + pagedata.publishname + "/" + btoa(thisblock.photo), (err) => {
this.fs.mkdirSync(destDir + this.wtvclient.session_store.subscriber_username + '/' + pagedata.publishname + "/" + atob(thisblock.photo).substr(0, atob(thisblock.photo).lastIndexOf("/")), { recursive: true })
this.fs.copyFile('includes/ServiceVault/wtv-author/' + atob(thisblock.photo), destDir + this.wtvclient.session_store.subscriber_username + '/' + pagedata.publishname + "/" + atob(thisblock.photo), (err) => {
if (err) throw err;
});
block = `<p><TABLE nocolor width=100%>`
@@ -486,7 +486,7 @@ ${thisblock.title}
</TD>
</TR>`
block += `<TR><td><CENTER>
<IMG SRC="${btoa(thisblock.photo)}">
<IMG SRC="${atob(thisblock.photo)}">
</CENTER>
</TD>
</TR>

View File

@@ -52,6 +52,7 @@ class WTVClientSessionData {
this.loginWhitelist = Object.assign([], this.lockdownWhitelist); // clone lockdown whitelist into login whitelist
this.loginWhitelist.push("wtv-head-waiter:/choose-user");
this.loginWhitelist.push("wtv-head-waiter:/password");
this.loginWhitelist.push("wtv-head-waiter:/confirm-transfer");
}
assignMailStore() {
@@ -170,8 +171,7 @@ class WTVClientSessionData {
var master_directory = this.getUserStoreDirectory(true);
var account_data = [];
var self = this;
this.debug(this.ssid)
var self = this;
this.fs.readdirSync(master_directory).forEach(f => {
if (self.fs.lstatSync(master_directory + self.path.sep + f).isDirectory()) {
if (f.substr(0, 4) == "user") {
@@ -234,6 +234,67 @@ class WTVClientSessionData {
return false;
}
setPendingTransfer(ssid) {
var pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json";
var ssidobj = { "ssid": ssid, "type": "source" };
this.fs.writeFileSync(pending_file, JSON.stringify(ssidobj));
var new_userstore = this.getAccountStoreDirectory() + this.path.sep + ssidobj.ssid;
if (!this.fs.existsSync(new_userstore)) this.fs.mkdirSync(new_userstore);
var dest_pending_file = new_userstore + this.path.sep + "pending_transfer.json";
var ssidobj = { "ssid": this.ssid, "type": "target" };
this.fs.writeFileSync(dest_pending_file, JSON.stringify(ssidobj));
}
cancelPendingTransfer() {
var pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json";
if (this.fs.existsSync(pending_file)) {
var file = this.fs.readFileSync(pending_file)
var ssidobj = JSON.parse(file);
var new_userstore = this.getAccountStoreDirectory() + this.path.sep + ssidobj.ssid;
var dest_pending_file = new_userstore + this.path.sep + "pending_transfer.json";
if (this.fs.existsSync(dest_pending_file)) this.fs.unlinkSync(dest_pending_file);
this.fs.unlinkSync(pending_file);
if (this.fs.existsSync(new_userstore)) this.fs.rmdirSync(new_userstore);
return ssidobj.ssid
}
return null;
}
finalizePendingTransfer() {
var pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json";
var file = this.fs.readFileSync(pending_file)
var ssidobj = JSON.parse(file);
if (ssidobj.type != "target") return false; // Only allow completion from target
var source_ssid = ssidobj.ssid
var old_account = this.getAccountStoreDirectory() + this.path.sep + source_ssid
var new_account = this.getUserStoreDirectory(true);
this.fs.cpSync(old_account, new_account, {
filter: (source, _destination) => {
return source != "pending_transfer.json";
},
recursive: true
});
this.fs.rmSync(old_account, { recursive: true })
this.fs.rmSync(pending_file);
return true;
}
hasPendingTransfer(dtype = null) {
var pending_file = this.getUserStoreDirectory(true) + this.path.sep + "pending_transfer.json";
if (this.fs.existsSync(pending_file)) {
var ssidobj = JSON.parse(this.fs.readFileSync(pending_file));
console.log(ssidobj)
if (dtype) {
(ssidobj.type == dtype) ? ssidobj.ssid : false;
}
else {
return ssidobj;
}
} else {
return false
}
}
/**
* Store a file in the user's file store
* @param {string} path Relative path to User's file store
@@ -631,14 +692,14 @@ class WTVClientSessionData {
}
setSessionData(key, value) {
if (key === null) throw ("ClientSessionData.set(): invalid key provided");
if (key === null) throw ("ClientSessionData.setSessionData(): invalid key provided");
if (typeof (this.session_store) === 'undefined') this.session_store = new Array();
this.session_store[key] = value;
this.SaveIfRegistered();
}
deleteSessionData(key) {
if (key === null) throw ("ClientSessionData.delete(): invalid key provided");
if (key === null) throw ("ClientSessionData.deleteSessionData(): invalid key provided");
delete this.session_store[key];
this.SaveIfRegistered(true);
}

View File

@@ -27,6 +27,19 @@ class WTVRegister {
return regex.test(username);
}
checkSSIDAvailable(ssid) {
var directory = (directory) ? directory : this.session_store_dir + this.path.sep + "accounts";
var available = true;
if (this.fs.existsSync(directory)) {
this.fs.readdirSync(directory).forEach(file => {
if (file.toLowerCase() == ssid.toLowerCase()) {
available = false;
return false;
}
});
};
return available;
}
checkUsernameAvailable(username, directory = null) {
var self = this;

View File

@@ -33,8 +33,7 @@ class WTVShared {
String.prototype.reverse = function () {
var splitString = this.split("");
var reverseArray = splitString.reverse();
var joinArray = reverseArray.join("");
return joinArray;
return reverseArray.join("");
}
}
if (!String.prototype.hexEncode) {
@@ -80,11 +79,10 @@ class WTVShared {
parseConfigVars(s) {
if (s.indexOf("%ServiceDeps%") >= 0) {
if (s.indexOf("%ServiceDeps%") >= 0)
return this.getServiceDep(s.replace("%ServiceDeps%", ""), true);
} else {
else
return s;
}
}
/**
@@ -119,12 +117,14 @@ class WTVShared {
return new RegExp(src);
} else if (src instanceof Date) {
return new Date(src.getTime());
} else if (Array.isArray(src)) {
return src.map(item => this.cloneObj(item));
} else if (typeof src === 'object' && src !== null) {
const clone = {};
Object.keys(src).forEach(k => {
clone[k] = this.cloneObj(src[k]);
var clone = null;
if (Array.isArray(src)) clone = [];
else clone = {};
var self = this;
Object.keys(src).forEach((k) => {
clone[k] = self.cloneObj(src[k]);
});
return clone;
}
@@ -349,7 +349,7 @@ class WTVShared {
utf8Decode(utf8String) {
if (typeof utf8String !== 'string') {
throw new TypeError('parameter <EFBFBD>utf8String<EFBFBD> is not a string');
throw new TypeError("parameter 'utf8String' is not a string");
}
const textDecoder = new TextDecoder('utf-8');
const bytes = new Uint8Array(utf8String.split('').map(c => c.charCodeAt(0)));
@@ -617,7 +617,7 @@ class WTVShared {
* @returns {string} Random string
*/
generatePassword(len, simple = false) {
return this.generateString(len, (simple) ? null : '!@#$%&()[]-_+=?.');
return this.generateString(len, (simple) ? '' : '!@#$%&()[]-_+=?.');
}
/**
@@ -749,7 +749,7 @@ class WTVShared {
*/
filterSSID(obj) {
var new_obj = false;
if (this.minisrv_config && this.minisrv_config.config.hide_ssid_in_logs) {
if (this.minisrv_config.config.hide_ssid_in_logs) {
if (typeof obj === "string") {
return this.censorSSID(obj);
} else if (typeof obj === "object" && obj !== null) {
@@ -765,23 +765,18 @@ class WTVShared {
filterRequestLog(obj) {
if (this.minisrv_config.config.filter_passwords_in_logs && obj.query) {
const passwordRegex = /(^pass$|passw(or)?d)/i;
let newobj = this.cloneObj(obj); // Clone the object once at the beginning
const passwordRegex = /(^pass$|passw(or)?d)/i;
var newobj = this.cloneObj(obj); // Clone the object once at the beginning
if (newobj.query) {
Object.keys(newobj.query).forEach((k) => {
if (passwordRegex.test(k)) {
newobj.query[k] = '*'.repeat(newobj.query[k].length);
}
});
}
delete newobj.raw_headers;
return newobj;
if (newobj.query) {
Object.keys(newobj.query).forEach((k) => {
if (passwordRegex.test(k)) {
newobj.query[k] = '*'.repeat(newobj.query[k].length);
}
});
}
return obj;
delete newobj.raw_headers;
return newobj;
}
@@ -974,6 +969,11 @@ class WTVShared {
}
}
/**
* Creates a 302 redirect and returns the headers/data
* @param {any} url
* @returns [headers, data]
*/
doRedirect(url) {
var headers = []
headers['Status'] = "302 Moved";
@@ -984,14 +984,14 @@ class WTVShared {
}
/**
* Creates an error message and sends it to the client
* Creates an error message and returns the headers/data
* @param {number} code HTTP Error Code
* @param {string} data Optinal Custom Error Message
* @param {string} details Optional extra error information
* @param {boolean} pc_mode If true, sends response formatted for PCs instead of WebTV
* @param {boolean} wtv_reset if true, tells the WebTV box to reset the service list and reconnect
*/
doErrorPage(code, data = null, details = null, pc_mode = false, wtv_reset = false) {
doErrorPage(code, data = null, details = null, pc_mode = false, wtv_reset = false) {
const minisrv_config = this.minisrv_config;
const errorMessage = minisrv_config.config.errorMessages[code] || "";
const message = data || errorMessage.replace(/\$\{(\w+)\}/g, (match, p1) => minisrv_config.config[p1] || '');
@@ -1000,7 +1000,7 @@ class WTVShared {
data += "<br>Details:<br>" + details;
}
let headers = `${code} ${message}\n`;
let headers = `Status: ${(pc_mode) ? 'HTTP/1.1' : ''} ${code} ${message}\n`;
headers += "Content-Type: text/html\n";
if (wtv_reset && !pc_mode) {
@@ -1075,12 +1075,16 @@ class WTVShared {
return path;
}
unpackCompressedB64(data) {
var data_buf = (typeof data === 'object') ? Buffer.from(data.toString('ascii'), 'base64') : Buffer.from(data, 'base64');
return this.zlib.inflateSync(data_buf, { finishFlush: this.zlib.Z_SYNC_FLUSH }).toString('ascii');
}
/**
* Compresses data and converts it to a base64 string
* @param {any} data
* @returns {string} base64 string
*/
packCompressedB64(data) {
return this.zlib.deflateSync(data, { 'level': 9 }).toString('base64');
}
@@ -1176,6 +1180,12 @@ class WTVShared {
return result;
}
/**
* Find a key in an object regardless of its case
* @param {string} key Key to find
* @param {obj} obj Object to search
* @returns {string|null} The found key or null if not found
*/
getCaseInsensitiveKey(key, obj) {
const foundKey = Object.keys(obj).find(k => k.toLowerCase() === key.toLowerCase());
return foundKey || null;
@@ -1191,6 +1201,17 @@ class clientShowAlert {
noback = null;
image = null;
/**
* User-friendly client:showalert generation
* @param {string} image Image URL
* @param {string} message Alert Message (HTML Allowed)
* @param {string} buttonlabel1 Button 1 Label
* @param {string} buttonaction1 Button 1 Action
* @param {string} buttonlabel2 Button 2 Label
* @param {string} buttonaction2 Button 2 Action
* @param {string} noback If true, disables the back button
* @param {string} sound Sound to play
*/
constructor(image = null, message = null, buttonlabel1 = null, buttonaction1 = null, buttonlabel2 = null, buttonaction2 = null, noback = null, sound = null) {
this.message = message;
this.buttonlabel1 = buttonlabel1;
@@ -1212,6 +1233,10 @@ class clientShowAlert {
}
}
/**
* Get the client:showalert URL from the helper class
* @returns {string} client:showalert URL
*/
getURL() {
var url = "client:ShowAlert?";
if (this.message) url += "message=" + escape(this.message) + "&";