409 lines
14 KiB
JavaScript
409 lines
14 KiB
JavaScript
/**
|
|
* Shared functions across all classes and apps
|
|
*/
|
|
|
|
|
|
class WTVShared {
|
|
|
|
path = require('path');
|
|
fs = require('fs');
|
|
minisrv_config = [];
|
|
|
|
constructor(minisrv_config) {
|
|
if (minisrv_config == null) this.minisrv_config = this.readMiniSrvConfig();
|
|
else this.minisrv_config = minisrv_config;
|
|
|
|
if (!String.prototype.reverse) {
|
|
String.prototype.reverse = function () {
|
|
var splitString = this.split("");
|
|
var reverseArray = splitString.reverse();
|
|
var joinArray = reverseArray.join("");
|
|
return joinArray;
|
|
}
|
|
}
|
|
}
|
|
|
|
isASCII(str) {
|
|
for (var i = 0, strLen = str.length; i < strLen; ++i) {
|
|
if (str.charCodeAt(i) > 127) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
isHTML(str) {
|
|
return /<[a-z][\s\S]*>/i.test(str);
|
|
}
|
|
|
|
decodeBufferText(buf) {
|
|
var out = "";
|
|
for (var i = 0; i < buf.data.length; i++) {
|
|
console.log(buf.data[i]);
|
|
out += String.fromCharCode(buf.data[i]);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
returnAbsolutePath(check_path) {
|
|
if (check_path.substring(0, 1) != this.path.sep && check_path.substring(1, 1) != ":") {
|
|
// non-absolute path, so use current directory as base
|
|
check_path = (__dirname + this.path.sep + check_path);
|
|
} else {
|
|
// already absolute path
|
|
}
|
|
return check_path;
|
|
}
|
|
|
|
isMiniBrowser(ssid_session) {
|
|
return (ssid_session.get("wtv-need-upgrade") || ssid_session.get("wtv-used-8675309")) ? true : false;
|
|
}
|
|
|
|
isOldBuild(ssid_session) {
|
|
return (this.isMiniBrowser(ssid_session) || parseInt(ssid_session.get("wtv-system-version")) < 3500) ? true : false;
|
|
}
|
|
|
|
readMiniSrvConfig(user_config = true, notices = true) {
|
|
if (notices) console.log(" *** Reading global configuration...");
|
|
try {
|
|
var minisrv_config = JSON.parse(this.fs.readFileSync(__dirname + this.path.sep + "config.json"));
|
|
} catch (e) {
|
|
throw ("ERROR: Could not read config.json", e);
|
|
}
|
|
|
|
var integrateConfig = function(main, user) {
|
|
Object.keys(user).forEach(function (k) {
|
|
if (typeof (user[k]) == 'object' && user[k] != null) {
|
|
// new entry
|
|
if (!main[k]) main[k] = new Array();
|
|
// go down the rabbit hole
|
|
main[k] = integrateConfig(main[k], user[k]);
|
|
} else {
|
|
// update main config
|
|
main[k] = user[k];
|
|
}
|
|
});
|
|
return main;
|
|
}
|
|
|
|
if (user_config) {
|
|
try {
|
|
if (this.fs.lstatSync(__dirname + "/user_config.json")) {
|
|
if (notices) console.log(" *** Reading user configuration...");
|
|
try {
|
|
var minisrv_user_config = JSON.parse(this.fs.readFileSync(__dirname + this.path.sep + "user_config.json"));
|
|
} catch (e) {
|
|
console.error("ERROR: Could not read user_config.json", e);
|
|
var throw_me = true;
|
|
}
|
|
// file exists and we read and parsed it, but the variable is undefined
|
|
// Likely a syntax parser error that did not trip the exception check above
|
|
try {
|
|
minisrv_config = integrateConfig(minisrv_config, minisrv_user_config)
|
|
} catch (e) {
|
|
console.error("ERROR: Could not read user_config.json", e);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (minisrv_config.config.debug_flags) {
|
|
if (minisrv_config.config.debug_flags.debug) console.error(" * Notice: Could not find user configuration (user_config.json). Using default configuration.");
|
|
}
|
|
}
|
|
}
|
|
|
|
return minisrv_config;
|
|
}
|
|
|
|
getMiniSrvConfig() {
|
|
return this.minisrv_config;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the Last-Modified date in Unix Timestamp format
|
|
* @param {string} file Path to a file
|
|
*/
|
|
getFileLastModified(file) {
|
|
var stats = this.fs.lstatSync(file);
|
|
if (stats) return new Date(stats.mtimeMs);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the Last-Modified date in a RFC7231 compliant UTC Date String
|
|
* @param {string} file Path to a file
|
|
*/
|
|
getFileLastModifiedUTCString(file) {
|
|
return this.getFileLastModified(file).toUTCString();
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
return new Date((new Date).getTime() + offset).toUTCString();
|
|
}
|
|
|
|
urlEncodeBytes = (buf) => {
|
|
let encoded = ''
|
|
for (let i = 0; i < buf.length; i++) {
|
|
const charBuf = Buffer.from('00', 'hex')
|
|
charBuf.writeUInt8(buf[i])
|
|
const char = charBuf.toString()
|
|
// if the character is safe, then just print it, otherwise encode
|
|
if (isUrlSafe(char)) {
|
|
encoded += char
|
|
} else {
|
|
encoded += `%${charBuf.toString('hex').toUpperCase()}`
|
|
}
|
|
}
|
|
return encoded
|
|
}
|
|
|
|
urlDecodeBytes = (encoded) => {
|
|
let decoded = Buffer.from('')
|
|
for (let i = 0; i < encoded.length; i++) {
|
|
if (encoded[i] === '%') {
|
|
const charBuf = Buffer.from(`${encoded[i + 1]}${encoded[i + 2]}`, 'hex')
|
|
decoded = Buffer.concat([decoded, charBuf])
|
|
i += 2
|
|
} else {
|
|
const charBuf = Buffer.from(encoded[i])
|
|
decoded = Buffer.concat([decoded, charBuf])
|
|
}
|
|
|
|
}
|
|
return decoded
|
|
}
|
|
|
|
/**
|
|
* Returns a censored SSID
|
|
* @param {string|Array} obj SSID String or Headers Object
|
|
*/
|
|
filterSSID(obj) {
|
|
var outobj = null;
|
|
if (this.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 (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;
|
|
}
|
|
}
|
|
|
|
decodePostData(obj) {
|
|
if (obj.post_data) {
|
|
obj.post_data = obj.post_data.toString();
|
|
}
|
|
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 this.fixPathSlashes(path);
|
|
}
|
|
|
|
/**
|
|
* Returns a percentage
|
|
* @param {number} partialValue
|
|
* @param {number} totalValue
|
|
* @returns {number} percentage
|
|
*/
|
|
getPercentage = function (partialValue, totalValue) {
|
|
return Math.floor((100 * partialValue) / totalValue);
|
|
}
|
|
|
|
/**
|
|
* If the file ends with .gz, remove it
|
|
* @param {string} path
|
|
* @return {string} path without gz, or unmodified path if it isnt a gz
|
|
*/
|
|
getFilePath(path) {
|
|
var path_split = path.split('/');
|
|
path_split.pop();
|
|
return path_split.join('/');
|
|
}
|
|
|
|
/**
|
|
* Gets the file extension from a path
|
|
* @param {string} path
|
|
* @returns {String} File Extension (without dot)
|
|
*/
|
|
getFileExt(path) {
|
|
return path.reverse().split(".")[0].reverse();
|
|
}
|
|
|
|
getLineFromFile(filename, line_no, callback) {
|
|
var stream = this.fs.createReadStream(filename, {
|
|
flags: 'r',
|
|
encoding: 'utf-8',
|
|
fd: null,
|
|
bufferSize: 64 * 1024
|
|
});
|
|
|
|
|
|
var fileData = '';
|
|
stream.on('data', function (data) {
|
|
fileData += data;
|
|
|
|
// The next lines should be improved
|
|
var lines = fileData.split("\n");
|
|
|
|
if (lines.length >= +line_no) {
|
|
stream.destroy();
|
|
callback(null, lines[+line_no]);
|
|
}
|
|
});
|
|
|
|
stream.on('error', function () {
|
|
callback('Error', null);
|
|
});
|
|
|
|
stream.on('end', function () {
|
|
callback('File end reached without finding line', null);
|
|
});
|
|
}
|
|
|
|
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.";
|
|
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.";
|
|
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:
|
|
headers = code + " " + data + "\n";
|
|
headers += "Content-Type: text/html\n";
|
|
break;
|
|
}
|
|
console.error(" * doErrorPage Called:", code, data);
|
|
return new Array(headers, data);
|
|
}
|
|
|
|
/**
|
|
* Strips bad things from paths
|
|
* @param {string} base Base path
|
|
* @param {string} target Sub path
|
|
*/
|
|
makeSafePath(base, target) {
|
|
target.replace(/[\|\&\;\$\%\@\"\<\>\+\,\\]/g, "");
|
|
var targetPath = this.path.posix.normalize(target)
|
|
return this.fixPathSlashes(base + this.path.sep + targetPath);
|
|
}
|
|
|
|
makeSafeUsername(username) {
|
|
return username.replace(/^([A-Za-z0-9\-\_]{5,16})$/, '');
|
|
}
|
|
|
|
/**
|
|
* Corrects any / or \ differences, if any for file paths
|
|
* @param {string} path
|
|
* @returns {string} corrected path
|
|
*/
|
|
fixPathSlashes(path) {
|
|
// fix slashes
|
|
if (this.path.sep == '/' && path.indexOf("\\") != -1) path = path.replace(/\\/g, this.path.sep);
|
|
else if (this.path.sep == "\\" && path.indexOf("/") != -1) path = path.replace(/\//g, this.path.sep);
|
|
|
|
// remove double slashes
|
|
while (path.indexOf(this.path.sep + this.path.sep) != -1) path = path.replace(this.path.sep + this.path.sep, this.path.sep);
|
|
|
|
return path;
|
|
}
|
|
/**
|
|
* 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; |