diff --git a/zefie_wtvp_minisrv/app.js b/zefie_wtvp_minisrv/app.js
index bf381a90..d2f5d8cb 100644
--- a/zefie_wtvp_minisrv/app.js
+++ b/zefie_wtvp_minisrv/app.js
@@ -211,7 +211,7 @@ async function sendRawFile(socket, path) {
var runScriptInVM = function (script_data, user_contextObj = {}, privileged = false, filename = null, debug_name = null) {
// Here we define the ServiceVault Script Context Object
- // The ServiceVault scripts will only be allowed to access the following fcnutions/variables.
+ // The ServiceVault scripts will only be allowed to access the following functions/variables.
// Furthermore, only modifications to variables in `updateFromVM` will be saved.
// Example: an attempt to change "minisrv_config" from a ServiceVault script would be discarded
@@ -240,8 +240,8 @@ var runScriptInVM = function (script_data, user_contextObj = {}, privileged = fa
"wtvmime": wtvmime,
"http": http,
"https": https,
- "URL": URL,
"sharp": sharp,
+ "URL": URL,
"wtvshared": wtvshared,
"zlib": zlib,
"clientShowAlert": clientShowAlert,
diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-author/scrapbook-add.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-author/scrapbook-add.js
index dd6f0dd7..62d58f74 100644
--- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-author/scrapbook-add.js
+++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-author/scrapbook-add.js
@@ -1,21 +1,140 @@
minisrv_service_file = true;
+request_is_async = true;
-if (!request_headers.query.mediaData) {
- var errpage = wtvshared.doErrorPage(400, "Bad Request", "Missing mediaData parameter.");
+function addToScrapbook(id, contentType, data) {
+ var result = session_data.addToScrapbook(id, contentType, data);
+ if (result) {
+ var successScrapbook = new clientShowAlert({
+ 'image': minisrv_config.config.service_logo,
+ 'message': "The image has been added to your scrapbook. Would you like to view your scrapbook now?",
+ 'buttonlabel1': "No",
+ 'buttonaction1': "client:donothing",
+ 'buttonlabel2': "Yes",
+ 'buttonaction2': "wtv-author:/scrapbook",
+ })
+ var files = session_data.listScrapbook();
+ var pageNum = Math.ceil(files.length / 6);
+ if (pageNum > 1) {
+ successScrapbook.buttonaction2 += '?pageNum=' + pageNum;
+ }
+ sendToClient(socket, {'Status': 302, 'wtv-expire-all': 'wtv-author:/scrapbook', 'Location': successScrapbook.getURL()}, '');
+ } else {
+ handleError('Failed to add image to scrapbook');
+ }
+}
+
+function handleError(reason) {
+ var errpage = wtvshared.doErrorPage(400, reason);
+ sendToClient(socket, errpage[0], errpage[1]);
+}
+if (!request_headers.query.mediaData && !request_headers.query.mediaPath) {
+ var errpage = wtvshared.doErrorPage(400, "Bad Request", "Missing mediaData or mediaPath parameter.");
headers = errpage[0];
data = errpage[1];
} else {
- var id = session_data.pagestore.getFreeScrapbookID();
- var result = session_data.pagestore.addToScrapbook(id, "image/jpg", request_headers.query.mediaData);
- if (result) {
- headers = `300 OK
+ const id = session_data.getFreeScrapbookID();
+ if (request_headers.query.mediaPath) {
+ if (!request_headers.query.confirm) {
+ var confirmScrapbook = new clientShowAlert({
+ 'image': minisrv_config.config.service_logo,
+ 'message': "You are about to add an image to your scrapbook.
Do you wish to continue?",
+ 'buttonlabel1': "Continue",
+ 'buttonaction1': "wtv-author:/scrapbook-add?confirm=true&mediaPath=" + encodeURIComponent(request_headers.query.mediaPath || ''),
+ 'buttonlabel2': "Cancel",
+ 'buttonaction2': "client:donothing"
+ }).getURL();
+ sendToClient(socket, {'Status': 302, 'Location': confirmScrapbook}, '');
+ } else {
+ function isValidImageType(contentType, url) {
+ // Check content-type header or file extension
+ if (contentType) {
+ return contentType === 'image/jpeg' || contentType == 'image/jpg' || contentType === 'image/gif';
+ }
+ return url.endsWith('.jpg') || url.endsWith('.jpeg') || url.endsWith('.gif');
+ }
+
+ try {
+ const parsedUrl = new URL(request_headers.query.mediaPath);
+ const protocol = parsedUrl.protocol === 'https:' ? https : http;
+
+ protocol.get(request_headers.query.mediaPath, (res) => {
+ if (res.statusCode !== 200) {
+ handleError('URL does not exist or returned status ' + res.statusCode);
+ res.resume();
+ return;
+ }
+
+ const contentType = res.headers['content-type'];
+ const contentLength = parseInt(res.headers['content-length'], 10);
+
+ if (!isValidImageType(contentType, request_headers.query.mediaPath)) {
+ handleError('URL is not a JPEG or GIF image');
+ res.resume();
+ return;
+ }
+
+ if (contentLength && contentLength > 1024 * 1024 * 4) {
+ handleError('Image is larger than 4MB');
+ res.resume();
+ return;
+ }
+
+ let data = [];
+ let totalLength = 0;
+ res.on('data', (chunk) => {
+ totalLength += chunk.length;
+ if (totalLength > 1024 * 1024 * 4) {
+ handleError('Image is larger than 4MB');
+ res.destroy();
+ return;
+ }
+ data.push(chunk);
+ });
+
+ res.on('end', () => {
+ if (totalLength > 1024 * 1024 * 4) return;
+ if (totalLength > 1024 * 1024) {
+ sharp(Buffer.concat(data))
+ .resize(640, 480, {
+ fit: 'inside',
+ withoutEnlargement: true
+ })
+ .jpeg({ quality: 75 })
+ .toBuffer()
+ .then(resizedBuffer => {
+ data = resizedBuffer;
+ addToScrapbook(id, "image/jpg", data);
+ })
+ .catch(err => {
+ handleError('Failed to resize image');
+ return;
+ });
+ } else {
+ data = Buffer.concat(data);
+ addToScrapbook(id, contentType, data);
+ }
+ });
+
+ res.on('error', (err) => {
+ handleError('Error downloading image');
+ });
+ }).on('error', (err) => {
+ handleError('Failed to fetch URL');
+ });
+ } catch (e) {
+ handleError(e.message);
+ }
+ }
+ } else {
+ var result = session_data.addToScrapbook(id, "image/jpg", request_headers.query.mediaData);
+ if (result) {
+ headers = `300 OK
Content-Type: text/html
wtv-expire-all: wtv-author:/scrapbook
Location: wtv-author:/scrapbook
wtv-visit: wtv-author:/scrapbook`;
- } else {
- var errpage = wtvshared.doErrorPage(500, "Internal Server Error", "Failed to add scrapbook item.");
- headers = errpage[0];
- data = errpage[1];
+ } else {
+ handleError('Failed to add image to scrapbook');
+ }
}
}
\ No newline at end of file
diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-author/scrapbook.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-author/scrapbook.js
index af667565..2d5102c6 100644
--- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-author/scrapbook.js
+++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-author/scrapbook.js
@@ -1,12 +1,14 @@
var minisrv_service_file = true;
-var files = session_data.pagestore.listScrapbook();
-var dir = session_data.pagestore.scrapbookDir()
-var start = 0;
+var files = session_data.listScrapbook();
+var dir = session_data.scrapbookDir()
+var pageNum = parseInt(request_headers.query.pageNum || 1);
+var start = (pageNum - 1) * 6;
headers = `200 OK
Connection: Keep-Alive
-Content-Type: text/html`
+Content-Type: text/html
+wtv-expire-all: wtv-author:/scrapbook`
data = `
@@ -124,7 +126,38 @@ function StorageWarning() { }
|
- Your scrapbook
+ Your scrapbook `
+if (files.length > 6) {
+data += ` |
+
+
+`;
+if (pageNum > 1) {
+data += ` `
+} else {
+data += ` `
+}
+data += `
+ |
+ |
+${pageNum} of ${Math.ceil(files.length / 6)} |
+`;
+if (files.length > start + 6) {
+data += ` `
+
+} else {
+ data += ` `
+}
+ data += `
+ |
+ |
+
+ `
+}
+data += `
|
|
@@ -144,7 +177,7 @@ Choose Help for instructions.
if (request_headers.query.addMediaURL) {
data += "Choose an image to add to your web page.";
} else {
- data += "Choose one of your saved images to view it full size.";
+ data += `You are currently using ${session_data.getScrapbookUsagePercent()}% of your scrapbook storage space. Choose one of your saved images to view it full size.`;
}
}
data += `
@@ -176,8 +209,9 @@ if (files.length > 0) {
|
-`
- for (let i = start; i < Math.min(files.length, start + 12); i++) {
+
+`
+ for (let i = start; i < Math.min(files.length, start + 6); i++) {
url = "wtv-tricks:/view-scrapbook-image?id=" + files[i];
if (request_headers.query.addMediaURL) {
url = unescape(request_headers.query.addMediaURL) + "&scrapbookID=" + files[i];
@@ -188,8 +222,9 @@ data += `
-${i % 4 === 1 ? ' ' : ''}`
-data += `
+${(i - start + 1) % 3 === 0 ? ' | ' : ''}`
+ }
+data += `
|
@@ -207,7 +242,7 @@ data += `
|
`
-}
+
}
data += `
diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/add-to-scrapbook.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/add-to-scrapbook.js
index f94497b4..ab928b75 100644
--- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/add-to-scrapbook.js
+++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/add-to-scrapbook.js
@@ -7,83 +7,10 @@ function handleError(reason) {
sendToClient(socket, errpage[0], errpage[1]);
}
-if (!request_headers.query.url) {
+if (!request_headers.query.url && !request_headers.query.mediaPath) {
handleError('No URL provided');
} else {
- var url = request_headers.query.url;
- function isValidImageType(contentType, url) {
- // Check content-type header or file extension
- if (contentType) {
- return contentType === 'image/jpeg' || contentType == 'image/jpg' || contentType === 'image/gif';
- }
- return url.endsWith('.jpg') || url.endsWith('.jpeg') || url.endsWith('.gif');
- }
-
- try {
- const parsedUrl = new URL(url);
- const protocol = parsedUrl.protocol === 'https:' ? https : http;
-
- protocol.get(url, (res) => {
- if (res.statusCode !== 200) {
- handleError('URL does not exist or returned status ' + res.statusCode);
- res.resume();
- return;
- }
-
- const contentType = res.headers['content-type'];
- const contentLength = parseInt(res.headers['content-length'], 10);
-
- if (!isValidImageType(contentType, url)) {
- handleError('URL is not a JPEG or GIF image');
- res.resume();
- return;
- }
-
- if (contentLength && contentLength > 1024 * 1024) {
- handleError('Image is larger than 1MB');
- res.resume();
- return;
- }
-
- let data = [];
- let totalLength = 0;
- res.on('data', (chunk) => {
- totalLength += chunk.length;
- if (totalLength > 1024 * 1024) {
- handleError('Image is larger than 1MB');
- res.destroy();
- return;
- }
- data.push(chunk);
- });
-
- res.on('end', () => {
- if (totalLength > 1024 * 1024) return;
- data = Buffer.concat(data);
- var id = session_data.pagestore.getFreeScrapbookID();
- var result = session_data.pagestore.addToScrapbook(id, contentType, data);
- if (result) {
- var successScrapbook = new clientShowAlert({
- 'image': minisrv_config.config.service_logo,
- 'message': "The image has been added to your scrapbook. Would you like to view your scrapbook now?",
- 'buttonlabel1': "No",
- 'buttonaction1': "client:donothing",
- 'buttonlabel2': "Yes",
- 'buttonaction2': "wtv-author:/scrapbook",
- }).getURL();
- sendToClient(socket, {'Status': 302, 'Location': successScrapbook, 'wtv-visit': successScrapbook}, '');
- } else {
- handleError('Failed to add image to scrapbook');
- }
- });
-
- res.on('error', (err) => {
- handleError('Error downloading image');
- });
- }).on('error', (err) => {
- handleError('Failed to fetch URL');
- });
- } catch (e) {
- handleError(e.message);
- }
+ var mediaURL = request_headers.query.url || request_headers.query.mediaPath;
+ var targetURL = 'wtv-author:/scrapbook-add?mediaPath=' + encodeURIComponent(mediaURL);
+ sendToClient(socket, {'Status': 302, 'Location': targetURL, 'wtv-visit': targetURL}, '');
}
\ No newline at end of file
diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/view-scrapbook-image.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/view-scrapbook-image.js
index 6e1ece60..087ac9aa 100644
--- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/view-scrapbook-image.js
+++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/view-scrapbook-image.js
@@ -10,7 +10,7 @@ async function handleImage() {
if (!request_headers.query.id) {
handleError('No image ID specified');
} else {
- data = session_data.pagestore.getScrapbookImage(request_headers.query.id);
+ data = session_data.getScrapbookImage(request_headers.query.id);
if (!data) {
handleError('Image not found');
} else {
@@ -21,7 +21,7 @@ async function handleImage() {
data = await sharp(data).resize({ width, withoutEnlargement: true }).toBuffer();
}
headers = `200 OK
-Content-Type: ${session_data.pagestore.getScrapbookImageType(request_headers.query.id)}
+Content-Type: ${session_data.getScrapbookImageType(request_headers.query.id)}
Content-Length: ${data.length}`
sendToClient(socket, headers, data);
} catch (error) {
diff --git a/zefie_wtvp_minisrv/includes/classes/WTVAdmin.js b/zefie_wtvp_minisrv/includes/classes/WTVAdmin.js
index 600ac709..e64d3055 100644
--- a/zefie_wtvp_minisrv/includes/classes/WTVAdmin.js
+++ b/zefie_wtvp_minisrv/includes/classes/WTVAdmin.js
@@ -1,3 +1,5 @@
+const WTVClientSessionData = require('./WTVClientSessionData.js');
+
class WTVAdmin {
fs = require('fs');
@@ -18,6 +20,12 @@ class WTVAdmin {
REASON_EXISTS = 4
REASON_NONEXIST = 5
+ /**
+ * Creates an instance of WTVAdmin.
+ * @param {Object} minisrv_config
+ * @param {WTVClientSessionData} wtvclient
+ * @param {string} service_name
+ */
constructor(minisrv_config, wtvclient, service_name) {
this.minisrv_config = minisrv_config;
var { WTVShared } = require("./WTVShared.js");
@@ -36,6 +44,12 @@ class WTVAdmin {
this.service_name = service_name;
}
+ /**
+ * Bans a specific SSID.
+ * @param {string} ssid The SSID to ban
+ * @param {string} admin_ssid The SSID of the admin requesting the ban
+ * @returns {number} The result of the ban operation
+ */
banSSID(ssid, admin_ssid = null) {
if (ssid == admin_ssid) {
return this.REASON_NOSELF;
@@ -58,22 +72,27 @@ class WTVAdmin {
}
}
+ /**
+ * Unbans a specific SSID.
+ * @param {string} ssid The SSID to unban
+ * @returns {number} The result of the unban operation
+ */
unbanSSID(ssid) {
var config_changed = false;
- var fake_config = wtvshared.getUserConfig();
+ var fake_config = this.wtvshared.getUserConfig();
if (!fake_config.config) fake_config.config = {};
if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = [];
- if (typeof request_headers.query.ssid === 'string') {
+ if (typeof ssid === 'string') {
Object.keys(fake_config.config.ssid_block_list).forEach(function (k) {
- if (fake_config.config.ssid_block_list[k].toLowerCase() == request_headers.query.ssid.toLowerCase()) {
+ if (fake_config.config.ssid_block_list[k].toLowerCase() == ssid.toLowerCase()) {
fake_config.config.ssid_block_list.splice(k, 1);
config_changed = true
}
});
} else {
Object.keys(fake_config.config.ssid_block_list).forEach(function (k) {
- Object.keys(request_headers.query.ssid).forEach(function (j) {
- if (fake_config.config.ssid_block_list[k].toLowerCase() == request_headers.query.ssid[j].toLowerCase()) {
+ Object.keys(ssid).forEach(function (j) {
+ if (fake_config.config.ssid_block_list[k].toLowerCase() == ssid[j].toLowerCase()) {
fake_config.config.ssid_block_list.splice(k, 1);
config_changed = true
}
@@ -89,38 +108,11 @@ class WTVAdmin {
}
}
- ip2long(ip) {
- var 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]);
- power *= 256;
- }
- return iplong;
- }
- else return -1;
- }
-
- isInSubnet(ip, subnet) {
- // If subnet is given as a single IP address (no CIDR notation)
- if (subnet.indexOf('/') === -1) {
- return this.ip2long(ip) === this.ip2long(subnet);
- } else {
- // Expect subnet in format "base_ip/prefix_length"
- let parts = subnet.match(/^(.*?)\/(\d{1,2})$/);
- if (parts && (this.ip2long(parts[1]) >= 0)) {
- let base_ip = this.ip2long(parts[1]);
- let prefixLength = parseInt(parts[2]);
- let freedom = Math.pow(2, 32 - prefixLength);
- return (this.ip2long(ip) >= base_ip) && (this.ip2long(ip) < base_ip + freedom);
- }
- }
- return false;
- }
-
+ /**
+ * Rejects a connection attempt based on the client's address or SSID.
+ * @param {boolean} reason_is_ssid If true, the rejection is based on SSID, otherwise on IP address
+ * @returns {string} The reason for rejecting the connection
+ */
rejectConnection(reason_is_ssid) {
var rejectReason;
if (this.pcservices) {
@@ -138,6 +130,11 @@ class WTVAdmin {
return rejectReason;
}
+ /**
+ * Checks if the provided password matches the service's password.
+ * @param {string} password The password to check
+ * @returns {boolean} True if the password matches, false otherwise
+ */
checkPassword(password) {
if (this.pcservices) {
if (this.minisrv_config.config.pc_admin.password) {
@@ -156,6 +153,10 @@ class WTVAdmin {
}
}
+ /**
+ * Lists all registered SSIDs.
+ * @returns {Array} An array of arrays, each containing the SSID and its associated account information
+ */
listRegisteredSSIDs() {
var search_dir = this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts");
var self = this;
@@ -169,6 +170,11 @@ class WTVAdmin {
return out;
}
+ /**
+ * Checks if the current client is authorized to access the service.
+ * @param {boolean} justchecking If true, only checks authorization without rejecting the connection
+ * @return {boolean} True if authorized, false otherwise
+ */
isAuthorized(justchecking = false) {
var allowed_ssid = false;
var allowed_ip = false;
@@ -186,7 +192,7 @@ class WTVAdmin {
if (ssid == self.wtvclient.ssid) {
allowed_ssid = true;
Object.keys(self.minisrv_config.services[self.service_name].authorized_ssids[k]).forEach(function (j) {
- if (self.isInSubnet(self.clientAddress, self.minisrv_config.services[self.service_name].authorized_ssids[k][j])) {
+ if (self.wtvshared.isInSubnet(self.clientAddress, self.minisrv_config.services[self.service_name].authorized_ssids[k][j])) {
if (allowed_ip) return;
allowed_ip = true;
}
@@ -206,7 +212,7 @@ class WTVAdmin {
var self = this;
Object.keys(this.minisrv_config.config.pc_admin.ip_whitelist).forEach(function (k) {
if (allowed_ip) return;
- allowed_ip = self.isInSubnet(self.clientAddress, self.minisrv_config.config.pc_admin.ip_whitelist[k]);
+ allowed_ip = self.wtvshared.isInSubnet(self.clientAddress, self.minisrv_config.config.pc_admin.ip_whitelist[k]);
});
}
}
@@ -219,6 +225,12 @@ class WTVAdmin {
}
}
+ /**
+ * Gets the account information for a specific username.
+ * @param {string} username The username to get the account information for
+ * @param {string|null} directory The directory to search for user accounts, defaults to the session store directory
+ * @returns {Object|null} An object containing account information if the username is found, null otherwise
+ */
getAccountInfo(username, directory = null) {
var search_dir = this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts");
var account_data = null;
@@ -255,6 +267,11 @@ class WTVAdmin {
return null;
}
+ /**
+ * Gets the account information for a specific SSID.
+ * @param {string} ssid The SSID to get the account information for
+ * @returns {Object|boolean} An object containing account information if the SSID is registered, false otherwise
+ */
getAccountInfoBySSID(ssid) {
var account_info = {};
var userSession = new this.WTVClientSessionData(this.minisrv_config, ssid);
@@ -278,13 +295,22 @@ class WTVAdmin {
else return false;
}
-
+ /**
+ * Gets the account session data for a specific SSID.
+ * @param {string} ssid The SSID to get the account data for
+ * @returns {WTVClientSessionData} The session data object for the account
+ */
getAccountBySSID(ssid) {
var userSession = new this.WTVClientSessionData(this.minisrv_config, ssid);
userSession.user_id = 0;
return userSession;
}
+ /**
+ * Checks if a specific SSID is banned.
+ * @param {string} ssid The SSID to check
+ * @returns {boolean} True if the SSID is banned, false otherwise
+ */
isBanned(ssid) {
var self = this;
var isBanned = false;
diff --git a/zefie_wtvp_minisrv/includes/classes/WTVAuthor.js b/zefie_wtvp_minisrv/includes/classes/WTVAuthor.js
index abb87738..a4b0bebe 100644
--- a/zefie_wtvp_minisrv/includes/classes/WTVAuthor.js
+++ b/zefie_wtvp_minisrv/includes/classes/WTVAuthor.js
@@ -10,7 +10,6 @@ class WTVAuthor {
wtvclient = null;
pageFileExt = ".page";
pagestore_dir = null;
- scrapbook_dir = null;
pageArr = [];
blockArr = [];
header = null;
@@ -68,18 +67,6 @@ class WTVAuthor {
}
return true;
}
-
- scrapbookExists() {
- if (!this.isguest) {
- if (this.scrapbook_dir === null) {
- var userstore_dir = this.wtvclient.getUserStoreDirectory();
- var store_dir = "Scrapbook" + this.path.sep;
- this.scrapbook_dir = userstore_dir + store_dir;
- }
- }
- return this.fs.existsSync(this.scrapbook_dir);
- }
-
createPagestore() {
if (this.pagestoreExists() === false) {
@@ -91,92 +78,6 @@ class WTVAuthor {
return false;
}
- createScrapbook() {
- if (this.scrapbookExists() === false) {
- try {
- if (!this.fs.existsSync(this.scrapbook_dir)) this.fs.mkdirSync(this.scrapbook_dir, { recursive: true });
- return true;
- } catch { }
- }
- return false
- }
-
- scrapbookDir() {
- if (this.scrapbookExists() === false) {
- this.createScrapbook();
- }
- return this.scrapbook_dir;
- }
-
- listScrapbook() {
- if (this.scrapbookExists() === false) {
- this.createScrapbook();
- }
- const files = this.fs.readdirSync(this.scrapbook_dir);
- const filteredFiles = files.filter(file => !file.endsWith('.meta'));
- return filteredFiles;
- }
-
- getFreeScrapbookID() {
- if (this.scrapbookExists() === false) {
- this.createScrapbook();
- }
- var id = 1;
- var files = this.fs.readdirSync(this.scrapbook_dir);
- if (files.length == 0) {
- return id;
- }
- files = files.map(file => parseInt(file.substr(0, file.indexOf('.'))));
- while (files.includes(id)) {
- id++;
- }
- return id;
- }
-
- getScrapbookImage(id) {
- if (this.scrapbookExists() === false) {
- this.createScrapbook();
- }
- var file = this.scrapbook_dir + id;
- if (this.fs.existsSync(file)) {
- return this.fs.readFileSync(file);
- }
- return null;
- }
-
- getScrapbookImageType(id) {
- if (this.scrapbookExists() === false) {
- this.createScrapbook();
- }
- var file = this.scrapbook_dir + id + ".meta";
- if (this.fs.existsSync(file)) {
- var meta = this.fs.readFileSync(file, 'utf8');
- try {
- var metaData = JSON.parse(meta);
- return metaData.contentType;
- } catch (e) {
- this.debug("getScrapbookImageType", "Error parsing metadata for image ID", id, e);
- }
- }
- return null;
- }
-
- addToScrapbook(filename, contentType, data) {
- try {
- if (this.scrapbookExists() === false) {
- this.createScrapbook();
- }
- var fileout = this.scrapbook_dir + filename;
- var fileout_meta = this.scrapbook_dir + filename + ".meta";
- this.fs.writeFileSync(fileout, data);
- this.fs.writeFileSync(fileout_meta, JSON.stringify({
- "contentType": contentType
- }));
- return true;
- } catch {}
- return false;
- }
-
createPage(style) {
this.pagestoreExists()
var pagestorepath = this.pagestore_dir;
diff --git a/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js b/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js
index 155fda22..5bccc731 100644
--- a/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js
+++ b/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js
@@ -16,6 +16,7 @@ class WTVClientSessionData {
mailstore = null;
favstore = null;
pagestore = null;
+ scrapbook_dir = null;
login_security = null;
capabilities = null;
session_storage = "";
@@ -108,6 +109,12 @@ class WTVClientSessionData {
}
}
+ /**
+ * Sets a ticket data value.
+ * @param {string} key The key of the ticket data
+ * @param {*} value The value to set
+ * @returns {boolean} True if the value was set successfully, false otherwise
+ */
setTicketData(key, value) {
if (this.data_store.wtvsec_login) this.data_store.wtvsec_login.setTicketData(key, value);
else return false;
@@ -115,16 +122,24 @@ class WTVClientSessionData {
return true;
}
+ /**
+ * Retrieves ticket data by key.
+ * @param {string} key The key of the ticket data
+ * @return {*} The value associated with the key, or false if not found
+ */
getTicketData(key) {
if (this.data_store.wtvsec_login) return this.data_store.wtvsec_login.getTicketData(key);
-
return false;
}
+ /**
+ * Deletes ticket data by key.
+ * @param {string} key The key of the ticket data to delete
+ * @return {boolean} True if the data was deleted successfully, false otherwise
+ */
deleteTicketData(key) {
if (this.data_store.wtvsec_login) this.data_store.wtvsec_login.deleteTicketData(key);
else return false;
-
return true;
}
@@ -328,6 +343,136 @@ class WTVClientSessionData {
return (result === false) ? false : true;
}
+ scrapbookExists() {
+ if (!this.isguest) {
+ if (this.scrapbook_dir === null) {
+ var userstore_dir = this.getUserStoreDirectory();
+ var store_dir = "Scrapbook" + this.path.sep;
+ this.scrapbook_dir = userstore_dir + store_dir;
+ }
+ }
+ return this.fs.existsSync(this.scrapbook_dir);
+ }
+
+ createScrapbook() {
+ if (this.scrapbookExists() === false) {
+ try {
+ if (!this.fs.existsSync(this.scrapbook_dir)) this.fs.mkdirSync(this.scrapbook_dir, { recursive: true });
+ return true;
+ } catch { }
+ }
+ return false
+ }
+
+ scrapbookDir() {
+ if (this.scrapbookExists() === false) {
+ this.createScrapbook();
+ }
+ return this.scrapbook_dir;
+ }
+
+ listScrapbook() {
+ if (this.scrapbookExists() === false) {
+ this.createScrapbook();
+ }
+ const files = this.fs.readdirSync(this.scrapbook_dir);
+ const filteredFiles = files.sort(function(a, b) {
+ return a.localeCompare(b, undefined, {
+ numeric: true,
+ sensitivity: 'base'
+ });
+ }).filter(file => !file.endsWith('.meta'));
+ return filteredFiles;
+ }
+
+ getFreeScrapbookID() {
+ if (this.scrapbookExists() === false) {
+ this.createScrapbook();
+ }
+ var id = 1;
+ var files = this.fs.readdirSync(this.scrapbook_dir);
+ if (files.length == 0) {
+ return id;
+ }
+ files = files.map(file => parseInt(file.substr(0, file.indexOf('.'))));
+ while (files.includes(id)) {
+ id++;
+ }
+ return id;
+ }
+
+ getScrapbookUsage() {
+ if (this.scrapbookExists() === false) {
+ this.createScrapbook();
+ }
+ var total_size = 0;
+ var files = this.fs.readdirSync(this.scrapbook_dir);
+ files.forEach(file => {
+ if (!file.endsWith('.meta')) {
+ var file_path = this.scrapbook_dir + file;
+ if (this.fs.existsSync(file_path)) {
+ total_size += this.fs.statSync(file_path).size;
+ }
+ }
+ });
+ return total_size;
+ }
+
+ getScrapbookUsagePercent() {
+ if (this.scrapbookExists() === false) {
+ this.createScrapbook();
+ }
+ var total_size = this.getScrapbookUsage();
+ var max_size = this.minisrv_config.config.user_accounts.scrapbook_storage * 1024 * 1024; // convert to bytes
+ if (max_size <= 0) return 0; // no storage limit set
+ var usage_percent = (total_size / max_size) * 100;
+ return Math.round(usage_percent, 2);
+ }
+
+ getScrapbookImage(id) {
+ if (this.scrapbookExists() === false) {
+ this.createScrapbook();
+ }
+ var file = this.scrapbook_dir + id;
+ if (this.fs.existsSync(file)) {
+ return this.fs.readFileSync(file);
+ }
+ return null;
+ }
+
+ getScrapbookImageType(id) {
+ if (this.scrapbookExists() === false) {
+ this.createScrapbook();
+ }
+ var file = this.scrapbook_dir + id + ".meta";
+ if (this.fs.existsSync(file)) {
+ var meta = this.fs.readFileSync(file, 'utf8');
+ try {
+ var metaData = JSON.parse(meta);
+ return metaData.contentType;
+ } catch (e) {
+ this.debug("getScrapbookImageType", "Error parsing metadata for image ID", id, e);
+ }
+ }
+ return null;
+ }
+
+ addToScrapbook(filename, contentType, data) {
+ try {
+ if (this.scrapbookExists() === false) {
+ this.createScrapbook();
+ }
+ var fileout = this.scrapbook_dir + filename;
+ var fileout_meta = this.scrapbook_dir + filename + ".meta";
+ this.fs.writeFileSync(fileout, data);
+ this.fs.writeFileSync(fileout_meta, JSON.stringify({
+ "contentType": contentType
+ }));
+ return true;
+ } catch {}
+ return false;
+ }
+
/**
* Retrieves a file from the user store
* @param {string} path Path relative to the User File Store
@@ -373,12 +518,23 @@ class WTVClientSessionData {
return Object.keys(this.session_store.cookies).length || 0;
}
+ /**
+ * Resets the user cookies
+ */
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.wtvshared.getUTCTime(365 * 86400000), "cookie_type=chocolatechip");
}
+ /**
+ * Adds a cookie to the user's session store
+ * @param {string|object} domain Domain for the cookie, or an object with cookie data
+ * @param {string|null} path Path for the cookie, defaults to null
+ * @param {string|null} expires Expiration date for the cookie, defaults to null
+ * @param {string|null} data Data for the cookie, defaults to null
+ * @return {boolean} True if the cookie was added successfully, false otherwise
+ */
addCookie(domain, path = null, expires = null, data = null) {
if (!this.checkCookies()) this.resetCookies();
if (!domain) return false;
@@ -413,6 +569,12 @@ class WTVClientSessionData {
return true;
}
+ /**
+ * Retrieves a cookie from the user's session store
+ * @param {string} domain Domain of the cookie
+ * @param {string} path Path of the cookie
+ * @return {object|false} Cookie data if found, false otherwise
+ */
getCookie(domain, path) {
if (!this.checkCookies()) this.resetCookies();
var self = this;
@@ -431,6 +593,12 @@ class WTVClientSessionData {
return result;
}
+ /**
+ * Retrieves a cookie string from the user's session store
+ * @param {string} domain Domain of the cookie
+ * @param {string} path Path of the cookie
+ * @return {string|false} Cookie string if found, false otherwise
+ */
getCookieString(domain, path) {
var cookie_data = this.getCookie(domain, path);
/*
@@ -443,6 +611,12 @@ class WTVClientSessionData {
return cookie_data.cookie;
}
+ /**
+ * Deletes a cookie from the user's session store
+ * @param {string|object} domain Domain of the cookie, or an object with cookie data
+ * @param {string|null} path Path of the cookie, defaults to null
+ * @return {boolean} True if the cookie was deleted successfully, false otherwise
+ */
deleteCookie(domain, path = null) {
var result = false;
if (!this.checkCookies()) {
@@ -472,12 +646,20 @@ class WTVClientSessionData {
return result;
}
+ /**
+ * Checks if there are any cookies stored in the session
+ * @return {boolean} True if there are cookies, false otherwise
+ */
checkCookies() {
if (!this.session_store.cookies) return false;
else if (this.session_store.cookies == []) return false;
return true;
}
+ /**
+ * Lists all cookies in the user's session store
+ * @return {string} String representation of all cookies, each cookie separated by a null character
+ */
listCookies() {
if (!this.checkCookies()) this.resetCookies();
var outstring = "";
diff --git a/zefie_wtvp_minisrv/includes/classes/WTVPCAdmin.js b/zefie_wtvp_minisrv/includes/classes/WTVPCAdmin.js
deleted file mode 100644
index c1705649..00000000
--- a/zefie_wtvp_minisrv/includes/classes/WTVPCAdmin.js
+++ /dev/null
@@ -1,177 +0,0 @@
-class WTVPCAdmin {
-
- fs = require('fs');
- path = require('path');
- minisrv_config = [];
- wtvr = null;
- wtvshared = null;
- socket = null;
- WTVClientSessionData = require("./WTVClientSessionData.js");
- service_name = "wtv-admin";
-
- constructor(minisrv_config, socket, service_name) {
- this.minisrv_config = minisrv_config;
- var { WTVShared } = require("./WTVShared.js");
- var WTVRegister = require("./WTVRegister.js");
- this.socket = socket;
- this.wtvr = new WTVRegister(minisrv_config);
- this.clientAddress = socket.remoteAddress;
- this.service_name = service_name;
- }
-
-
- ip2long(ip) {
- var 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]);
- power *= 256;
- }
- return iplong;
- }
- else return -1;
- }
-
- 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);
- } 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);
- }
- }
- return false;
- }
-
- rejectConnection() {
- var rejectReason;
- rejectReason = this.clientAddress + " is not in the whitelist.";
- console.log(" * Request from IP", this.clientAddress, "for PC Services Admin, but that IP is not authorized.");
- return rejectReason;
- }
-
- checkPassword(password) {
- if (this.minisrv_config.config.pc_admin.password) {
- return (password == this.minisrv_config.config.pc_admin.password);
- } else {
- // no password set
- return true;
- }
- }
-
- isAuthorized(justchecking = false) {
- var allowed_ip = false;
- if (this.minisrv_config.config.pc_admin.ip_whitelist) {
- var self = this;
- Object.keys(this.minisrv_config.config.pc_admin.ip_whitelist).forEach(function (k) {
- allowed_ip = self.isInSubnet(self.clientAddress, self.minisrv_config.config.pc_admin.ip_whitelist[k]);
- });
- }
- if (justchecking) {
- return allowed_ip;
- } else {
- return allowed_ip ? true : this.rejectConnection();
- }
- }
-
- listRegisteredSSIDs() {
- var search_dir = this.minisrv_config.config.SessionStore + this.path.sep + "accounts";
- var self = this;
- var out = [];
- this.fs.readdirSync(search_dir).forEach(file => {
- if (self.fs.lstatSync(search_dir + self.path.sep + file).isDirectory()) {
- var user = self.getAccountInfoBySSID(file);
- out.push([file,user]);
- }
- });
- return out;
- }
-
- getAccountInfo(username, directory = null) {
- var search_dir = this.minisrv_config.config.SessionStore + this.path.sep + "accounts";
- var account_data = null;
- var self = this;
- if (directory) search_dir = directory;
- this.fs.readdirSync(search_dir).forEach(file => {
- if (self.fs.lstatSync(search_dir + self.path.sep + file).isDirectory() && account_data === null) {
- account_data = self.getAccountInfo(username, search_dir + self.path.sep + file);
- }
- if (account_data !== null) return;
- if (!file.match(/.*\.json/ig)) return;
- try {
- var temp_session_data_file = self.fs.readFileSync(search_dir + self.path.sep + file, 'Utf8');
- var temp_session_data = JSON.parse(temp_session_data_file);
-
- if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) {
- account_data = [temp_session_data, (search_dir + self.path.sep + file).replace(this.minisrv_config.config.SessionStore + this.path.sep + "accounts", "").split(this.path.sep)[1]];
- }
- } catch (e) {
- console.error(" # Error parsing Session Data JSON", search_dir + self.path.sep + file, e);
- }
- });
- if (account_data !== null) {
- if (account_data.ssid) return account_data;
- var account_info = {};
- account_info.ssid = account_data[1];
- account_info.username = account_data[0].subscriber_username;
- account_info.user_id = account_data[0].subscriber_userid;
- var userSession = new this.WTVClientSessionData(this.minisrv_config, account_info.ssid);
- userSession.user_id = 0;
- account_info.account_users = userSession.listPrimaryAccountUsers();
- return account_info;
- }
- return null;
- }
-
- getAccountInfoBySSID(ssid) {
- var account_info = {};
- var userSession = new this.WTVClientSessionData(this.minisrv_config, ssid);
- userSession.user_id = 0;
- if (userSession.isRegistered(false)) {
- account_info.ssid = ssid;
- account_info.account_users = userSession.listPrimaryAccountUsers();
- if (account_info.account_users) {
- if (account_info.account_users['subscriber']) {
- account_info.username = account_info.account_users['subscriber'].subscriber_username;
- } else {
- account_info.username = account_info.account_users[0];
- }
- } else {
- account_info.username = account_info.account_users[0];
- }
-
- account_info.user_id = 0;
- return account_info;
- }
- else return false;
- }
-
-
- getAccountBySSID(ssid) {
- var userSession = new this.WTVClientSessionData(this.minisrv_config, ssid);
- userSession.user_id = 0;
- return userSession;
- }
-
- isBanned(ssid) {
- var self = this;
- var isBanned = false;
- if (this.minisrv_config.config.ssid_block_list) {
- Object.keys(this.minisrv_config.config.ssid_block_list).forEach(function (k) {
- if (self.minisrv_config.config.ssid_block_list[k] == ssid) {
- isBanned = true;
- }
- });
- }
- return isBanned;
- }
-}
-
-module.exports = WTVPCAdmin;
diff --git a/zefie_wtvp_minisrv/includes/classes/WTVRegister.js b/zefie_wtvp_minisrv/includes/classes/WTVRegister.js
index 3ad0192b..70fde42c 100644
--- a/zefie_wtvp_minisrv/includes/classes/WTVRegister.js
+++ b/zefie_wtvp_minisrv/includes/classes/WTVRegister.js
@@ -21,12 +21,22 @@ class WTVRegister {
}
}
+ /**
+ * Checks if the username is valid according to the configured rules.
+ * @param {string} username The username to check
+ * @returns {boolean} True if the username is valid, false otherwise
+ */
checkUsernameSanity(username) {
var regex_str = "^([A-Za-z0-9-\_]{" + this.minisrv_config.config.user_accounts.min_username_length + "," + this.minisrv_config.config.user_accounts.max_username_length + "})$";
var regex = new RegExp(regex_str);
return regex.test(username);
}
+ /**
+ * Checks if the SSID is already registered.
+ * @param {string} ssid The SSID to check
+ * @returns {boolean} True if the SSID is available for registration, false if it already has an account registered.
+ */
checkSSIDAvailable(ssid) {
var directory = (directory) ? directory : this.session_store_dir + this.path.sep + "accounts";
var available = true;
@@ -41,6 +51,12 @@ class WTVRegister {
return available;
}
+ /**
+ * Checks if the username is already taken.
+ * @param {string} username The username to check
+ * @param {string} directory The directory to search for user accounts
+ * @returns {boolean} True if the username is available, false if it is already taken
+ */
checkUsernameAvailable(username, directory = null) {
var self = this;
var return_val = false;
diff --git a/zefie_wtvp_minisrv/includes/classes/WTVSSL.js b/zefie_wtvp_minisrv/includes/classes/WTVSSL.js
index ccbbff20..d3ea0906 100644
--- a/zefie_wtvp_minisrv/includes/classes/WTVSSL.js
+++ b/zefie_wtvp_minisrv/includes/classes/WTVSSL.js
@@ -7,7 +7,7 @@ class WTVSSL {
getCACert() {
// return the CA cert
- const caCertFile = this.wtvshared.getServiceDep("https/ca.der")
+ const caCertFile = this.wtvshared.getServiceDep("https/ca.der", true)
if (!this.wtvshared.fs.existsSync(caCertFile)) {
throw new Error("CA certificate file not found");
}
diff --git a/zefie_wtvp_minisrv/includes/classes/WTVSec.js b/zefie_wtvp_minisrv/includes/classes/WTVSec.js
index f32ce9a6..49ef766e 100644
--- a/zefie_wtvp_minisrv/includes/classes/WTVSec.js
+++ b/zefie_wtvp_minisrv/includes/classes/WTVSec.js
@@ -2,6 +2,7 @@ const CryptoJS = require('crypto-js');
const endianness = require('endianness');
var RC4 = require('rc4-crypto');
var crypto = require('crypto');
+var WTVShared = require("./WTVShared.js")['WTVShared'];
/**
* Javascript implementation of WTVP Security
@@ -34,6 +35,7 @@ class WTVSec {
RC4Session = new Array();
minisrv_config = [];
update_ticket = false;
+ wtvshared = null;
ticket_store = {};
/**
@@ -46,6 +48,7 @@ class WTVSec {
*/
constructor(minisrv_config, wtv_incarnation = 1) {
this.minisrv_config = minisrv_config;
+ this.wtvshared = new WTVShared(minisrv_config);
this.initial_shared_key = CryptoJS.enc.Base64.parse(this.minisrv_config.config.keys.initial_shared_key);
if (this.initial_shared_key.sigBytes === 8) {
@@ -58,7 +61,6 @@ class WTVSec {
/**
* Set the wtv-incarnation for this instance
- *
* @param {Number} wtv_incarnation
*/
set_incarnation(wtv_incarnation) {
@@ -77,16 +79,16 @@ class WTVSec {
/**
* Clones a WordArray to allow modification without referencing its original
- * @param {CryptoJS.lib.WordArray} wa
- *
+ * @param {CryptoJS.lib.WordArray} wordArray
* @returns {CryptoJS.lib.WordArray}
*/
- DuplicateWordArray(wa) {
- return CryptoJS.lib.WordArray.create(this.wordArrayToBuffer(wa));
+ DuplicateWordArray(wordArray) {
+ return CryptoJS.lib.WordArray.create(this.wordArrayToBuffer(wordArray));
}
/**
* Prepares the wtv-ticket for this instance
+ * @returns {Base64} wtv-ticket
*/
PrepareTicket() {
// store last challenge response in ticket
@@ -110,20 +112,8 @@ class WTVSec {
return this.ticket_b64;
}
- tryDecodeJSON(json_string) {
- var out;
- try {
- out = JSON.parse(json_string);
- } catch (e) {
- console.log(e);
- out = {};
- }
- return out;
- }
-
/**
* Decodes a wtv-ticket to set up this instance
- *
* @param {Base64} ticket_b64
*/
DecodeTicket(ticket_b64) {
@@ -146,7 +136,7 @@ class WTVSec {
var 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);
- this.ticket_store = this.tryDecodeJSON(ticket_data_dec);
+ this.ticket_store = this.wtvshared.tryDecodeJSON(ticket_data_dec);
} else {
this.ticket_store = {};
}
@@ -155,6 +145,11 @@ class WTVSec {
if (this.minisrv_config.config.debug_flags.debug) console.log(" * Decoded session from wtv-ticket with ticket_store:", this.ticket_store);
}
+ /**
+ * Gets the ticket data for this instance
+ * @param {string} key The key of the ticket data to retrieve
+ * @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;
else if (key === null) return this.ticket_store;
@@ -162,6 +157,11 @@ class WTVSec {
else return null;
}
+ /**
+ * Sets the ticket data for this instance
+ * @param {string} key The key of the ticket data to set
+ * @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 = {};
@@ -170,6 +170,10 @@ class WTVSec {
this.update_ticket = true;
}
+ /**
+ * Deletes the ticket data for this instance
+ * @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') {
@@ -234,7 +238,6 @@ class WTVSec {
/**
* Generates a wtv-challenge for this instance
- *
* @returns {Base64} wtv-challenge
*/
IssueChallenge() {
@@ -246,7 +249,7 @@ class WTVSec {
* bytes 64 - 80: session key 2 used in RC4 encryption triggered by SECURE ON
* bytes 80 - 88: new key for future challenges
* bytes 88 - 104: MD5 of 8 - 88
- * bytes 104 - 112: padding.not important
+ * bytes 104 - 112: padding. seemingly not important, but by default is 8 bytes of 0x08
*/
const challenge_id = CryptoJS.lib.WordArray.random(8);
const echo_me = CryptoJS.lib.WordArray.random(40);
@@ -280,8 +283,7 @@ class WTVSec {
/**
* convert a CryptoJS.lib.WordArray to a Javascript Buffer
* @param {CryptoJS.lib.WordArray} wordArray
- *
- * #returns {Buffer} JS Buffer object
+ * @returns {Buffer} JS Buffer object
*/
wordArrayToBuffer(wordArray) {
if (wordArray) return new Buffer.from(wordArray.toString(CryptoJS.enc.Hex), 'hex');
@@ -291,7 +293,6 @@ class WTVSec {
/**
* Starts an encryption session
* @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);
@@ -326,7 +327,6 @@ class WTVSec {
* RC4 Encrypt data
* @param {Number} keynum Which key to use (0 = k1, 1 = k2)
* @param {CryptoJS.lib.WordArray|ArrayBuffer|Buffer} data Data to encrypt
- *
* @returns {ArrayBuffer} Encrypted data
*/
Encrypt(keynum, data) {
@@ -357,8 +357,8 @@ class WTVSec {
* RC4 Decrypt data
* @param {Number} keynum Which key to use (0 = k1, 1 = k2)
* @param {CryptoJS.lib.WordArray|ArrayBuffer|Buffer} data Data to decrypt
- *
* @returns {ArrayBuffer} Decrypted data
+ * @notice This function is an alias for Encrypt, as WTVSec uses the same method for both encryption and decryption.
*/
Decrypt(keynum, data) {
return this.Encrypt(keynum, data)
diff --git a/zefie_wtvp_minisrv/includes/classes/WTVShared.js b/zefie_wtvp_minisrv/includes/classes/WTVShared.js
index 1040afb9..563c4806 100644
--- a/zefie_wtvp_minisrv/includes/classes/WTVShared.js
+++ b/zefie_wtvp_minisrv/includes/classes/WTVShared.js
@@ -23,6 +23,12 @@ class WTVShared {
minisrv_config = [];
+ /**
+ * Constructor for WTVShared class
+ * @param {object} minisrv_config The configuration object for the minisrv
+ * @param {boolean} quiet If true, suppresses console output
+ * @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);
else this.minisrv_config = minisrv_config;
@@ -47,7 +53,120 @@ class WTVShared {
}
}
+ /**
+ * Converts an IP address to a hexadecimal string (WTV)
+ * @param {string} ip The IP address to convert
+ * @returns {string} The hexadecimal representation of the IP address
+ * @throws {Error} If the IP address is invalid
+ */
+ ipToHex(ip) {
+ const parts = ip.split('.');
+ if (parts.length !== 4) {
+ throw new Error('Invalid IP address');
+ }
+ let num = 0;
+ for (let i = 0; i < 4; i++) {
+ const part = parseInt(parts[i], 10);
+ if (part < 0 || part > 255) {
+ throw new Error('Invalid IP address');
+ }
+ num = (num << 8) | part;
+ }
+ // Convert to unsigned 32-bit number before converting to hex
+ return "0x" + (num >>> 0).toString(16).toUpperCase();
+ }
+ /**
+ * Converts an IP address to a long integer
+ * @param {string} ip The IP address to convert
+ * @returns {number} The long integer representation of the IP address, or -1 if the IP address is invalid
+ */
+ ip2long(ip) {
+ var 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]);
+ power *= 256;
+ }
+ return iplong;
+ }
+ else return -1;
+ }
+
+ /**
+ * Checks if an IP address is in a given subnet.
+ * @param {string} ip The IP address to check
+ * @param {string} subnet The subnet in CIDR notation
+ * @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);
+ } 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);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Converts a byte array to a 32-bit unsigned integer (big-endian)
+ * @param {Uint8Array} bytes The byte array
+ * @param {number} offset The offset within the byte array
+ * @returns {number} The 32-bit unsigned integer
+ */
+ toUint32(bytes, offset) {
+ return (
+ (bytes[offset] << 24) >>> 0 |
+ (bytes[offset + 1] << 16) |
+ (bytes[offset + 2] << 8) |
+ (bytes[offset + 3])
+ ) >>> 0;
+ }
+
+ /**
+ * Converts a 32-bit unsigned integer to a byte array (big-endian)
+ * @param {number} num The 32-bit unsigned integer
+ * @returns {number[]} The byte array representation
+ * @notice The output is an array of 4 bytes, each byte is an unsigned integer (0-255)
+ */
+ uint32ToBytes(num) {
+ return [
+ (num >>> 24) & 0xff,
+ (num >>> 16) & 0xff,
+ (num >>> 8) & 0xff,
+ num & 0xff,
+ ];
+ }
+
+ /**
+ * Tries to decode a JSON string
+ * @param {String} json_string
+ * @returns {Object} The decoded JSON object, or an empty object if decoding fails.
+ */
+ tryDecodeJSON(json_string) {
+ var out;
+ try {
+ out = JSON.parse(json_string);
+ } catch (e) {
+ console.log(e);
+ out = {};
+ }
+ return out;
+ }
+
+ /**
+ * Gets the box name based on the client ROM type
+ * @param {string} client_rom_type The client ROM type
+ * @returns {string} The box name
+ */
getBoxName(client_rom_type) {
switch (client_rom_type) {
case "bf0app":
@@ -94,14 +213,22 @@ class WTVShared {
return crc.toString(16).padStart(2, '0');
}
- // check if the SSID has a valid checksum
+ /**
+ * Checks if the SSID has a valid checksum
+ * @param {string} ssid The SSID to check
+ * @return {boolean} true if the SSID is valid, false if not
+ */
checkSSID(ssid) {
if (ssid.slice(-2) == this.getSSIDCRC(ssid))
return true;
return false;
}
-
+ /**
+ * Parses variables in a string, replacing %ServiceDeps% with the service dependencies
+ * @param {string} s The string to parse
+ * @returns {string} The parsed string
+ */
parseConfigVars(s) {
if (s.indexOf("%ServiceDeps%") >= 0)
return this.getServiceDep(s.replace("%ServiceDeps%", ""), true);
@@ -155,7 +282,6 @@ class WTVShared {
return src;
}
-
/**
* Checks if the user has been whitelisted for wtv-admin
* @param {object} wtvclient the clientSessionData object for the user
@@ -169,6 +295,11 @@ class WTVShared {
return result;
}
+ /**
+ * Parses a JSON string, removing unsupported comments
+ * @param {string} json JSON string to parse
+ * @returns {object} Parsed JSON object
+ */
parseJSON(json) {
if (typeof json !== 'string') json = json ? json.toString() : '';
let result = '';
@@ -206,7 +337,6 @@ class WTVShared {
return JSON.parse(result);
}
-
/**
* Attempts to convert val into a boolean
* @param {string,int,boolean} val
@@ -258,7 +388,6 @@ class WTVShared {
return entitized;
}
-
/**
* Attempts to sanitize HTML code to remove possible exploits when embedded in a WebTV Service
* @param {string} string The string to sanitize
@@ -332,7 +461,7 @@ class WTVShared {
* @param {string} headers Header string to convert
* @param {boolean} response If true, the headers are a response, otherwise they are a request
* @return {object} Headers object
- * */
+ */
headerStringToObj(headers, response = false) {
var inc_headers = 0;
var headers_obj = {};
@@ -421,7 +550,6 @@ class WTVShared {
return typeof str === 'string' && /^[\x00-\x7F]*$/.test(str);
}
-
/**
* Attempts to determine if the string contains HTML
* @param {string} str
@@ -432,7 +560,6 @@ class WTVShared {
return typeof str === 'string' && pattern.test(str);
}
-
/**
* Attempts to determine if the string is Base64 or not
* @param {string} str String to check
@@ -455,7 +582,11 @@ class WTVShared {
return new RegExp(regex, 'gi').test(str);
}
-
+ /**
+ * Decodes a UTF-8 string to a regular string
+ * @param {string} utf8String The UTF-8 encoded string to decode
+ * @returns {string} The decoded string
+ * */
utf8Decode(utf8String) {
if (typeof utf8String !== 'string') {
throw new TypeError("parameter 'utf8String' is not a string");
@@ -465,7 +596,11 @@ class WTVShared {
return textDecoder.decode(bytes);
}
-
+ /**
+ * Decodes a buffer containing ISO-8859-1 encoded text to a UTF-8 string
+ * @param {Buffer} buf The buffer to decode
+ * @returns {string} The decoded string
+ */
decodeBufferText(buf) {
var out = "";
out = this.utf8Decode(this.iconv.decode(Buffer.from(buf),'ISO-8859-1'));
@@ -487,7 +622,6 @@ class WTVShared {
return this.fixPathSlashes(check_path);
}
-
/**
* Detects if the client is in MiniBrowser mode
* @param {object} ssid_session
@@ -507,6 +641,12 @@ class WTVShared {
return (this.isMiniBrowser(ssid_session) || parseInt(ssid_session.get("wtv-system-version")) < minBuild) ? true : false;
}
+ /**
+ * Gets the user configuration from the user_config.json file
+ * @returns {object} User configuration object
+ * @notice If the file does not exist, it will return an empty object
+ * @notice If the file exists but cannot be parsed, it will terminate the process with an error message
+ */
getUserConfig() {
try {
var user_config_filename = this.getAbsolutePath("user_config.json", this.appdir);
@@ -577,7 +717,6 @@ class WTVShared {
return ssid_obj;
}
-
/**
* Alias for parseSSID, but just the manufacture info
* @param {string} ssid
@@ -589,6 +728,13 @@ class WTVShared {
else return this.parseSSID(ssid).manufacturer || null;
}
+ /**
+ * Reads the MiniSrv configuration files
+ * @param {boolean} user_config If true, also read user_config.json
+ * @param {boolean} notices If true, show notices
+ * @param {boolean} reload_notice If true, show reload notice
+ * @returns {object} The MiniSrv configuration object
+ */
readMiniSrvConfig(user_config = true, notices = true, reload_notice = false) {
const log = (msg) => {
if (notices || reload_notice) console.log(msg);
@@ -642,8 +788,15 @@ class WTVShared {
log(" *** Configuration successfully read.");
this.minisrv_config = minisrv_config;
return this.minisrv_config;
-}
+ }
+ /**
+ * Integrates the user configuration into the main configuration object
+ * @param {object} main The main configuration object
+ * @param {object} user The user configuration object
+ * @returns {object} The integrated configuration object
+ * @notice This will overwrite any existing keys in the main configuration with the user configuration
+ * */
integrateConfig(main, user) {
for (const key in user) {
if (typeof user[key] === 'object' && user[key] !== null && !Array.isArray(user[key])) {
@@ -655,7 +808,11 @@ class WTVShared {
return main;
}
-
+ /**
+ * Writes the user configuration to the user_config.json file
+ * @param {object} config Configuration object to write
+ * @returns {boolean} true if successful, false if not
+ */
writeToUserConfig(config) {
if (config) {
try {
@@ -684,7 +841,6 @@ class WTVShared {
return false;
}
-
/**
* Generates a random string
* @param {int} len desired generated string length
@@ -718,7 +874,6 @@ class WTVShared {
return result;
}
-
/**
* Any alias of generateString with optional special characters enabled as well
* @param {string} len desired generated string length
@@ -737,6 +892,13 @@ class WTVShared {
return this.minisrv_config;
}
+ /**
+ * Wraps a string to a specified length, breaking at whitespace
+ * @param {string} string The string to wrap
+ * @param {number} len The maximum line length
+ * @param {string} join The string to join the wrapped lines with (default is "\n")
+ * @returns {string} The wrapped string
+ */
lineWrap(string, len = 72, join = "\n") {
if (string.length <= len) return string;
@@ -771,6 +933,11 @@ class WTVShared {
return this.getFileLastModified(file).toUTCString();
}
+ /**
+ * Returns the Last-Modified date in a Date object with UTC time
+ * @param {string} file Path to a file
+ * @return {Date} Date object with UTC time
+ */
getFileLastModifiedUTCObj(file) {
return new Date(new Date().setUTCSeconds(this.getFileLastModified(file).getUTCSeconds()));
}
@@ -842,6 +1009,11 @@ class WTVShared {
return decoded;
}
+ /**
+ * Censors the SSID by replacing parts of it with asterisks
+ * @param {string} ssid The SSID to censor
+ * @returns {string} Censored SSID
+ * */
censorSSID(ssid) {
if (ssid) {
if (ssid.slice(0, 8) === "MSTVSIMU") {
@@ -875,7 +1047,11 @@ class WTVShared {
return obj;
}
-
+ /**
+ * Filters sensitive information from request logs
+ * @param {object} obj The request log object to filter
+ * @returns {object} Filtered request log object
+ * */
filterRequestLog(obj) {
const passwordRegex = /(^pass$|passw(or)?d)/i;
var newobj = this.cloneObj(obj); // Clone the object once at the beginning
@@ -891,8 +1067,11 @@ class WTVShared {
return newobj;
}
-
-
+ /**
+ * Decodes post data from a request log object
+ * @param {object} obj The request log object
+ * @returns {object} The request log object with decoded post data
+ */
decodePostData(obj) {
if (obj.post_data) {
const filterPasswords = this.minisrv_config.config.filter_passwords_in_logs === true;
@@ -924,11 +1103,12 @@ class WTVShared {
return obj;
}
-
// DON'T USE THIS
// Saved for reference until I come up with a better way
// If used, this will exceed the stack limit over time
unloadModule(moduleName) {
+ // Prevent usage
+ return;
// Search for the module in the require cache
let resolvedPath = require.resolve(moduleName);
@@ -973,8 +1153,6 @@ class WTVShared {
return this.path.resolve(path);
}
-
-
/**
* Returns a percentage
* @param {number} partialValue
@@ -1005,6 +1183,12 @@ class WTVShared {
return path.reverse().split(".")[0].reverse();
}
+ /**
+ * Gets a line from a file by line number
+ * @param {string} filename The file to read
+ * @param {number} lineNo The line number to read (0-indexed)
+ * @param {function} callback Callback function to call with the line or an error
+ * */
getLineFromFile(filename, lineNo, callback) {
let lineCount = 0;
const lineReader = this.readline.createInterface({
@@ -1034,8 +1218,6 @@ class WTVShared {
});
}
-
-
/**
* Checks if service is enabled or disabled in the config
* @param {string} service Service Name
@@ -1127,7 +1309,6 @@ class WTVShared {
return [headers, message];
}
-
/**
* Strips bad things from paths
* @param {string} base Base path
@@ -1169,7 +1350,6 @@ class WTVShared {
return pathModule.normalize(normalizedPath);
}
-
/**
* Makes sure an SSID is clean, and doesn't contain any exploitable characters
* @param {string} ssid
@@ -1181,12 +1361,22 @@ class WTVShared {
return ssid;
}
+ /**
+ * Makes sure a string path is clean, and doesn't contain any exploitable characters
+ * @param {string} path
+ * @returns {string|null} Sanitized Path
+ * */
makeSafeStringPath(path = "") {
path = path.replace(/[^\w]/g, "").replace(/\.\./g, "");
if (path.length == 0) path = null;
return path;
}
+ /**
+ * Unpacks a base64 compressed string
+ * @param {string|Buffer} data Base64 encoded compressed data
+ * @returns {string} Uncompressed string
+ * */
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');
@@ -1252,6 +1442,7 @@ class WTVShared {
}
return keys.indexOf(key);
}
+
/**
* Moves an object to the desired location in the object (reorder)
* @param {string|int} currentKey Name of the object Key to move or the index to move
@@ -1322,7 +1513,7 @@ class clientShowAlert {
* @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
+ * @param {string} sound Sound to play when showing the alert (default is "none")
*/
constructor(image = null, message = null, buttonlabel1 = null, buttonaction1 = null, buttonlabel2 = null, buttonaction2 = null, noback = null, sound = null) {
this.message = message;
diff --git a/zefie_wtvp_minisrv/includes/classes/WTVShenanigans.js b/zefie_wtvp_minisrv/includes/classes/WTVShenanigans.js
index 99813039..a7148a7d 100644
--- a/zefie_wtvp_minisrv/includes/classes/WTVShenanigans.js
+++ b/zefie_wtvp_minisrv/includes/classes/WTVShenanigans.js
@@ -9,14 +9,27 @@ class WTVShenanigans {
"DISABLE_HTML_SANITIZER": 5 // disables HTML Sanitizer, allowing all sorts of chaos in email/usenet posts and signatures
}
+ /**
+ * Creates an instance of WTVShenanigans.
+ * @param {Object} minisrv_config - The minisrv configuration object.
+ */
constructor(minisrv_config) {
this.minisrv_config = minisrv_config;
}
+ /**
+ * Returns the current shenanigans level set in the minisrv configuration.
+ * @returns {boolean|number} The shenanigans level, or false if shenanigans are disabled.
+ */
getShenanigansLevel() {
return this.minisrv_config.config.shenanigans;
}
+ /**
+ * Checks if a specific shenanigan is enabled based on the current shenanigans level.
+ * @param {number} value - The shenanigan level to check against.
+ * @returns {boolean} True if the shenanigan is enabled, false otherwise.
+ */
checkShenanigan(value) {
var level = this.getShenanigansLevel();
diff --git a/zefie_wtvp_minisrv/includes/classes/WTVTellyScript.js b/zefie_wtvp_minisrv/includes/classes/WTVTellyScript.js
index ecaff398..2d535ddf 100644
--- a/zefie_wtvp_minisrv/includes/classes/WTVTellyScript.js
+++ b/zefie_wtvp_minisrv/includes/classes/WTVTellyScript.js
@@ -1,4 +1,5 @@
const LZSS = require("./LZSS.js");
+const WTVShared = require("./WTVShared.js")['WTVShared'];
const WhitespaceInstruction = {
ADD_NONE: 0,
@@ -278,7 +279,7 @@ class WTVTellyScriptTokenizer {
this.tokenizeIdentifierOrConstant(checkSequence);
}
} else {
- // Not alphanumeric – try symbol sequence.
+ // Not alphanumeric � try symbol sequence.
this.index = currentIdx;
checkSequence = this.buildCheckSequence(ch, "^[\\-+=<>!\\|\\&]$");
if (this.replacements.has(checkSequence)) {
@@ -1048,7 +1049,7 @@ class WTVTellyScriptMinifier {
class WTVTellyScript {
// --- TellyScript Class ---
- /*
+ /**
* Constructs a new TellyScript object.
* @param {Uint8Array|string} data - The TellyScript data (either packed, tokenized, or raw).
* @param {number} dataState - One of TellyScriptState (default: PACKED).
@@ -1064,10 +1065,14 @@ class WTVTellyScript {
this.raw_data = null;
this.preprocessor_definitions = preprocessor_definitions;
this.version_minor = version_minor;
-
+ this.wtvshared = new WTVShared();
this.process(data, dataState);
}
+ /**
+ * Preprocesses the tellscript data based on the current preprocessor definitions.
+ * It handles directives like #ifdef, #ifndef, #if, #else, #endif.
+ */
preprocess() {
var definitions = this.preprocessor_definitions || {};
// Split input into lines (handling CRLF and LF)
@@ -1140,6 +1145,10 @@ class WTVTellyScript {
}
+ /**
+ * Minifies the TellyScript data.
+ * @returns {string} The minified TellyScript data.
+ */
minify() {
let minifier = new WTVTellyScriptMinifier();
this.raw_data = minifier.minify(this);
@@ -1148,51 +1157,18 @@ class WTVTellyScript {
this.pack();
}
- ipToHex(ip) {
- const parts = ip.split('.');
- if (parts.length !== 4) {
- throw new Error('Invalid IP address');
- }
- let num = 0;
- for (let i = 0; i < 4; i++) {
- const part = parseInt(parts[i], 10);
- if (part < 0 || part > 255) {
- throw new Error('Invalid IP address');
- }
- num = (num << 8) | part;
- }
- // Convert to unsigned 32-bit number before converting to hex
- return "0x" + (num >>> 0).toString(16).toUpperCase();
- }
-
setTemplateVars(service_name, dialin_number, DNS1IP, DNS2IP) {
this.raw_data = this.raw_data.replaceAll("%ServiceName%", service_name);
this.raw_data = this.raw_data.replaceAll("%DialinNumber%", dialin_number);
this.raw_data = this.raw_data.replaceAll("%DNSIP1%", DNS1IP);
this.raw_data = this.raw_data.replaceAll("%DNSIP2%", DNS2IP);
- this.raw_data = this.raw_data.replaceAll("%DNS1%", this.ipToHex(DNS1IP));
- this.raw_data = this.raw_data.replaceAll("%DNS2%", this.ipToHex(DNS2IP));
+ this.raw_data = this.raw_data.replaceAll("%DNS1%", this.wtvshared.ipToHex(DNS1IP));
+ this.raw_data = this.raw_data.replaceAll("%DNS2%", this.wtvshared.ipToHex(DNS2IP));
}
// --- Big Endian Converter Helpers ---
- toUint32(bytes, offset) {
- return (
- (bytes[offset] << 24) >>> 0 |
- (bytes[offset + 1] << 16) |
- (bytes[offset + 2] << 8) |
- (bytes[offset + 3])
- ) >>> 0;
- }
- uint32ToBytes(num) {
- return [
- (num >>> 24) & 0xff,
- (num >>> 16) & 0xff,
- (num >>> 8) & 0xff,
- num & 0xff,
- ];
- }
// --- CRC32 Calculation ---
@@ -1253,7 +1229,7 @@ class WTVTellyScript {
autoDetectState(data) {
if (data instanceof Uint8Array) {
if (data.length > 4) {
- const magic = this.toUint32(data, 0);
+ const magic = this.wtvshared.toUint32(data, 0);
if (magic === 0x414e4459) { // "ANDY"
this.tellyscript_type = TellyScriptType.ORIGINAL;
return TellyScriptState.PACKED;
@@ -1312,19 +1288,23 @@ class WTVTellyScript {
}
// --- Unpacking ---
+ /**
+ * Unpacks the packed TellyScript data
+ * @returns {Uint8Array} The unpacked TellyScript data
+ */
unpack() {
// Read header fields from the first 36 bytes.
const headerBytes = this.packed_data.slice(0, PACKED_TELLYSCRIPT_HEADER_SIZE);
this.packed_header = {
magic: String.fromCharCode(...headerBytes.slice(0, 4)),
- version_major: this.toUint32(headerBytes, 4),
- version_minor: this.toUint32(headerBytes, 8),
- script_id: this.toUint32(headerBytes, 12),
- script_mod: this.toUint32(headerBytes, 16),
- compressed_data_length: this.toUint32(headerBytes, 20),
- decompressed_data_length: this.toUint32(headerBytes, 24),
- decompressed_checksum: this.toUint32(headerBytes, 28),
- unknown1: this.toUint32(headerBytes, 32),
+ version_major: this.wtvshared.toUint32(headerBytes, 4),
+ version_minor: this.wtvshared.toUint32(headerBytes, 8),
+ script_id: this.wtvshared.toUint32(headerBytes, 12),
+ script_mod: this.wtvshared.toUint32(headerBytes, 16),
+ compressed_data_length: this.wtvshared.toUint32(headerBytes, 20),
+ decompressed_data_length: this.wtvshared.toUint32(headerBytes, 24),
+ decompressed_checksum: this.wtvshared.toUint32(headerBytes, 28),
+ unknown1: this.wtvshared.toUint32(headerBytes, 32),
};
// Extract compressed data from the remainder of the packed_data.
@@ -1337,21 +1317,30 @@ class WTVTellyScript {
return this.tokenized_data;
}
- // --- Detokenization ---
+ /**
+ * Detokenizes the tokenized TellyScript data
+ * @returns {string} The detokenized TellyScript data
+ */
detokenize() {
// Uses the previously defined TellyScriptDetokenizer class.
this.raw_data = new WTVTellyScriptDetokenizer(this.tokenized_data).detokenize();
return this.raw_data;
}
- // --- Tokenization ---
+ /**
+ * Tokenizes the raw TellyScript data
+ * @returns {Uint8Array} The tokenized TellyScript data
+ */
tokenize() {
// Uses the previously defined TellyScriptTokenizer class.
this.tokenized_data = new WTVTellyScriptTokenizer(this.raw_data).tokenize();
return this.tokenized_data;
}
- // --- Packing ---
+ /**
+ * Packs the tokenized TellyScript data into a packed format
+ * @returns {Uint8Array} The packed TellyScript data
+ */
pack() {
// Compress tokenized data using LZSS.
const comp = new LZSS();
@@ -1399,14 +1388,14 @@ class WTVTellyScript {
buffer[i] = header.magic.charCodeAt(i);
}
// Next fields: each 4 bytes in Big Endian order.
- buffer.set(this.uint32ToBytes(header.version_major), 4);
- buffer.set(this.uint32ToBytes(header.version_minor), 8);
- buffer.set(this.uint32ToBytes(header.script_id), 12);
- buffer.set(this.uint32ToBytes(header.script_mod), 16);
- buffer.set(this.uint32ToBytes(header.compressed_data_length), 20);
- buffer.set(this.uint32ToBytes(header.decompressed_data_length), 24);
- buffer.set(this.uint32ToBytes(header.decompressed_checksum), 28);
- buffer.set(this.uint32ToBytes(header.unknown1), 32);
+ buffer.set(this.wtvshared.uint32ToBytes(header.version_major), 4);
+ buffer.set(this.wtvshared.uint32ToBytes(header.version_minor), 8);
+ buffer.set(this.wtvshared.uint32ToBytes(header.script_id), 12);
+ buffer.set(this.wtvshared.uint32ToBytes(header.script_mod), 16);
+ buffer.set(this.wtvshared.uint32ToBytes(header.compressed_data_length), 20);
+ buffer.set(this.wtvshared.uint32ToBytes(header.decompressed_data_length), 24);
+ buffer.set(this.wtvshared.uint32ToBytes(header.decompressed_checksum), 28);
+ buffer.set(this.wtvshared.uint32ToBytes(header.unknown1), 32);
return buffer;
}
diff --git a/zefie_wtvp_minisrv/includes/config.json b/zefie_wtvp_minisrv/includes/config.json
index 88c5c73b..f101a060 100644
--- a/zefie_wtvp_minisrv/includes/config.json
+++ b/zefie_wtvp_minisrv/includes/config.json
@@ -58,7 +58,9 @@
"reserved_names_files": [
"includes/badWords.json",
"includes/reservedWords.json"
- ]
+ ],
+ "filestore_storage": 16, // Megabytes
+ "scrapbook_storage": 8 // Megabytes
},
"irc": {
"enabled": false,
|