From f51c0b712f1e1d3080e06ddef653555d56f0f611 Mon Sep 17 00:00:00 2001 From: zefie Date: Wed, 6 Aug 2025 15:21:33 -0400 Subject: [PATCH] more optimizations --- zefie_wtvp_minisrv/includes/classes/HTTPX.js | 9 +- .../includes/classes/WTVClientSessionData.js | 74 +++--- zefie_wtvp_minisrv/includes/classes/WTVSec.js | 78 +++---- .../includes/classes/WTVShared.js | 212 +++++++++--------- 4 files changed, 189 insertions(+), 184 deletions(-) diff --git a/zefie_wtvp_minisrv/includes/classes/HTTPX.js b/zefie_wtvp_minisrv/includes/classes/HTTPX.js index 3e86f816..99a8d630 100644 --- a/zefie_wtvp_minisrv/includes/classes/HTTPX.js +++ b/zefie_wtvp_minisrv/includes/classes/HTTPX.js @@ -1,13 +1,12 @@ // From https://stackoverflow.com/a/42019773 'use strict'; -let net = require('net'); -let http = require('http'); -let https = require('https'); +const net = require('net'); +const http = require('http'); +const https = require('https'); exports.createServer = (opts, handler) => { - - let server = net.createServer(socket => { + const server = net.createServer(socket => { socket.once('data', buffer => { // Pause the socket socket.pause(); diff --git a/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js b/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js index 5bc9624a..e3bf65e7 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js @@ -42,7 +42,7 @@ class WTVClientSessionData { this.wtvmime = new WTVMime(minisrv_config); this.lockdown = false; this.ssid = ssid; - this.data_store = new Array(); + this.data_store = []; this.session_store = {}; this.lockdownWhitelist = minisrv_config.config.lockdownWhitelist; this.lockdownWhitelist.push(minisrv_config.config.unauthorized_url); @@ -90,7 +90,7 @@ class WTVClientSessionData { clearUserSessionMemory() { this.setUserLoggedIn(false); - this.data_store = new Array(); + this.data_store = []; this.session_store = {}; this.assignFavoriteStore(); this.assignMailStore() @@ -98,7 +98,7 @@ class WTVClientSessionData { switchUserID(user_id, update_mail = true, update_ticket = true, update_favorite = true) { this.user_id = parseInt(user_id); - if (user_id != null) { + if (user_id !== null) { this.loadSessionData(); if (this.isRegistered() && update_mail) this.assignMailStore(); if (this.isRegistered() && update_favorite) this.assignMailStore(); @@ -189,7 +189,7 @@ class WTVClientSessionData { 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") { + if (f.startsWith("user")) { var user_file = this.path.resolve(master_directory + self.path.sep + f + self.path.sep + f + ".json"); if (self.fs.existsSync(user_file)) { if (f == "user0") { @@ -230,7 +230,7 @@ class WTVClientSessionData { * @returns {string|boolean} Absolute path to the user's file store, or false if unregistered */ getUserStoreDirectory(subscriber = false, user_id = null) { - if (user_id == null) user_id = this.user_id; + if (user_id === null) user_id = this.user_id; var userstore = this.getAccountStoreDirectory() + this.path.sep + this.ssid + this.path.sep; if (!subscriber) userstore += "user" + user_id + this.path.sep; return this.wtvshared.getAbsolutePath(userstore) + this.path.sep; @@ -340,7 +340,7 @@ class WTVClientSessionData { } catch (e) { console.error(" # User File Store failed", e); } - return (result === false) ? false : true; + return result !== false; } scrapbookExists() { @@ -355,7 +355,7 @@ class WTVClientSessionData { } createScrapbook() { - if (this.scrapbookExists() === false) { + if (!this.scrapbookExists()) { try { if (!this.fs.existsSync(this.scrapbook_dir)) this.fs.mkdirSync(this.scrapbook_dir, { recursive: true }); return true; @@ -365,18 +365,18 @@ class WTVClientSessionData { } scrapbookDir() { - if (this.scrapbookExists() === false) { + if (!this.scrapbookExists()) { this.createScrapbook(); } return this.scrapbook_dir; } listScrapbook() { - if (this.scrapbookExists() === false) { + if (!this.scrapbookExists()) { this.createScrapbook(); } const files = this.fs.readdirSync(this.scrapbook_dir); - const filteredFiles = files.sort(function(a, b) { + const filteredFiles = files.sort((a, b) => { return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' @@ -386,15 +386,15 @@ class WTVClientSessionData { } getFreeScrapbookID() { - if (this.scrapbookExists() === false) { + if (!this.scrapbookExists()) { this.createScrapbook(); } var id = 1; var files = this.fs.readdirSync(this.scrapbook_dir); - if (files.length == 0) { + if (files.length === 0) { return id; } - files = files.map(file => parseInt(file.substr(0, file.indexOf('.')))); + files = files.map(file => parseInt(file.slice(0, file.indexOf('.')))); while (files.includes(id)) { id++; } @@ -402,7 +402,7 @@ class WTVClientSessionData { } getScrapbookUsage() { - if (this.scrapbookExists() === false) { + if (!this.scrapbookExists()) { this.createScrapbook(); } var total_size = 0; @@ -419,7 +419,7 @@ class WTVClientSessionData { } getScrapbookUsagePercent() { - if (this.scrapbookExists() === false) { + if (!this.scrapbookExists()) { this.createScrapbook(); } var total_size = this.getScrapbookUsage(); @@ -430,7 +430,7 @@ class WTVClientSessionData { } getScrapbookImage(id) { - if (this.scrapbookExists() === false) { + if (!this.scrapbookExists()) { this.createScrapbook(); } var file = this.scrapbook_dir + id; @@ -441,7 +441,7 @@ class WTVClientSessionData { } getScrapbookImageType(id) { - if (this.scrapbookExists() === false) { + if (!this.scrapbookExists()) { this.createScrapbook(); } var file = this.scrapbook_dir + id + ".meta"; @@ -459,7 +459,7 @@ class WTVClientSessionData { addToScrapbook(filename, contentType, data) { try { - if (this.scrapbookExists() === false) { + if (!this.scrapbookExists()) { this.createScrapbook(); } var fileout = this.scrapbook_dir + filename; @@ -546,7 +546,7 @@ class WTVClientSessionData { else return false; } else { if (path && expires && data) { - var cookie_data = new Array(); + var cookie_data = {}; cookie_data['cookie'] = unescape(data); cookie_data['expires'] = unescape(expires); cookie_data['path'] = unescape(path); @@ -564,7 +564,7 @@ class WTVClientSessionData { 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(); + if (cookie_index === -1) cookie_index = this.countCookies(); this.session_store.cookies[cookie_index] = Object.assign({}, cookie_data); @@ -582,7 +582,7 @@ class WTVClientSessionData { var self = this; var result = false; Object.keys(this.session_store['cookies']).forEach(function (k) { - if (result != false) return; + if (result !== false) return; if (self.session_store['cookies'][k].domain == domain && self.session_store['cookies'][k].path == path) { @@ -728,7 +728,7 @@ class WTVClientSessionData { getUserPasswordEnabled() { if (!this.minisrv_config.config.passwords.enabled) return false; // master config override var enabled = this.getSessionData("subscriber_password"); - return (enabled != null && typeof enabled != undefined); // true if set, false if null/disabled + return (enabled !== null && typeof enabled !== 'undefined'); // true if set, false if null/disabled } validateUserPassword(passwd) { @@ -846,7 +846,7 @@ class WTVClientSessionData { nick = nick.replace(/[^a-zA-Z0-9\-\_\`\^]/g, ""); // limit nick length based on build support - nick = nick.substring(0, this.getMaxUsernameLength()); + nick = nick.slice(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; @@ -877,7 +877,7 @@ class WTVClientSessionData { setSessionData(key, value) { if (key === null) throw ("ClientSessionData.setSessionData(): invalid key provided"); - if (typeof (this.session_store) === 'undefined') this.session_store = new Array(); + if (typeof (this.session_store) === 'undefined') this.session_store = {}; this.session_store[key] = value; this.SaveIfRegistered(); } @@ -898,7 +898,7 @@ class WTVClientSessionData { set(key, value) { if (key === null) throw ("ClientSessionData.set(): invalid key provided"); - if (typeof (this.data_store) === 'undefined') this.data_store = new Array(); + if (typeof (this.data_store) === 'undefined') this.data_store = []; this.data_store[key] = value; this.SaveIfRegistered(); } @@ -957,7 +957,7 @@ class WTVClientSessionData { }; var isInSubnet = function (ip, subnet) { - if (subnet.indexOf('/') == -1) { + if (!subnet.includes('/')) { var mask, base_ip, long_ip = this.ip2long(ip); var mask2, base_ip2, long_ip2 = this.ip2long(ip); return (long_ip == long_ip2); @@ -986,7 +986,7 @@ class WTVClientSessionData { if (self.minisrv_config.config.ssid_ip_allow_list) { if (self.minisrv_config.config.ssid_ip_allow_list[self.ssid]) { Object.keys(self.minisrv_config.config.ssid_ip_allow_list[self.ssid]).forEach(function (k) { - if (self.minisrv_config.config.ssid_ip_allow_list[self.ssid][k].indexOf('/') > 0) { + if (self.minisrv_config.config.ssid_ip_allow_list[self.ssid][k].includes('/')) { if (isInSubnet(self.clientAddress, self.minisrv_config.config.ssid_ip_allow_list[self.ssid][k])) { // remoteAddr is in allowed subnet ssid_access_list_ip_override = true; @@ -1011,7 +1011,7 @@ class WTVClientSessionData { // process whitelist first if (self.ssid && self.minisrv_config.config.ssid_allow_list) { var ssid_is_in_whitelist = self.minisrv_config.config.ssid_allow_list.findIndex(element => element == self.ssid); - if (ssid_is_in_whitelist == -1) { + if (ssid_is_in_whitelist === -1) { // no whitelist match, but lets see if the remoteAddress is allowed checkSSIDIPWhitelist(self.ssid, false); } @@ -1020,7 +1020,7 @@ class WTVClientSessionData { // now check blacklist if (self.ssid && self.minisrv_config.config.ssid_block_list) { var ssid_is_in_blacklist = self.minisrv_config.config.ssid_block_list.findIndex(element => element == self.ssid); - if (ssid_is_in_blacklist != -1) { + if (ssid_is_in_blacklist !== -1) { // blacklist match, but lets see if the remoteAddress is allowed checkSSIDIPWhitelist(self.ssid, true); } @@ -1046,19 +1046,21 @@ class WTVClientSessionData { switch (whitelist) { case "lockdown": Object.keys(this.lockdownWhitelist).forEach(function (k) { - if (self.lockdownWhitelist[k].charAt(self.lockdownWhitelist[k].length - 1) == '*') { - if (self.lockdownWhitelist[k].substring(0, self.lockdownWhitelist[k].length - 1) == url.substring(0, self.lockdownWhitelist[k].length - 1)) authorized = true; + if (self.lockdownWhitelist[k].endsWith('*')) { + const prefix = self.lockdownWhitelist[k].slice(0, -1); + if (url.startsWith(prefix)) authorized = true; } else { - if (self.lockdownWhitelist[k].substring(0, url.length) == url) authorized = true; + if (url.startsWith(self.lockdownWhitelist[k])) authorized = true; } }); break; case "login": Object.keys(this.loginWhitelist).forEach(function (k) { - if (self.loginWhitelist[k].charAt(self.loginWhitelist[k].length - 1) == '*') { - if (self.loginWhitelist[k].substring(0, self.loginWhitelist[k].length - 1) == url.substring(0, self.loginWhitelist[k].length - 1)) authorized = true; + if (self.loginWhitelist[k].endsWith('*')) { + const prefix = self.loginWhitelist[k].slice(0, -1); + if (url.startsWith(prefix)) authorized = true; } else { - if (self.loginWhitelist[k].substring(0, url.length) == url) authorized = true; + if (url.startsWith(self.loginWhitelist[k])) authorized = true; } }); break; @@ -1099,7 +1101,7 @@ class WTVClientSessionData { else return "Sony"; else if (brandId == 1) - if (url && isPlus == true) + if (url && isPlus === true) return "Philips-Plus"; else return "Philips"; diff --git a/zefie_wtvp_minisrv/includes/classes/WTVSec.js b/zefie_wtvp_minisrv/includes/classes/WTVSec.js index f682faab..550991c1 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVSec.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVSec.js @@ -1,8 +1,8 @@ const CryptoJS = require('crypto-js'); const endianness = require('endianness'); -var RC4 = require('rc4-crypto'); -var crypto = require('crypto'); -var WTVShared = require("./WTVShared.js")['WTVShared']; +const RC4 = require('rc4-crypto'); +const crypto = require('crypto'); +const WTVShared = require("./WTVShared.js")['WTVShared']; /** * Javascript implementation of WTVP Security @@ -26,7 +26,7 @@ class WTVSec { session_key2 = null; hRC4_Key1 = null; hRC4_Key2 = null; - RC4Session = new Array(); + RC4Session = []; minisrv_config = []; update_ticket = false; wtvshared = null; @@ -49,7 +49,7 @@ class WTVSec { this.incarnation = wtv_incarnation; this.current_shared_key = this.initial_shared_key; } else { - throw ("Invalid initial key length"); + throw new Error("Invalid initial key length"); } } @@ -58,7 +58,7 @@ class WTVSec { * @param {Number} wtv_incarnation */ set_incarnation(wtv_incarnation) { - if (this.incarnation != wtv_incarnation) { + if (this.incarnation !== wtv_incarnation) { this.incarnation = wtv_incarnation; this.SecureOn(); } @@ -68,7 +68,7 @@ class WTVSec { * Increments the wtv-incaration for this instance by 1 */ increment_incarnation() { - this.set_incarnation(parseInt(this.incarnation) + 1); + this.set_incarnation(Number(this.incarnation) + 1); } /** @@ -86,21 +86,21 @@ class WTVSec { */ PrepareTicket() { // store last challenge response in ticket - if (this.minisrv_config.config.debug_flags.debug) console.log(" * Preparing a new ticket with ticket_store:", this.ticket_store) - var ticket_data_raw = this.challenge_raw; + if (this.minisrv_config.config.debug_flags.debug) console.log(" * Preparing a new ticket with ticket_store:", this.ticket_store); + let ticket_data_raw = this.challenge_raw; try { - var ticket_data = ticket_data_raw.toString(CryptoJS.enc.Hex) + CryptoJS.enc.Utf8.parse(JSON.stringify(this.ticket_store)).toString(CryptoJS.enc.Hex); + const ticket_data = ticket_data_raw.toString(CryptoJS.enc.Hex) + CryptoJS.enc.Utf8.parse(JSON.stringify(this.ticket_store)).toString(CryptoJS.enc.Hex); ticket_data_raw = CryptoJS.enc.Hex.parse(ticket_data); - var ticket_data_enc = CryptoJS.DES.encrypt(ticket_data_raw, this.initial_shared_key, { + const ticket_data_enc = CryptoJS.DES.encrypt(ticket_data_raw, this.initial_shared_key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); // create a copy of WordArray since concat modifies the original - var challenge_signed_key = this.DuplicateWordArray(this.challenge_signed_key); + const challenge_signed_key = this.DuplicateWordArray(this.challenge_signed_key); this.ticket_b64 = challenge_signed_key.concat(ticket_data_enc.ciphertext).toString(CryptoJS.enc.Base64); } catch (e) { - console.log("Error encrypting ticket: " + e.toString()); + console.log(`Error encrypting ticket: ${e}`); return null; } return this.ticket_b64; @@ -111,11 +111,11 @@ class WTVSec { * @param {Base64} ticket_b64 */ DecodeTicket(ticket_b64) { - var ticket_hex = CryptoJS.enc.Base64.parse(ticket_b64).toString(CryptoJS.enc.Hex); - var challenge_key = CryptoJS.enc.Hex.parse(ticket_hex.substring(0, 16)); - var challenge_enc = CryptoJS.enc.Hex.parse(ticket_hex.substring(16)); + const ticket_hex = CryptoJS.enc.Base64.parse(ticket_b64).toString(CryptoJS.enc.Hex); + const challenge_key = CryptoJS.enc.Hex.parse(ticket_hex.slice(0, 16)); + const challenge_enc = CryptoJS.enc.Hex.parse(ticket_hex.slice(16)); - var ticket_dec = CryptoJS.DES.decrypt( + const ticket_dec = CryptoJS.DES.decrypt( { ciphertext: challenge_enc }, @@ -125,11 +125,11 @@ class WTVSec { padding: CryptoJS.pad.Pkcs7 } ); - var data_offset = 216; // (108 * 2); - var challenge_code = ticket_dec.toString().substring(0, data_offset); - var challenge_code_b64 = CryptoJS.enc.Hex.parse(challenge_code).toString(CryptoJS.enc.Base64); + const data_offset = 216; // (108 * 2); + const challenge_code = ticket_dec.toString().slice(0, data_offset); + const challenge_code_b64 = CryptoJS.enc.Hex.parse(challenge_code).toString(CryptoJS.enc.Base64); if ((ticket_dec.sigBytes * 2) >= challenge_code.length) { - var ticket_data_dec = CryptoJS.enc.Hex.parse(ticket_dec.toString().substring(data_offset)).toString(CryptoJS.enc.Utf8); + const ticket_data_dec = CryptoJS.enc.Hex.parse(ticket_dec.toString().slice(data_offset)).toString(CryptoJS.enc.Utf8); this.ticket_store = this.wtvshared.tryDecodeJSON(ticket_data_dec); } else { this.ticket_store = {}; @@ -145,9 +145,9 @@ class WTVSec { * @returns {any} The ticket data for the specified key, or null if not found */ getTicketData(key = null) { - if (typeof (this.ticket_store) === 'session_store') return null; + if (typeof this.ticket_store === 'session_store') return null; else if (key === null) return this.ticket_store; - else if (typeof this.ticket_store[key] !== 'undefined') return this.ticket_store[key]; + else if (key in this.ticket_store) return this.ticket_store[key]; else return null; } @@ -157,8 +157,8 @@ class WTVSec { * @param {any} value The value to set for the specified key */ setTicketData(key, value) { - if (key === null) throw ("WTVSec.setTicketData(): invalid key provided"); - if (typeof (this.ticket_store) === 'undefined') this.ticket_store = {}; + if (!key) throw new Error("WTVSec.setTicketData(): invalid key provided"); + if (!this.ticket_store) this.ticket_store = {}; this.ticket_store[key] = value; if (this.ticket_b64) this.PrepareTicket(); this.update_ticket = true; @@ -169,8 +169,8 @@ class WTVSec { * @param {string} key The key of the ticket data to delete */ deleteTicketData(key) { - if (key === null) throw ("WTVSec.deleteTicketData(): invalid key provided"); - if (typeof (this.ticket_store) === 'undefined') { + if (!key) throw new Error("WTVSec.deleteTicketData(): invalid key provided"); + if (!this.ticket_store) { this.ticket_store = {}; return; } @@ -194,8 +194,8 @@ class WTVSec { } const challenge_raw_hex = challenge_raw.toString(CryptoJS.enc.Hex); - const challenge_id_hex = challenge_raw_hex.substring(0, 16); // 8 bytes * 2 - const challenge_enc = CryptoJS.enc.Hex.parse(challenge_raw_hex.substring(16)); + const challenge_id_hex = challenge_raw_hex.slice(0, 16); // 8 bytes * 2 + const challenge_enc = CryptoJS.enc.Hex.parse(challenge_raw_hex.slice(16)); const challenge_decrypted = CryptoJS.DES.decrypt( { ciphertext: challenge_enc }, @@ -204,18 +204,18 @@ class WTVSec { ); const challenge_dec_hex = challenge_decrypted.toString(CryptoJS.enc.Hex); - const challenge_md5_challenge = CryptoJS.MD5(CryptoJS.enc.Hex.parse(challenge_dec_hex.substring(0, 160))).toString(CryptoJS.enc.Hex); // 80 bytes * 2 + const challenge_md5_challenge = CryptoJS.MD5(CryptoJS.enc.Hex.parse(challenge_dec_hex.slice(0, 160))).toString(CryptoJS.enc.Hex); // 80 bytes * 2 - if (challenge_dec_hex.substring(160, 192) !== challenge_md5_challenge) { // 96 bytes * 2 + if (challenge_dec_hex.slice(160, 192) !== challenge_md5_challenge) { // 96 bytes * 2 return ""; } - this.current_shared_key = CryptoJS.enc.Hex.parse(challenge_dec_hex.substring(144, 160)); // 72 bytes * 2, 80 bytes * 2 - const challenge_echo = CryptoJS.enc.Hex.parse(challenge_dec_hex.substr(0, 80)); // 40 bytes * 2 + this.current_shared_key = CryptoJS.enc.Hex.parse(challenge_dec_hex.slice(144, 160)); // 72 bytes * 2, 80 bytes * 2 + const challenge_echo = CryptoJS.enc.Hex.parse(challenge_dec_hex.slice(0, 80)); // 40 bytes * 2 // RC4 encryption keys. Stored in the wtv-ticket on the server side. - this.session_key1 = CryptoJS.enc.Hex.parse(challenge_dec_hex.substring(80, 112)); // 40 bytes * 2, 56 bytes * 2 - this.session_key2 = CryptoJS.enc.Hex.parse(challenge_dec_hex.substring(112, 144)); // 56 bytes * 2, 72 bytes * 2 + this.session_key1 = CryptoJS.enc.Hex.parse(challenge_dec_hex.slice(80, 112)); // 40 bytes * 2, 56 bytes * 2 + this.session_key2 = CryptoJS.enc.Hex.parse(challenge_dec_hex.slice(112, 144)); // 56 bytes * 2, 72 bytes * 2 const echo_encrypted = CryptoJS.DES.encrypt( CryptoJS.MD5(challenge_echo).concat(challenge_echo).concat(CryptoJS.enc.Utf8.parse("\x08".repeat(8))), @@ -279,14 +279,14 @@ class WTVSec { * @param {Number} rc4session Session Type (0 = enc k1, 1 = dec k1, 2 = enc k2, 3 = dec k2, default: all) */ SecureOn(rc4session = null) { - if (this.minisrv_config.config.debug_flags.debug) console.log(" # Generating RC4 sessions with wtv-incarnation: " + this.incarnation); + if (this.minisrv_config.config.debug_flags.debug) console.log(` # Generating RC4 sessions with wtv-incarnation: ${this.incarnation}`); - var buf = new Uint8Array([0xff & this.incarnation, 0xff & (this.incarnation >> 8), 0xff & (this.incarnation >> 16), 0xff & (this.incarnation >> 24)]); + const buf = new Uint8Array([0xff & this.incarnation, 0xff & (this.incarnation >> 8), 0xff & (this.incarnation >> 16), 0xff & (this.incarnation >> 24)]); endianness(buf, 4); this.hRC4_Key1 = CryptoJS.MD5(this.DuplicateWordArray(this.session_key1).concat(CryptoJS.lib.WordArray.create(buf).concat(this.DuplicateWordArray(this.session_key1)))); this.hRC4_Key2 = CryptoJS.MD5(this.DuplicateWordArray(this.session_key2).concat(CryptoJS.lib.WordArray.create(buf).concat(this.DuplicateWordArray(this.session_key2)))); - var key1 = this.wtvshared.wordArrayToBuffer(this.hRC4_Key1); - var key2 = this.wtvshared.wordArrayToBuffer(this.hRC4_Key2); + const key1 = this.wtvshared.wordArrayToBuffer(this.hRC4_Key1); + const key2 = this.wtvshared.wordArrayToBuffer(this.hRC4_Key2); const setRC4Session = (sessionIndex, key) => { this.RC4Session[sessionIndex] = new RC4.RC4(key); }; diff --git a/zefie_wtvp_minisrv/includes/classes/WTVShared.js b/zefie_wtvp_minisrv/includes/classes/WTVShared.js index 57b57607..5055981a 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVShared.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVShared.js @@ -30,22 +30,22 @@ class WTVShared { * @notice If minisrv_config is null, it will attempt to read the configuration from the minisrv_config.json file * */ constructor(minisrv_config, quiet = false) { - if (minisrv_config == null) this.minisrv_config = this.readMiniSrvConfig(true, !quiet); + if (minisrv_config === null || typeof minisrv_config === 'undefined') this.minisrv_config = this.readMiniSrvConfig(true, !quiet); else this.minisrv_config = minisrv_config; this.shenanigans = new WTVShenanigans(this.minisrv_config); if (!String.prototype.reverse) { String.prototype.reverse = function () { - var splitString = this.split(""); - var reverseArray = splitString.reverse(); + const splitString = this.split(""); + const reverseArray = splitString.reverse(); return reverseArray.join(""); } } if (!String.prototype.hexEncode) { String.prototype.hexEncode = function () { - var result = ''; - for (var i = 0; i < this.length; i++) { + let result = ''; + for (let i = 0; i < this.length; i++) { result += this.charCodeAt(i).toString(16); } return result; @@ -59,7 +59,7 @@ class WTVShared { * @returns {Buffer} JS Buffer object */ wordArrayToBuffer(wordArray) { - if (wordArray) return new Buffer.from(wordArray.toString(CryptoJS.enc.Hex), 'hex'); + if (wordArray) return Buffer.from(wordArray.toString(CryptoJS.enc.Hex), 'hex'); else return null; } @@ -92,12 +92,12 @@ class WTVShared { * @returns {number} The long integer representation of the IP address, or -1 if the IP address is invalid */ ip2long(ip) { - var components; + let components; if (components = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) { - var iplong = 0; - var power = 1; - for (var i = 4; i >= 1; i -= 1) { - iplong += power * parseInt(components[i]); + let iplong = 0; + let power = 1; + for (let i = 4; i >= 1; i -= 1) { + iplong += power * parseInt(components[i], 10); power *= 256; } return iplong; @@ -112,15 +112,19 @@ class WTVShared { * @returns {boolean} True if the IP address is in the subnet, false otherwise */ isInSubnet(ip, subnet) { - if (subnet.indexOf('/') == -1) { - var mask, base_ip, long_ip = this.ip2long(ip); - var mask2, base_ip2, long_ip2 = this.ip2long(ip); - return (long_ip == long_ip2); + if (!subnet.includes('/')) { + const long_ip = this.ip2long(ip); + const long_subnet = this.ip2long(subnet); + return long_ip === long_subnet; } else { - var mask, base_ip, long_ip = this.ip2long(ip); - if ((mask = subnet.match(/^(.*?)\/(\d{1,2})$/)) && ((base_ip = this.ip2long(mask[1])) >= 0)) { - var freedom = Math.pow(2, 32 - parseInt(mask[2])); - return (long_ip > base_ip) && (long_ip < base_ip + freedom - 1); + const long_ip = this.ip2long(ip); + const mask = subnet.match(/^(.*?)\/(\d{1,2})$/); + if (mask && mask[1]) { + const base_ip = this.ip2long(mask[1]); + if (base_ip >= 0) { + const freedom = 2 ** (32 - parseInt(mask[2], 10)); + return (long_ip >= base_ip) && (long_ip <= base_ip + freedom - 1); + } } } return false; @@ -162,7 +166,7 @@ class WTVShared { * @returns {Object} The decoded JSON object, or an empty object if decoding fails. */ tryDecodeJSON(json_string) { - var out; + let out; try { out = JSON.parse(json_string); } catch (e) { @@ -209,7 +213,7 @@ class WTVShared { let crc = 0; for (let i = 0; i < 14; i += 2) { - let inbyte = parseInt(ssid.substring(i, i + 2), 16); + let inbyte = parseInt(ssid.slice(i, i + 2), 16); if (isNaN(inbyte)) return '00'; for (let ii = 0; ii < 8; ii++) { @@ -240,7 +244,7 @@ class WTVShared { * @returns {string} The parsed string */ parseConfigVars(s) { - if (s.indexOf("%ServiceDeps%") >= 0) + if (s.includes("%ServiceDeps%")) return this.getServiceDep(s.replace("%ServiceDeps%", ""), true); else return s; @@ -279,11 +283,11 @@ class WTVShared { } else if (src instanceof Date) { return new Date(src.getTime()); } else if (typeof src === 'object' && src !== null) { - var clone = null; + let clone = null; if (Array.isArray(src)) clone = []; else clone = {}; - var self = this; + const self = this; Object.keys(src).forEach((k) => { clone[k] = self.cloneObj(src[k]); }); @@ -298,10 +302,9 @@ class WTVShared { * @param {string} service_name (optional) Service to check */ isAdmin(wtvclient, service_name = "wtv-admin") { - var WTVAdmin = require("./WTVAdmin.js"); - var wtva = new WTVAdmin(this.minisrv_config, wtvclient, service_name); - var result = wtva.isAuthorized(true); - wtva, WTVAdmin = null; + const WTVAdmin = require("./WTVAdmin.js"); + const wtva = new WTVAdmin(this.minisrv_config, wtvclient, service_name); + const result = wtva.isAuthorized(true); return result; } @@ -360,7 +363,7 @@ class WTVShared { // for easy retrofitting old code to work with the webtvism of allowing multiple of the same query name // pass it the query, and it will return a string regardless. if its a string it just sends it back // if its an array we just pull the last object - if (typeof (query) === "object") + if (typeof query === "object") return query[(Object.keys(query).length - 1)]; else return query @@ -379,7 +382,7 @@ class WTVShared { } // Directly replace &, <, >, ", and ' characters - let entitized = string.replace(/[&<>"]/g, function (match) { + let entitized = string.replace(/[&<>"]/g, (match) => { switch (match) { case '&': return '&'; case '<': return '<'; @@ -404,11 +407,11 @@ class WTVShared { * @returns {string} Sanitized string */ sanitizeSignature(string) { - var allowedSchemes = ['http', 'https', 'ftp', 'mailto']; - var self = this; + const allowedSchemes = ['http', 'https', 'ftp', 'mailto']; + const self = this; // allow links to services flagged as "wideopen" Object.keys(this.minisrv_config.services).forEach((k) => { - var flag = parseInt(this.minisrv_config.services[k].flags, 16); + const flag = parseInt(this.minisrv_config.services[k].flags, 16); if (flag === 4 || flag === 7) { if (!allowedSchemes.includes(k)) allowedSchemes.push(k); } @@ -434,12 +437,12 @@ class WTVShared { allowedSchemes: allowedSchemes, allowedSchemesByTag: {}, allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'], - exclusiveFilter: function (frame) { - var allowed = true; + exclusiveFilter: (frame) => { + let allowed = true; Object.keys(frame.attribs).forEach((k) => { if (k == "href" || k == "background" || k == "src") { allowed = false; - var value = frame.attribs[k]; + const value = frame.attribs[k]; if (frame.tag !== "a") { // check everything except normal links @@ -473,33 +476,33 @@ class WTVShared { * @return {object} Headers object */ headerStringToObj(headers, response = false) { - var inc_headers = 0; - var headers_obj = {}; + let inc_headers = 0; + const headers_obj = {}; headers_obj.raw_headers = headers; - var headers_obj_pre = headers.split("\n"); - headers_obj_pre.forEach(function (d) { + const headers_obj_pre = headers.split("\n"); + headers_obj_pre.forEach((d) => { if (/^SECURE ON/.test(d) && !response) { headers_obj.secure = true; - } else if (/^([0-9]{3}) $/.test(d.substring(0, 4)) && response && !headers_obj.Status) { + } else if (/^([0-9]{3}) $/.test(d.slice(0, 4)) && response && !headers_obj.Status) { d.s headers_obj.Status = d.trim("\r"); - } else if (/^(GET |PUT |POST)$/.test(d.substring(0, 4)) && !response) { + } else if (/^(GET |PUT |POST)$/.test(d.slice(0, 4)) && !response) { headers_obj.request = d.trim("\r"); - var request_url = d.split(' '); + let request_url = d.split(' '); if (request_url.length > 2) { request_url.shift(); request_url = request_url.join(" "); if (request_url.indexOf("HTTP/") > 0) { - var index = request_url.indexOf(" HTTP/"); - request_url = request_url.substring(0, index); + const index = request_url.indexOf(" HTTP/"); + request_url = request_url.slice(0, index); } } else { request_url = request_url[1]; } headers_obj.request_url = decodeURI(request_url).trim("\r"); } else if (d.indexOf(":") > 0) { - var d_split = d.split(':'); - var header_name = d_split[0]; + const d_split = d.split(':'); + let header_name = d_split[0]; if (headers_obj[header_name] != null) { header_name = header_name + "_" + inc_headers; inc_headers++; @@ -507,8 +510,8 @@ class WTVShared { d_split.shift(); d = d_split.join(':'); headers_obj[header_name] = (d).trim("\r"); - if (headers_obj[header_name].substring(0, 1) == " ") { - headers_obj[header_name] = headers_obj[header_name].substring(1); + if (headers_obj[header_name].startsWith(" ")) { + headers_obj[header_name] = headers_obj[header_name].slice(1); } } }); @@ -522,17 +525,17 @@ class WTVShared { * @returns {object} // Headers object with only whitelisted headers */ stripHeaders(headers_obj, whitelist) { - var whitelisted_headers = new Array(); - var out_headers = new Array(); + const whitelisted_headers = []; + const out_headers = []; out_headers.Status = headers_obj.Status; if (headers_obj['wtv-connection-close']) out_headers['wtv-connection-close'] = headers_obj['wtv-connection-close']; // compare regardless of case - Object.keys(whitelist).forEach(function (k) { - Object.keys(headers_obj).forEach(function (j) { - if (whitelist[k].toLowerCase() == j.toLowerCase()) { + Object.keys(whitelist).forEach((k) => { + Object.keys(headers_obj).forEach((j) => { + if (whitelist[k].toLowerCase() === j.toLowerCase()) { // if header = connection, strip 'upgrade' - if (j.toLowerCase() == "connection") { + if (j.toLowerCase() === "connection") { headers_obj[j] = headers_obj[j].replace("Upgrade", "").replace(",", "").trim(); } whitelisted_headers[j.toLowerCase()] = [whitelist[k], j, headers_obj[j]]; @@ -541,9 +544,9 @@ class WTVShared { }); // restore original header order - Object.keys(headers_obj).forEach(function (k) { + Object.keys(headers_obj).forEach((k) => { if (whitelisted_headers[k.toLowerCase()]) { - if (whitelisted_headers[k.toLowerCase()][1] == k) out_headers[whitelisted_headers[k.toLowerCase()][0]] = whitelisted_headers[k.toLowerCase()][2]; + if (whitelisted_headers[k.toLowerCase()][1] === k) out_headers[whitelisted_headers[k.toLowerCase()][0]] = whitelisted_headers[k.toLowerCase()][2]; } }); @@ -712,15 +715,15 @@ class WTVShared { }; const ssid_obj = { - boxType: boxTypeMapping[ssid.substring(0, 2)], - unique_id: ssid.substring(2, 8), - region: regionMapping[ssid.substring(10, 14).toUpperCase()], - manufacturer: manufacturerMapping[ssid.substring(8, 10).toUpperCase()], - crc: ssid.substring(14) + boxType: boxTypeMapping[ssid.slice(0, 2)], + unique_id: ssid.slice(2, 8), + region: regionMapping[ssid.slice(10, 14).toUpperCase()], + manufacturer: manufacturerMapping[ssid.slice(8, 10).toUpperCase()], + crc: ssid.slice(14) }; // Special case for manufacturer based on region - if (ssid_obj.region === "Japan" && ssid.substring(8, 10).toUpperCase() === "00") { + if (ssid_obj.region === "Japan" && ssid.slice(8, 10).toUpperCase() === "00") { ssid_obj.manufacturer = "Panasonic"; } @@ -734,7 +737,7 @@ class WTVShared { * @return {string} */ getManufacturer(ssid, bit = false) { - if (bit) return ssid.substring(8, 10).toUpperCase(); + if (bit) return ssid.slice(8, 10).toUpperCase(); else return this.parseSSID(ssid).manufacturer || null; } @@ -930,7 +933,7 @@ class WTVShared { * @param {string} file Path to a file */ getFileLastModified(file) { - var stats = this.fs.lstatSync(file); + const stats = this.fs.lstatSync(file); if (stats) return new Date(stats.mtimeMs); return false; } @@ -1026,7 +1029,7 @@ class WTVShared { for (let i = 0; i < encoded.length; i++) { if (encoded[i] === '%') { - decoded[bufferIndex++] = parseInt(encoded.substr(i + 1, 2), 16); + decoded[bufferIndex++] = parseInt(encoded.slice(i + 1, i + 3), 16); i += 2; // Skip the next two characters } else { decoded[bufferIndex++] = encoded.charCodeAt(i); @@ -1059,7 +1062,7 @@ class WTVShared { * @param {string|Array} obj SSID String or Headers Object */ filterSSID(obj) { - var new_obj = false; + let new_obj = false; if (this.minisrv_config.config.hide_ssid_in_logs) { if (typeof obj === "string") { return this.censorSSID(obj); @@ -1081,7 +1084,7 @@ class WTVShared { * */ filterRequestLog(obj) { const passwordRegex = /(^pass$|passw(or)?d)/i; - var newobj = this.cloneObj(obj); // Clone the object once at the beginning + const newobj = this.cloneObj(obj); // Clone the object once at the beginning if (newobj.query) { Object.keys(newobj.query).forEach((k) => { @@ -1151,7 +1154,7 @@ class WTVShared { * @param {string} directory Root directory */ getAbsolutePath(path = '', directory = '.') { - if (directory[0] == "/" || directory.substr(1, 2) == ":" + this.path.sep) { + if (directory[0] == "/" || directory.slice(1, 3) == ":" + this.path.sep) { return this.path.resolve(directory + this.path.sep + path); } try { @@ -1196,7 +1199,7 @@ class WTVShared { * @return {string} path without gz, or unmodified path if it isnt a gz */ getFilePath(path) { - var path_split = path.split(this.path.sep); + const path_split = path.split(this.path.sep); path_split.pop(); return path_split.join(this.path.sep); } @@ -1266,14 +1269,14 @@ class WTVShared { getServiceString(service, overrides = {}) { // used externally by service scripts if (service === "all") { - var self = this; - var out = ""; - Object.keys(this.minisrv_config.services).sort().forEach(function (k) { + const self = this; + let out = ""; + Object.keys(this.minisrv_config.services).sort().forEach((k) => { if (!self.isConfiguredService(k)) return true; if (self.minisrv_config.services[k].pc_services) return true; if (overrides.exceptions) { - Object.keys(overrides.exceptions).forEach(function (j) { + Object.keys(overrides.exceptions).forEach((j) => { if (k != overrides.exceptions[j]) out += self.minisrv_config.services[k].toString(overrides) + "\n"; }); } else { @@ -1283,7 +1286,7 @@ class WTVShared { return out; } else { if (!this.minisrv_config.services[service]) { - throw ("SERVICE ERROR: Attempted to provision unconfigured service: " + service) + throw new Error("SERVICE ERROR: Attempted to provision unconfigured service: " + service); } else { return this.minisrv_config.services[service].toString(overrides); } @@ -1296,12 +1299,12 @@ class WTVShared { * @returns [headers, data] */ doRedirect(url) { - var headers = [] + const headers = []; headers['Status'] = "302 Moved"; headers["Location"] = url; headers["wtv-visit"] = url; - var data = '' - return [headers, data] + const data = ''; + return [headers, data]; } /** @@ -1318,7 +1321,7 @@ class WTVShared { const message = data || errorMessage.replace(/\$\{(\w+)\}/g, (match, p1) => minisrv_config.config[p1] || ''); if (details && [400, 500].includes(code)) { - data += "
Details:
" + details; + data += `
Details:
${details}`; } let headers = `Status: ${(pc_mode) ? 'HTTP/1.1' : ''} ${code} ${message}\n`; @@ -1342,14 +1345,15 @@ class WTVShared { * @param {string} target Sub path */ makeSafePath(base, target = null, force_forward_slash = false) { + let output = null; if (target) { target.replace(/[\|\&\;\$\%\@\"\<\>\+\,\\]/g, ""); - var targetPath = this.path.posix.normalize(target) - var output = this.fixPathSlashes(base + this.path.sep + targetPath); + const targetPath = this.path.posix.normalize(target) + output = this.fixPathSlashes(base + this.path.sep + targetPath); } else { base.replace(/[\|\&\;\$\%\@\"\<\>\+\,\\]/g, ""); - var targetPath = this.path.posix.normalize(base) - var output = this.fixPathSlashes(targetPath); + const targetPath = this.path.posix.normalize(base) + output = this.fixPathSlashes(targetPath); } return (force_forward_slash) ? output.replace(this.path.sep, '/') : output; } @@ -1384,7 +1388,7 @@ class WTVShared { */ makeSafeSSID(ssid = "") { ssid = ssid.replace(/[^a-zA-Z0-9]/g, ""); - if (ssid.length == 0) ssid = null; + if (ssid.length === 0) ssid = null; return ssid; } @@ -1395,7 +1399,7 @@ class WTVShared { * */ makeSafeStringPath(path = "") { path = path.replace(/[^\w]/g, "").replace(/\.\./g, ""); - if (path.length == 0) path = null; + if (path.length === 0) path = null; return path; } @@ -1405,7 +1409,7 @@ class WTVShared { * @returns {string} Uncompressed string * */ unpackCompressedB64(data) { - var data_buf = (typeof data === 'object') ? Buffer.from(data.toString('ascii'), 'base64') : Buffer.from(data, 'base64'); + const 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'); } @@ -1425,14 +1429,14 @@ class WTVShared { * @param {boolean} template If true, looks under templates subdir. */ getServiceDep(file, path_only = false, template = false) { - var self = this; - var outdata = null; - var found = false - this.minisrv_config.config.ServiceDeps.forEach(function (dep_vault_dir) { + const self = this; + let outdata = null; + let found = false + this.minisrv_config.config.ServiceDeps.forEach((dep_vault_dir) => { if (found) return; if (template) dep_vault_dir += self.path.sep + "templates"; - var search = self.getAbsolutePath(file, dep_vault_dir); + const search = self.getAbsolutePath(file, dep_vault_dir); if (self.fs.existsSync(search)) { if (path_only) outdata = search; else outdata = self.fs.readFileSync(search); @@ -1482,10 +1486,10 @@ class WTVShared { let keys = Object.keys(obj); let values = Object.values(obj); - const currentIndex = typeof currentKey === 'string' ? this.findObjectKeyIndex(currentKey, obj, case_insensitive) : parseInt(currentKey); + const currentIndex = typeof currentKey === 'string' ? this.findObjectKeyIndex(currentKey, obj, case_insensitive) : +currentKey; if (currentIndex === -1) return obj; - var destIndex = typeof destKey === 'string' ? this.findObjectKeyIndex(destKey, obj, case_insensitive) : parseInt(destKey); + let destIndex = typeof destKey === 'string' ? this.findObjectKeyIndex(destKey, obj, case_insensitive) : +destKey; // Bump by one if the destKey is a string (put after the key) if (typeof destKey === 'string' && destIndex !== -1) destIndex++; @@ -1555,9 +1559,9 @@ class clientShowAlert { if (typeof image === 'object') { this.image = null; - Object.keys(image).forEach(function (k) { + Object.keys(image).forEach((k) => { if (this[k] === null) this[k] = image[k]; - }, this); + }); } else { this.image = image; } @@ -1568,16 +1572,16 @@ class clientShowAlert { * @returns {string} client:showalert URL */ 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.sound) url += "sound=" + escape(this.sound) + "&"; + let 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.sound) url += `sound=${escape(this.sound)}&`; if (this.noback) url += "noback=true&"; - return url.substring(0, url.length - 1); + return url.slice(0, -1); } }