v0.9.35
- numerous bug fixes - wtv-news goodies, ready for local testing - custom patched nntp-server node module with support for POSTing - should be able to post locally - 4 groups are made by default - can override in user_config.json (look at the config.json changes but dont do them there) - can sync down from an upstream server with sync_nntp.js - sync does not push new posts to upstream yet
This commit is contained in:
@@ -40,20 +40,7 @@ class WTVClientSessionData {
|
||||
this.ssid = ssid;
|
||||
this.data_store = new Array();
|
||||
this.session_store = {};
|
||||
this.lockdownWhitelist = [
|
||||
"wtv-1800:/preregister",
|
||||
"wtv-head-waiter:/login",
|
||||
"wtv-head-waiter:/ValidateLogin",
|
||||
"wtv-head-waiter:/login-stage-two",
|
||||
"wtv-head-waiter:/relogin",
|
||||
"wtv-head-waiter:/ROMCache/Spacer.gif",
|
||||
"wtv-head-waiter:/ROMCache/NameStrip.gif",
|
||||
"wtv-head-waiter:/images/PasswordBanner.gif",
|
||||
"wtv-head-waiter:/ROMCache/UtilityBullet.gif",
|
||||
"wtv-head-waiter:/images/NameBanner.gif",
|
||||
"wtv-head-waiter:/bad-disk",
|
||||
"wtv-log:/log"
|
||||
];
|
||||
this.lockdownWhitelist = minisrv_config.config.lockdownWhitelist;
|
||||
this.lockdownWhitelist.push(minisrv_config.config.unauthorized_url);
|
||||
this.lockdownWhitelist.push(minisrv_config.config.service_logo);
|
||||
this.mailstore = new WTVMail(this.minisrv_config, this)
|
||||
@@ -61,14 +48,16 @@ class WTVClientSessionData {
|
||||
this.loginWhitelist = Object.assign([], this.lockdownWhitelist); // clone lockdown whitelist into login whitelist
|
||||
this.loginWhitelist.push("wtv-head-waiter:/choose-user");
|
||||
this.loginWhitelist.push("wtv-head-waiter:/password");
|
||||
this.loginWhitelist.push("http://*"); // allow http proxy without login
|
||||
this.loginWhitelist.push("https://*"); // allow https proxy without login
|
||||
}
|
||||
|
||||
assignMailStore() {
|
||||
this.mailstore = new WTVMail(this.minisrv_config, this)
|
||||
}
|
||||
|
||||
assignFavoriteStore() {
|
||||
this.mailstore = this.favstore = new WTVFavorites(this.minisrv_config, this)
|
||||
}
|
||||
|
||||
createWTVSecSession() {
|
||||
return new WTVSec(this.minisrv_config)
|
||||
}
|
||||
@@ -79,22 +68,39 @@ class WTVClientSessionData {
|
||||
|
||||
|
||||
var total_unread_messages = 0;
|
||||
for (var i = 0; i < this.minisrv_config.config.user_accounts.max_users_per_account; i++) {
|
||||
var subUserSession = new this.constructor(this.minisrv_config, this.ssid);
|
||||
subUserSession.switchUserID(i, false, false);
|
||||
var accounts = this.listPrimaryAccountUsers();
|
||||
var self = this;
|
||||
Object.keys(accounts).forEach((k) => {
|
||||
var user_id = accounts[k].user_id;
|
||||
var subUserSession = new self.constructor(self.minisrv_config, self.ssid);
|
||||
subUserSession.switchUserID(user_id, false, false);
|
||||
subUserSession.assignMailStore();
|
||||
if (subUserSession.mailstore) {
|
||||
total_unread_messages += subUserSession.mailstore.countUnreadMessages(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
return total_unread_messages;
|
||||
}
|
||||
|
||||
switchUserID(user_id, update_mail = true, update_ticket = true) {
|
||||
clearUserSessionMemory() {
|
||||
this.setUserLoggedIn(false);
|
||||
this.data_store = new Array();
|
||||
this.session_store = {};
|
||||
this.assignFavoriteStore();
|
||||
this.assignMailStore()
|
||||
}
|
||||
|
||||
switchUserID(user_id, update_mail = true, update_ticket = true, update_favorite = true) {
|
||||
this.user_id = user_id;
|
||||
this.loadSessionData();
|
||||
if (this.isRegistered()) this.assignMailStore();
|
||||
if (this.data_store.wtvsec_login && update_ticket) this.setTicketData('user_id', user_id);
|
||||
if (user_id != null) {
|
||||
this.loadSessionData();
|
||||
if (this.isRegistered() && update_mail) this.assignMailStore();
|
||||
if (this.isRegistered() && update_favorite) this.assignMailStore();
|
||||
if (this.data_store.wtvsec_login && update_ticket) this.setTicketData('user_id', user_id);
|
||||
} else {
|
||||
this.user_id = 0;
|
||||
this.clearUserSessionMemory();
|
||||
}
|
||||
}
|
||||
|
||||
setTicketData(key, value) {
|
||||
@@ -153,8 +159,14 @@ class WTVClientSessionData {
|
||||
if (f.substr(0, 4) == "user") {
|
||||
var user_file = master_directory + self.path.sep + f + self.path.sep + f + ".json";
|
||||
if (self.fs.existsSync(user_file)) {
|
||||
if (f == "user0") account_data['subscriber'] = JSON.parse(this.fs.readFileSync(user_file));
|
||||
else account_data[f] = JSON.parse(this.fs.readFileSync(user_file));
|
||||
if (f == "user0") {
|
||||
account_data['subscriber'] = JSON.parse(this.fs.readFileSync(user_file));
|
||||
account_data['subscriber'].user_id = 0;
|
||||
}
|
||||
else {
|
||||
account_data[f] = JSON.parse(this.fs.readFileSync(user_file));
|
||||
account_data[f].user_id = parseInt(f.replace("user", ''))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,10 +438,6 @@ class WTVClientSessionData {
|
||||
return encoded_passwd.toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
|
||||
generatePassword(len) {
|
||||
return CryptoJS.lib.WordArray.random(Math.round(len / 2)).toString(CryptoJS.enc.Hex);
|
||||
}
|
||||
|
||||
setUserPassword(passwd) {
|
||||
var encoded_passwd = this.encodePassword(passwd);
|
||||
this.setSessionData("subscriber_password", encoded_passwd);
|
||||
@@ -465,7 +473,6 @@ class WTVClientSessionData {
|
||||
}
|
||||
|
||||
isUserLoggedIn() {
|
||||
if (!this.getUserPasswordEnabled()) return true; // no password is set so always validate
|
||||
var password_valid = this.get("password_valid");
|
||||
return (password_valid);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ class WTVGuide {
|
||||
constructor(minisrv_config, session_data, socket, runScriptInVM) {
|
||||
if (!minisrv_config) throw ("minisrv_config required");
|
||||
if (!session_data) throw ("WTVClientSessionData required");
|
||||
var WTVShared = require("./WTVShared.js")['WTVShared'];
|
||||
const WTVShared = require("./WTVShared.js")['WTVShared'];
|
||||
this.minisrv_config = minisrv_config;
|
||||
this.session_data = session_data;
|
||||
this.wtvshared = new WTVShared(minisrv_config);
|
||||
@@ -24,8 +24,8 @@ class WTVGuide {
|
||||
|
||||
switch (topic.toLowerCase()) {
|
||||
case "glossary":
|
||||
var template = this.wtvshared.getAbsolutePath(this.minisrv_config.config.ServiceDeps + "/wtv-guide/templates/glossary.js");
|
||||
var glossary_datafile = this.wtvshared.getAbsolutePath(this.minisrv_config.config.ServiceDeps + "/wtv-guide/glossary.json");
|
||||
var template =this.wtvshared.getTemplate("wtv-guide", "templates/glossary.js", true);
|
||||
var glossary_datafile =this.wtvshared.getTemplate("wtv-guide", "glossary.json", true);
|
||||
if (!this.fs.existsSync(template)) break;
|
||||
if (!this.fs.existsSync(glossary_datafile)) break;
|
||||
|
||||
@@ -44,13 +44,9 @@ class WTVGuide {
|
||||
var start = definition.indexOf(search) + search.length;
|
||||
original_start = start;
|
||||
// handle <word="whatever">
|
||||
console.log("start:", start)
|
||||
console.log("debug > position:", definition.substr(start, 1));
|
||||
if (definition.substr(start, 1) != ">") {
|
||||
console.log("debug: detecting word in tag")
|
||||
start++; // +1 to skip =
|
||||
end = definition.indexOf(">", start);
|
||||
console.log("debug > position 2:", definition.indexOf(">", start));
|
||||
link_word_override = definition.substring(start, end);
|
||||
// strip any quotes
|
||||
if (link_word_override.substr(0, 1).match(/[\"\']/)) link_word_override = link_word_override.substring(1);
|
||||
@@ -60,10 +56,8 @@ class WTVGuide {
|
||||
link_word_start_letter = link_word_for_link.substr(0, 1).toUpperCase();
|
||||
start = end + 1; // update start pos for rest of processing
|
||||
} else {
|
||||
console.log("debug: generating word")
|
||||
start++;
|
||||
}
|
||||
console.log("end:", end)
|
||||
end = definition.indexOf("</word>", start);
|
||||
var link_word = definition.substring(start, end);
|
||||
if (!link_word_for_link) link_word_for_link = link_word.replace(/ /g, '').replace(/\'/g,'').replace(/\"/g,'').toLowerCase();
|
||||
@@ -72,13 +66,6 @@ class WTVGuide {
|
||||
|
||||
var link = `wtv-guide:/help?topic=Glossary&subtopic=${link_word_start_letter}&page=${link_word_for_link}&word=${encodeURIComponent(link_word_override)}`
|
||||
var new_definition = definition.substring(0, original_start - search.length) + `<a href="${link}">${link_word}</a>` + definition.substring(end + 7);
|
||||
console.log("start:", start)
|
||||
console.log("end:", end)
|
||||
console.log("link_word:", link_word)
|
||||
console.log("link_word_for_link:", link_word_for_link);
|
||||
console.log("link_word_start_letter:", link_word_start_letter);
|
||||
console.log("link:", link)
|
||||
console.log("new_definition:", new_definition)
|
||||
definition = new_definition;
|
||||
}
|
||||
// replaces <boxname> with the friendly name of the type of unit the user has
|
||||
@@ -110,7 +97,7 @@ class WTVGuide {
|
||||
}
|
||||
} else {
|
||||
// glossary letter word index
|
||||
var template = this.wtvshared.getAbsolutePath(this.minisrv_config.config.ServiceDeps + "/wtv-guide/templates/glossary_word_index.js");
|
||||
var template =this.wtvshared.getTemplate("wtv-guide", "templates/glossary_word_index.js", true);
|
||||
var isPlusBox = false;
|
||||
if (this.session_data.hasCap("client-has-tv-experience")) isPlusBox = true;
|
||||
var worddb = [];
|
||||
@@ -133,9 +120,8 @@ class WTVGuide {
|
||||
case "index":
|
||||
switch (subtopic.toLowerCase()) {
|
||||
case "glossary":
|
||||
var template = this.wtvshared.getAbsolutePath(this.minisrv_config.config.ServiceDeps + "/wtv-guide/templates/glossary_index.js");
|
||||
console.log(template);
|
||||
var glossary_datafile = this.wtvshared.getAbsolutePath(this.minisrv_config.config.ServiceDeps + "/wtv-guide/glossary.json");
|
||||
var template =this.wtvshared.getTemplate("wtv-guide", "templates/glossary_index.js", true);
|
||||
var glossary_datafile =this.wtvshared.getTemplate("wtv-guide", "glossary.json", true);
|
||||
if (!this.fs.existsSync(template)) break;
|
||||
if (!this.fs.existsSync(glossary_datafile)) break;
|
||||
|
||||
@@ -154,8 +140,8 @@ class WTVGuide {
|
||||
// fallback to old js file method
|
||||
try {
|
||||
var prerendered = null;
|
||||
if (!page) prerendered = this.wtvshared.getAbsolutePath(this.minisrv_config.config.ServiceDeps + "/wtv-guide/prerendered/" + topic + "/" + subtopic + ".js");
|
||||
else prerendered = this.wtvshared.getAbsolutePath(this.minisrv_config.config.ServiceDeps + "/wtv-guide/prerendered/" + topic + "/" + subtopic + "/" + page + ".js");
|
||||
if (!page) prerendered = this.wtvshared.getTemplate("wtv-guide", "prerendered/" + topic + "/" + subtopic + ".js", true);
|
||||
else prerendered =this.wtvshared.getTemplate("wtv-guide", "prerendered/" + topic + "/" + subtopic + "/" + page + ".js", true);
|
||||
|
||||
if (!this.fs.existsSync(prerendered)) break;
|
||||
|
||||
@@ -184,7 +170,7 @@ class WTVGuide {
|
||||
console.log(" * wtv-template error:", e)
|
||||
}
|
||||
// unload and clean up module
|
||||
wtvshared.unloadModule(template);
|
||||
this.wtvshared.unloadModule(template);
|
||||
}
|
||||
|
||||
// return generated page
|
||||
|
||||
@@ -39,7 +39,7 @@ class WTVMail {
|
||||
this.trashMailboxName
|
||||
];
|
||||
this.defaultColors = {
|
||||
bgcolor: "#171726",
|
||||
bgcolor: "#191919",
|
||||
text: "#82A9D9",
|
||||
link: "#BDA73A",
|
||||
vlink: "#62B362"
|
||||
@@ -156,7 +156,7 @@ class WTVMail {
|
||||
return this.uuid.v1();
|
||||
}
|
||||
|
||||
createMessage(mailboxid, from_addr, to_addr, msgbody, subject = null, from_name = null, to_name = null, signature = null, date = null, known_sender = false, attachments = [], url = null, url_title = null) {
|
||||
createMessage(mailboxid, from_addr, to_addr, msgbody, subject = null, from_name = null, to_name = null, signature = null, date = null, known_sender = false, attachments = [], url = null, url_title = null, allow_html = false) {
|
||||
if (this.createMailbox(mailboxid)) {
|
||||
if (!date) date = Math.floor(Date.now() / 1000);
|
||||
|
||||
@@ -177,7 +177,8 @@ class WTVMail {
|
||||
"unread": true,
|
||||
"attachments": attachments,
|
||||
"url": url,
|
||||
"url_title": url_title
|
||||
"url_title": url_title,
|
||||
"allow_html": allow_html
|
||||
}
|
||||
try {
|
||||
if (this.fs.existsSync(message_file_out)) {
|
||||
@@ -202,14 +203,53 @@ class WTVMail {
|
||||
}
|
||||
|
||||
createWelcomeMessage() {
|
||||
var from_addr = (this.minisrv_config.config.service_owner_account) ? this.minisrv_config.config.service_owner_account : this.minisrv_config.config.service_owner;
|
||||
from_addr += "@" + this.minisrv_config.config.service_name;
|
||||
var from_name = this.minisrv_config.config.service_owner
|
||||
var welcomeTemplate = this.wtvshared.getTemplate("wtv-mail", "welcomeMail.txt").toString('ascii');
|
||||
var end_of_headers = false;
|
||||
var msg = "";
|
||||
var self = this;
|
||||
var to_addr = this.wtvclient.getSessionData("subscriber_username") + "@" + this.minisrv_config.config.service_name;
|
||||
var to_name = this.wtvclient.getSessionData("subscriber_name");
|
||||
var subj = "Welcome to " + this.minisrv_config.config.service_name;
|
||||
var msg = "poop";
|
||||
return this.createMessage(0, from_addr, to_addr, msg, subj, from_name, to_name, null, null, true);
|
||||
var available_tags = {
|
||||
...this.minisrv_config.config,
|
||||
"user_address": to_addr,
|
||||
"user_name": to_name
|
||||
}
|
||||
var from_name, from_addr, subj = null;
|
||||
var lines = welcomeTemplate.replace(/\r/g, '').split("\n");
|
||||
lines.forEach((line) => {
|
||||
if (line.indexOf(": ") > 1 && !end_of_headers) {
|
||||
var header = [line.slice(0, line.indexOf(':')), line.slice(line.indexOf(':') + 2).trim()];
|
||||
switch (header[0].toLowerCase()) {
|
||||
case "from":
|
||||
if (header[1].indexOf("<") >= 0) {
|
||||
var email = header[1].match(/(.+) \<(.+)\>/);
|
||||
if (email) {
|
||||
from_name = email[1];
|
||||
from_addr = email[2];
|
||||
} else {
|
||||
var email = header[1].match(/\<(.+)\>/);
|
||||
from_addr = email[1];
|
||||
}
|
||||
} else if (header[1].indexOf('@') >= 0) {
|
||||
from_addr = header[1];
|
||||
}
|
||||
break;
|
||||
|
||||
case "subject":
|
||||
subj = header[1];
|
||||
break;
|
||||
}
|
||||
} else if (line == '') end_of_headers = true;
|
||||
else {
|
||||
msg += line.replace(/\$\{(\w{1,})\}/g, function (x) {
|
||||
var out = '';
|
||||
var tag = x.replace("${", '').replace('}', '');
|
||||
if (available_tags[tag]) out = available_tags[tag];
|
||||
return out
|
||||
}) + "\n";
|
||||
}
|
||||
});
|
||||
return this.createMessage(0, from_addr, to_addr, msg, subj, from_name, to_name, null, null, true, [], null, null, true);
|
||||
}
|
||||
|
||||
getMessage(mailboxid, messageid) {
|
||||
|
||||
@@ -11,7 +11,7 @@ class WTVMime {
|
||||
|
||||
|
||||
constructor(minisrv_config) {
|
||||
var WTVShared = require("./WTVShared.js")['WTVShared'];
|
||||
const { WTVShared } = require("./WTVShared.js");
|
||||
this.minisrv_config = minisrv_config;
|
||||
this.wtvshared = new WTVShared(minisrv_config);
|
||||
if (!String.prototype.reverse) {
|
||||
@@ -199,6 +199,67 @@ class WTVMime {
|
||||
return new Array(wtv_mime_type, modern_mime_type);
|
||||
}
|
||||
|
||||
// modified from https://github.com/sergi/mime-multipart/blob/master/index.js
|
||||
|
||||
generateMultipartMIME(tuples, options) {
|
||||
// modified for creating usenet compliant headers/content from an attachment
|
||||
var CRLF = '\n';
|
||||
if (tuples.length === 0) {
|
||||
// according to rfc1341 there should be at least one encapsulation
|
||||
throw new Error('Missing argument. At least one part to generate is required');
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
var preamble = options.preamble || "This is a multi-part message in MIME format.";
|
||||
var epilogue = options.epilogue;
|
||||
var boundary = options.boundary || "------------" + this.wtvshared.generateString(24);
|
||||
|
||||
if (boundary.length < 1 || boundary.length > 70) {
|
||||
throw new Error('Boundary should be between 1 and 70 characters long');
|
||||
}
|
||||
|
||||
var boundary_header = 'multipart/mixed; boundary="' + boundary + '"';
|
||||
|
||||
var delimiter = CRLF + '--' + boundary;
|
||||
var closeDelimiter = delimiter + '--';
|
||||
|
||||
var wtvshared = this.wtvshared;
|
||||
|
||||
var encapsulations = tuples.map(function (tuple, i) {
|
||||
var mimetype = tuple.mime || 'text/plain';
|
||||
var encoding = tuple.encoding || 'utf-8';
|
||||
var use_base64 = tuple.use_base64 || !wtvshared.isASCII(tuple.content);
|
||||
var is_base64 = tuple.is_base64 || wtvshared.isBase64(tuple.content);
|
||||
var filename = (tuple.filename) ? tuple.filename : (use_base64) ? ('file' + i) : null;
|
||||
|
||||
var headers = [
|
||||
`Content-Type: ${mimetype}; ${(use_base64) ? `name="${filename}"` : `charset=${encoding.toUpperCase()}; format=flowed`}`,
|
||||
];
|
||||
|
||||
if (filename) headers.push(`Content-Disposition: attachment; filename="${filename}"`);
|
||||
headers.push(`Content-Transfer-Encoding: ${(use_base64) ? 'base64' : '7bit'}`);
|
||||
|
||||
var bodyPart = headers.join(CRLF) + CRLF + CRLF;
|
||||
if (use_base64 && !is_base64) bodyPart += wtvshared.lineWrap(Buffer.from(tuple.content).toString('base64'),72) + CRLF;
|
||||
else bodyPart += wtvshared.lineWrap(tuple.content,72);
|
||||
|
||||
return delimiter + CRLF + bodyPart;
|
||||
});
|
||||
|
||||
var multipartBody = [
|
||||
preamble ? preamble : undefined,
|
||||
encapsulations.join(''),
|
||||
closeDelimiter,
|
||||
epilogue ? CRLF + epilogue : undefined,
|
||||
].filter(function (element) { return !!element; });
|
||||
|
||||
return {
|
||||
"mime_version": "1.0",
|
||||
"content_type": boundary_header,
|
||||
"content": multipartBody.join('')
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = WTVMime;
|
||||
|
||||
@@ -2,23 +2,61 @@ class WTVNews {
|
||||
|
||||
minisrv_config = null;
|
||||
newsie = require('newsie').default;
|
||||
strftime = require('strftime');
|
||||
wtvshared = null;
|
||||
service_name = null;
|
||||
client = null;
|
||||
username = null;
|
||||
password = null;
|
||||
posting_allowed = true;
|
||||
|
||||
constructor(minisrv_config, service_name) {
|
||||
this.minisrv_config = minisrv_config;
|
||||
this.service_name = service_name;
|
||||
this.client = new this.newsie({
|
||||
host: this.minisrv_config.services[service_name].upstream_address,
|
||||
port: this.minisrv_config.services[service_name].upstream_port
|
||||
})
|
||||
const { WTVShared } = require("./WTVShared.js");
|
||||
this.wtvshared = new WTVShared(minisrv_config);
|
||||
}
|
||||
|
||||
initializeUsenet(host, port = 119, tls_options = null, username = null, password = null) {
|
||||
// use local self-signed cert for local server
|
||||
var newsie_options = {
|
||||
host: host,
|
||||
port: port,
|
||||
tlsPort: (tls_options !== null) ? true : false,
|
||||
}
|
||||
if (newsie_options.tlsPort) newsie_options.tlsOptions = tls_options;
|
||||
this.client = new this.newsie(newsie_options);
|
||||
if (username && password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
connectUsenet() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.connect().then((response) => {
|
||||
if (response.code == 200) {
|
||||
resolve(true);
|
||||
if (response.code == 200 || response.code == 201) {
|
||||
if (response.code == 201) this.posting_allowed = false;
|
||||
if (this.username && this.password) {
|
||||
this.client.authInfoUser(this.username).then((res) => {
|
||||
if (res.code == "381") {
|
||||
res.authInfoPass(this.password).then((res) => {
|
||||
if (res.code == 281) resolve(true);
|
||||
else reject(res.description);
|
||||
}).catch((e) => {
|
||||
console.error(" * WTVNews Error:", "Command: connect", e);
|
||||
reject("Could not connect to upstream usenet server");
|
||||
});
|
||||
} else {
|
||||
reject(res.description)
|
||||
}
|
||||
}).catch((e) => {
|
||||
console.error(" * WTVNews Error:", "Command: connect", e);
|
||||
reject("Could not connect to upstream usenet server");
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
}
|
||||
}).catch((e) => {
|
||||
console.error(" * WTVNews Error:", "Command: connect", e);
|
||||
@@ -27,21 +65,63 @@ class WTVNews {
|
||||
});
|
||||
}
|
||||
|
||||
listGroup(group) {
|
||||
listGroup(group, page = 0, limit = 100, raw_range = null) {
|
||||
// list of articles from group
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.listGroup(group).then((data) => {
|
||||
resolve(data);
|
||||
this.selectGroup(group).then((res) => {
|
||||
if (!raw_range) {
|
||||
var range = {
|
||||
start: (limit * page) + res.group.low,
|
||||
}
|
||||
range.end = range.start + limit;
|
||||
if (page) range.start++;
|
||||
if (range.end > res.high) delete range.group.end;
|
||||
} else {
|
||||
range = raw_range;
|
||||
}
|
||||
this.client.listGroup(group, range).then((data) => {
|
||||
resolve(data);
|
||||
}).catch((e) => {
|
||||
console.error(" * WTVNews Error:", "Command: listGroup", e);
|
||||
reject(`No such group <b>${group}</b>`);
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.error(" * WTVNews Error:", "Command: listGroup", e);
|
||||
reject(`No such group <b>${group}</b>`);
|
||||
console.error(" * WTVNews Error:", "Command: selectGroup", e);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
processGroupList(list) {
|
||||
if (list) return list.newsgroups;
|
||||
else return null;
|
||||
}
|
||||
|
||||
listGroups(search = null) {
|
||||
// list of groups on the server
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!search) {
|
||||
this.client.list().then((data) => {
|
||||
console.log('listGroups data', data)
|
||||
resolve(this.processGroupList(data));
|
||||
}).catch((e) => {
|
||||
console.error(" * WTVNews Error:", "Command: listGroups (all)", e);
|
||||
reject(e);
|
||||
});
|
||||
} else {
|
||||
this.client.listNewsgroups((search === '*') ? '*' : '*' + search + '*').then((data) => {
|
||||
resolve(this.processGroupList(data));
|
||||
}).catch((e) => {
|
||||
console.error(" * WTVNews Error:", "Command: listGroups (search)", search, e);
|
||||
reject(e);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
selectGroup(group) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.group(group).then((response) => {
|
||||
if (response.code == 211) resolve(true);
|
||||
if (response.code == 211) resolve(response);
|
||||
else reject(`No such group <b>${group}</b>`);
|
||||
}).catch((e) => {
|
||||
console.error(" * WTVNews Error:", "Command: selectGroup", e);
|
||||
@@ -50,20 +130,102 @@ class WTVNews {
|
||||
});
|
||||
}
|
||||
|
||||
getArticle(articleID) {
|
||||
getArticle(articleID, get_next_last = true) {
|
||||
var articleID = parseInt(articleID);
|
||||
return new Promise((resolve, reject) => {
|
||||
var promises = [];
|
||||
this.client.article(articleID).then((data) => {
|
||||
resolve(data)
|
||||
if (get_next_last) {
|
||||
// ask server for next article
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
this.client.next().then((res) => {
|
||||
data.next_article = res.article.articleNumber;
|
||||
resolve(data.next_article);
|
||||
}).catch((e) => {
|
||||
data.next_article = null;
|
||||
resolve(data.next_article);
|
||||
})
|
||||
}));
|
||||
|
||||
// ask server for previous article
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
this.client.last().then((res) => {
|
||||
data.prev_article = res.article.articleNumber;
|
||||
if (data.prev_article === articleID) {
|
||||
// do it again, needed this for CodoSoft NNTPd?
|
||||
this.client.article(data.prev_article).then(() => {
|
||||
this.client.last().then((res) => {
|
||||
data.prev_article = res.article.articleNumber;
|
||||
resolve(data.prev_article);
|
||||
}).catch(() => {
|
||||
data.prev_article = null;
|
||||
resolve(data.prev_article);
|
||||
});
|
||||
}).catch(() => {
|
||||
data.prev_article = null;
|
||||
resolve(data.prev_article);
|
||||
});
|
||||
} else {
|
||||
resolve(data.prev_article);
|
||||
}
|
||||
}).catch(() => {
|
||||
data.prev_article = null;
|
||||
resolve(data.prev_article);
|
||||
})
|
||||
}));
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
var self = this;
|
||||
if (data.article.headers) Object.keys(data.article.headers).forEach((k) => {
|
||||
data.article.headers[k] = self.decodeCharset(data.article.headers[k])
|
||||
});
|
||||
resolve(data);
|
||||
});
|
||||
} else {
|
||||
var self = this;
|
||||
if (data.article.headers) Object.keys(data.article.headers).forEach((k) => {
|
||||
data.article.headers[k] = self.decodeCharset(data.article.headers[k])
|
||||
});
|
||||
resolve(data);
|
||||
}
|
||||
}).catch((e) => {
|
||||
reject(`Error reading article ID ${articleID}`);
|
||||
console.error(" * WTVNews Error:", "Command: article", e);
|
||||
console.error(" * WTVNews Error:", "Command: article", "args:", articleID, "Error:", e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
decodeCharset(string) {
|
||||
var regex = /=\?{1}(.+)\?{1}([B|Q])\?{1}(.+)\?{1}=/;
|
||||
var decoded = null;
|
||||
var check = string.match(regex);
|
||||
if (check) {
|
||||
var match = check[0];
|
||||
var charset = check[1];
|
||||
var encoding = check[2];
|
||||
var encoded_text = check[3];
|
||||
switch (encoding) {
|
||||
case "B":
|
||||
var buffer = new Buffer.from(encoded_text, 'base64')
|
||||
decoded = buffer.toString(charset).replace(/[^\x00-\x7F]/g, "");;
|
||||
break;
|
||||
|
||||
case "Q":
|
||||
// unimplemented
|
||||
return string;
|
||||
}
|
||||
if (decoded) return string.replace(match, decoded);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
getHeader(articleID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.head(articleID).then((data) => {
|
||||
var self = this;
|
||||
if (data.article.headers) Object.keys(data.article.headers).forEach((k) => {
|
||||
data.article.headers[k] = self.decodeCharset(data.article.headers[k])
|
||||
});
|
||||
resolve(data);
|
||||
}).catch((e) => {
|
||||
reject(`Error getting header for article ID ${articleID}`);
|
||||
@@ -72,6 +234,19 @@ class WTVNews {
|
||||
});
|
||||
}
|
||||
|
||||
getHeaderFromMessage(message, header) {
|
||||
var response = null;
|
||||
if (message.article.headers) {
|
||||
Object.keys(message.article.headers).forEach((k) => {
|
||||
if (k.toLowerCase() == header.toLowerCase()) {
|
||||
response = message.article.headers[k];
|
||||
return false;
|
||||
}
|
||||
})
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
quitUsenet() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.quit().then((response) => {
|
||||
@@ -87,24 +262,263 @@ class WTVNews {
|
||||
});
|
||||
}
|
||||
|
||||
postToGroup(group, from_addr, msg_subject, msg_body, article = null, headers = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var promises = [];
|
||||
var messageid = null;
|
||||
this.connectUsenet()
|
||||
.then(() => {
|
||||
if (article) {
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
this.selectGroup(group).then((res) => {
|
||||
this.getArticleMessageID(article).then((data) => {
|
||||
messageid = data;
|
||||
resolve(data);
|
||||
}).catch((e) => {
|
||||
console.log('Error getting articleID',article, e)
|
||||
reject(e)
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.log('Error selecting group', e)
|
||||
reject(e)
|
||||
});
|
||||
}));
|
||||
}
|
||||
Promise.all(promises).then(() => {
|
||||
this.client.post()
|
||||
.then((response) => {
|
||||
if (response.code == 340) {
|
||||
var articleData = {};
|
||||
articleData.headers = {
|
||||
'Relay-Version': "version zefie_wtvp_minisrv " + this.minisrv_config.version + "; site " + this.minisrv_config.config.domain_name,
|
||||
'Posting-Version': "version zefie_wtvp_minisrv " + this.minisrv_config.version + "; site " + this.minisrv_config.config.domain_name,
|
||||
'Path': "@" + this.minisrv_config.config.domain_name,
|
||||
'From': from_addr,
|
||||
'Newsgroups': group,
|
||||
'Subject': msg_subject || "(No subject)",
|
||||
'Message-ID': "<" + this.wtvshared.generateString(16) + "@" + this.minisrv_config.config.domain_name + ">",
|
||||
'Date': this.strftime('%a, %-d %b %Y %H:%M:%S %z', new Date())
|
||||
}
|
||||
if (headers) {
|
||||
Object.keys(headers).forEach((k) => {
|
||||
articleData.headers[k] = headers[k];
|
||||
});
|
||||
}
|
||||
if (messageid) {
|
||||
articleData.headers.References = messageid;
|
||||
articleData.headers['In-Reply-To'] = messageid;
|
||||
}
|
||||
if (msg_body) articleData.body = msg_body.split("\n");
|
||||
else articleData.body = [];
|
||||
|
||||
response.send(articleData).then((response) => {
|
||||
this.client.quit();
|
||||
if (response.code !== 240) {
|
||||
reject("Could not send post. Server returned error " + response.code);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
this.client.quit();
|
||||
reject("Could not send post. Server returned error " + response.code);
|
||||
});
|
||||
} else {
|
||||
this.client.quit();
|
||||
console.error('usenet upstream uncaught error', e);
|
||||
reject("Could not send post. Server returned unknown error");
|
||||
};
|
||||
}).catch((e) => {
|
||||
console.error('could not connect to server', e);
|
||||
reject("could not connect to server");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getArticleMessageID(articleID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.article(articleID).then((data) => {
|
||||
resolve(data.article.messageId);
|
||||
}).catch((e) => {
|
||||
console.error("error getting messageID from article", articleID, e)
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getHeaderObj(NGArticles) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var messages = [];
|
||||
var failed = false;
|
||||
var promises = [];
|
||||
for (var article in NGArticles) {
|
||||
if (failed) continue;
|
||||
if (article == "getCaseInsensitiveKey") continue;
|
||||
this.getHeader(NGArticles[article]).then((data) => {
|
||||
if (data.article) messages.push(data.article)
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
this.getHeader(NGArticles[article]).then((data) => {
|
||||
if (data.article) messages.push(data.article)
|
||||
resolve();
|
||||
}).catch((e) => {
|
||||
reject(e);
|
||||
});
|
||||
}));
|
||||
}
|
||||
if (promises.length > 0) {
|
||||
Promise.all(promises).then(() => {
|
||||
if (messages.length > 0) resolve(messages);
|
||||
}).catch((e) => {
|
||||
console.log(e, article);
|
||||
failed = e;
|
||||
reject("Could not read message list", e);
|
||||
});
|
||||
} else {
|
||||
resolve(messages);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
parseAttachments(message) {
|
||||
var contype = this.getHeaderFromMessage(message, 'Content-Type');
|
||||
if (contype) {
|
||||
var regex = /multipart\/mixed\; boundary=\"(.+)\"/i;
|
||||
var match = contype.match(regex);
|
||||
if (match) {
|
||||
var boundary = "--" + match[1];
|
||||
var body = message.article.body.join("\n").split(boundary);
|
||||
var attachments = [];
|
||||
var i = 0;
|
||||
var message_body = '';
|
||||
var message_type = 'text/plain';
|
||||
body.forEach((element) => {
|
||||
var section_type = null;
|
||||
var section = element.split("\n");
|
||||
attachments[i] = {};
|
||||
section.forEach((line) => {
|
||||
var section_header_match = line.match(/^Content\-/i)
|
||||
if (section_header_match) {
|
||||
var section_match = line.match(/^Content\-Type\: (.+)\;/i)
|
||||
if (section_match) {
|
||||
if (section_match[1].match("text/plain")) {
|
||||
section_type = section_match[1].match("text/plain")[1];
|
||||
message_type = section_type;
|
||||
} else {
|
||||
section_type = section_match[1];
|
||||
attachments[i].content_type = section_match[1]
|
||||
}
|
||||
}
|
||||
section_match = line.match(/^Content\-Disposition\: (.+)\;/i)
|
||||
if (section_match) {
|
||||
section_match = line.match(/^Content\-Disposition\: (.+)\; filename=\"(.+)\"/i)
|
||||
if (section_match) attachments[i].filename = section_match[2];
|
||||
}
|
||||
section_match = line.match(/^Content-Transfer-Encoding: (.+)/i)
|
||||
if (section_match) attachments[i].content_encoding = section_match[1];
|
||||
} else {
|
||||
if (section_type != null) {
|
||||
if (section_type.match("text/plain")) message_body += line;
|
||||
else {
|
||||
if (attachments[i].data) attachments[i].data += line;
|
||||
else attachments[i].data = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if (attachments[i].content_type) i++;
|
||||
})
|
||||
attachments.pop();
|
||||
return {
|
||||
text: message_body,
|
||||
text_type: message_type || "text/plain",
|
||||
attachments: attachments
|
||||
}
|
||||
} else {
|
||||
var message_body = '';
|
||||
if (message.article.body) message_body = message.article.body.join("\n")
|
||||
|
||||
return { text: message_body }
|
||||
}
|
||||
} else {
|
||||
var message_body = '';
|
||||
if (message.article.body) message_body = message.article.body.join("\n")
|
||||
|
||||
return { text: message_body }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sortByResponse(messages) {
|
||||
var sorted = [];
|
||||
var message_id_roots = [];
|
||||
var message_relations = [];
|
||||
Object.keys(messages).forEach((k) => {
|
||||
var messageId = messages[k].messageId;
|
||||
var ref = messages[k].headers.REFERENCES;
|
||||
if (ref) {
|
||||
var res = message_id_roots.find(e => e.messageId == ref);
|
||||
if (res) {
|
||||
// see if its attached to a root post
|
||||
if (message_relations[res.messageId]) message_relations[res.messageId].push({ "messageId": messageId, "index": k });
|
||||
else message_relations[res.messageId] = [{ "messageId": messageId, "index": k }];
|
||||
} else {
|
||||
// see if its related to a relation
|
||||
if (Object.keys(message_relations).length > 0) {
|
||||
var found = false;
|
||||
Object.keys(message_relations).forEach((j) => {
|
||||
if (message_relations[j].length > 0) {
|
||||
Object.keys(message_relations[j]).forEach((h) => {
|
||||
if (found) return;
|
||||
if (message_relations[j][h].messageId == ref) {
|
||||
var searchref = messages[message_relations[j][h].index].headers.REFERENCES || null;
|
||||
var mainref = null;
|
||||
while (searchref !== null) {
|
||||
var searchart = messages.find(e => e.messageId == searchref);
|
||||
var searchref = searchart.headers.REFERENCES || null;
|
||||
}
|
||||
mainref = searchart.messageId;
|
||||
message_relations[mainref].push({ "messageId": messageId, "index": k });
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
} else {
|
||||
message_id_roots.push({ "messageId": messageId, "index": k });
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
message_id_roots.push({ "messageId": messageId, "index": k });
|
||||
}
|
||||
});
|
||||
|
||||
// sort the relations, putting root articles first, followed by their relations
|
||||
var message_roots_sorted = [];
|
||||
Object.keys(message_id_roots).forEach((k) => {
|
||||
// sort relations by date
|
||||
var article = messages[message_id_roots[k].index];
|
||||
var article_date = Date.parse(article.headers.DATE);
|
||||
message_roots_sorted.push({ "article": article, "relation": null, "date": article_date });
|
||||
});
|
||||
message_roots_sorted.sort((a, b) => { return (a.date - b.date) });
|
||||
Object.keys(message_roots_sorted).forEach((k) => {
|
||||
sorted.push(message_roots_sorted[k]);
|
||||
|
||||
if (message_relations[message_id_roots[k].messageId]) {
|
||||
var relations = [];
|
||||
Object.keys(message_relations[message_id_roots[k].messageId]).forEach((j) => {
|
||||
// sort relations by date
|
||||
var article = messages[message_relations[message_id_roots[k].messageId][j].index];
|
||||
var article_date = Date.parse(article.headers.DATE);
|
||||
relations.push({ "article": article, "relation": message_id_roots[k].messageId, "date": article_date })
|
||||
});
|
||||
relations.sort((a, b) => { return (a.date - b.date) });
|
||||
Object.keys(relations).forEach((j) => {
|
||||
sorted.push(relations[j]);
|
||||
});
|
||||
}
|
||||
if (!failed) resolve(messages);
|
||||
else reject("Could not read message list", failed);
|
||||
});
|
||||
})
|
||||
return sorted;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = WTVNews;
|
||||
476
zefie_wtvp_minisrv/includes/WTVNewsServer.js
Normal file
476
zefie_wtvp_minisrv/includes/WTVNewsServer.js
Normal file
@@ -0,0 +1,476 @@
|
||||
class WTVNewsServer {
|
||||
fs = require('fs');
|
||||
path = require('path');
|
||||
minisrv_config = null;
|
||||
strftime = require('strftime');
|
||||
wtvshared = null;
|
||||
username = null;
|
||||
password = null;
|
||||
using_auth = false;
|
||||
local_server = null;
|
||||
data_path = null;
|
||||
featuredGroups = null
|
||||
|
||||
constructor(minisrv_config, local_server_port, using_auth = false, username = null, password = null, run_server = true) {
|
||||
this.minisrv_config = minisrv_config;
|
||||
const { WTVShared } = require("./WTVShared.js");
|
||||
this.wtvshared = new WTVShared(minisrv_config);
|
||||
this.featuredGroups = minisrv_config.services['wtv-news'].featuredGroups;
|
||||
const nntp_server = require('nntp-server-zefie');
|
||||
var nntp_statuses = require('nntp-server-zefie/lib/status');
|
||||
|
||||
this.username = username || null;
|
||||
this.password = password || null;
|
||||
this.using_auth = using_auth;
|
||||
this.scan_interval = minisrv_config.services['wtv-news'].groupMetaRefreshInterval || 86400;
|
||||
this.data_path = this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + '/minisrv_internal_nntp');
|
||||
this.createDataStore();
|
||||
|
||||
if (using_auth && (!username && !password)) {
|
||||
// using auth, but no auth info specified, so randomly generate it
|
||||
this.username = this.wtvshared.generateString(8);
|
||||
this.password = this.wtvshared.generatePassword(16);
|
||||
}
|
||||
|
||||
if (run_server) {
|
||||
// nntp-server module overrides
|
||||
var self = this;
|
||||
|
||||
nntp_server.prototype = {
|
||||
...nntp_server.prototype,
|
||||
_authenticate: function (session) {
|
||||
// authenticate
|
||||
if (session.authinfo_user == self.username && session.authinfo_pass == self.password) {
|
||||
session.posting_allowed = true;
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
},
|
||||
_postArticle: function (session) {
|
||||
try {
|
||||
session.group.name = self.getHeader(session.post_data, "newsgroups");
|
||||
if (session.group.name.indexOf(',') >= 0) return false; // cross post not implemented
|
||||
return self.postArticle(session.group.name, session.post_data)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_getGroups: function (session, time = 0, wildmat = null) {
|
||||
if (time > 0) return false // unimplemented
|
||||
return self.getGroups(wildmat);
|
||||
},
|
||||
_getLast: function (session) {
|
||||
if (!session.group.name) return nntp_statuses._412_GRP_NOT_SLCTD;
|
||||
if (!session.group.current_article) return nntp_statuses._420_ARTICLE_NOT_SLCTD;
|
||||
if (!self.articleExists(session.group.name, session.group.current_article)) return nntp_statuses._420_ARTICLE_NOT_SLCTD;
|
||||
var res = self.getLastArticle(session.group.name, session.group.current_article);
|
||||
if (!res) return nntp_statuses._422_NO_LAST_ARTICLE;
|
||||
return res;
|
||||
},
|
||||
|
||||
_getNext: function (session) {
|
||||
if (!session.group.name) return nntp_statuses._412_GRP_NOT_SLCTD;
|
||||
if (!session.group.current_article) return nntp_statuses._420_ARTICLE_NOT_SLCTD;
|
||||
if (!self.articleExists(session.group.name, session.group.current_article)) return nntp_statuses._420_ARTICLE_NOT_SLCTD;
|
||||
var res = self.getNextArticle(session.group.name, session.group.current_article);
|
||||
if (!res) return nntp_statuses._421_NO_NEXT_ARTICLE;
|
||||
return res;
|
||||
},
|
||||
|
||||
_selectGroup: function (session, name) {
|
||||
// selectGroup
|
||||
var res = self.selectGroup(name);
|
||||
if (!res.failed) {
|
||||
session.group = res;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_buildHead: function (session, message) {
|
||||
var out = "";
|
||||
Object.keys(message.headers).forEach((k) => {
|
||||
if (k.length > 0) out += `${k}: ${message.headers[k]}\r\n`;
|
||||
});
|
||||
out = out.substr(0, out.length - 2);
|
||||
return out;
|
||||
},
|
||||
|
||||
_buildHeaderField: function (session, message, field) {
|
||||
if (field.indexOf(':') > 0) field = field.replace(/\:/g, '');
|
||||
var search = self.getHeader(message, field);
|
||||
if (search) return search;
|
||||
else return null;
|
||||
},
|
||||
|
||||
_getOverviewFmt: function (session) {
|
||||
var headers = [
|
||||
"Subject:",
|
||||
"From:",
|
||||
"Date:",
|
||||
"Message-ID:",
|
||||
"References:",
|
||||
":bytes",
|
||||
":lines"
|
||||
]
|
||||
return headers;
|
||||
},
|
||||
_getArticle: function (session, message_id) {
|
||||
// getArticle
|
||||
return new Promise((resolve, reject) => {
|
||||
var res = self.getArticle(session.group.name, message_id);
|
||||
if (!res.messageId) reject(res);
|
||||
else resolve(res)
|
||||
});
|
||||
},
|
||||
|
||||
_buildBody: function (session, message) {
|
||||
return message.body;
|
||||
},
|
||||
|
||||
_getRange: function (session, first, last) {
|
||||
var res = self.listGroup(session.group.name, first, last)
|
||||
if (res.failed) return false;
|
||||
session.group = res.group_data;
|
||||
return res.articles;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var tls_path = this.wtvshared.getAbsolutePath(this.minisrv_config.config.ServiceDeps + '/wtv-news');
|
||||
var tls_options = {
|
||||
ca: this.fs.readFileSync(tls_path + this.path.sep + 'localserver_ca.pem'),
|
||||
key: this.fs.readFileSync(tls_path + this.path.sep + 'localserver_key.pem'),
|
||||
cert: this.fs.readFileSync(tls_path + this.path.sep + 'localserver_cert.pem'),
|
||||
}
|
||||
this.local_server = new nntp_server({ requireAuth: using_auth, tls: tls_options, secure: true, allow_posting: true });
|
||||
this.local_server.listen('nntps://localhost:' + local_server_port);
|
||||
}
|
||||
}
|
||||
|
||||
getMetaFilename(group) {
|
||||
var g = this.getGroupPath(group);
|
||||
if (g) return g + this.path.sep + "meta.json";
|
||||
else return null;
|
||||
}
|
||||
|
||||
getHeader(message, header) {
|
||||
if (message.headers) {
|
||||
var search = Object.keys(message.headers).find(e => (e.toLowerCase() == header.toLowerCase()));
|
||||
if (search) return message.headers[search];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
createDataStore() {
|
||||
if (!this.fs.existsSync(this.data_path)) return this.fs.mkdirSync(this.data_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
createMetaFile(group, description = null) {
|
||||
const g = this.getMetaFilename(group);
|
||||
if (this.fs.existsSync(g)) return false;
|
||||
var metadata = this.selectGroup(group, true, true);
|
||||
if (description) metadata.description = description;
|
||||
this.saveMetadata(group, metadata, true);
|
||||
return (!metadata.failed) ? metadata : false
|
||||
}
|
||||
|
||||
saveMetadata(group, metadata, creating = false) {
|
||||
const g = this.getMetaFilename(group);
|
||||
if (g) {
|
||||
if (!this.fs.existsSync(g) && !creating) this.createMetaFile(group);
|
||||
else this.fs.writeFileSync(g, JSON.stringify(metadata));
|
||||
} else return false;
|
||||
}
|
||||
|
||||
getMetadata(group) {
|
||||
const g = this.getMetaFilename(group);
|
||||
if (g) {
|
||||
if (this.fs.existsSync(g)) return JSON.parse(this.fs.readFileSync(g));
|
||||
else return false
|
||||
} else return false;
|
||||
}
|
||||
|
||||
findHeaderCaseInsensitive(headers, header) {
|
||||
// returns the key with the found case
|
||||
var response = null;
|
||||
if (headers) {
|
||||
Object.keys(headers).forEach((k) => {
|
||||
if (k.toLowerCase() == header.toLowerCase()) {
|
||||
response = k;
|
||||
return false;
|
||||
}
|
||||
})
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
postArticle(group, post_data) {
|
||||
var articleNumber = this.getMetadata(group).max_index + 1;
|
||||
if (!articleNumber) return false;
|
||||
try {
|
||||
post_data.articleNumber = articleNumber;
|
||||
post_data.messageId = this.getHeader(post_data, "message-id");
|
||||
if (!post_data.messageId) {
|
||||
var messageId = "<" + this.wtvshared.generateString(16) + "@" + this.minisrv_config.config.domain_name + ">";
|
||||
post_data.messageId = post_data.headers['Message-ID'] = messageId;
|
||||
}
|
||||
|
||||
if (!post_data.headers.Path) post_data.headers.Path = "@" + this.minisrv_config.config.domain_name;
|
||||
if (!post_data.headers.Subject) post_data.headers.Subject = "(No subject)";
|
||||
|
||||
post_data.headers.Date = this.strftime("%a, %-d %b %Y %H:%M:%S %z", Date.parse(post_data.headers.date))
|
||||
|
||||
// server added Injection-Date
|
||||
post_data.headers['Injection-Date'] = this.strftime("%a, %-d %b %Y %H:%M:%S %z", Date.parse(Date.now()))
|
||||
|
||||
// Reorder headers per examples in RFC3977 sect 6.2.1.3, not sure if needed
|
||||
post_data.headers = this.wtvshared.moveObjectElement('Path', null, post_data.headers, true);
|
||||
post_data.headers = this.wtvshared.moveObjectElement('From', 'Path', post_data.headers, true);
|
||||
post_data.headers = this.wtvshared.moveObjectElement('Newsgroups', 'From', post_data.headers, true);
|
||||
post_data.headers = this.wtvshared.moveObjectElement('Subject', 'Newsgroups', post_data.headers, true);
|
||||
post_data.headers = this.wtvshared.moveObjectElement('Date', 'Subject', post_data.headers, true);
|
||||
post_data.headers = this.wtvshared.moveObjectElement('Organization', 'Date', post_data.headers, true);
|
||||
post_data.headers = this.wtvshared.moveObjectElement('Message-ID', 'Organization', post_data.headers, true);
|
||||
// end reordering of headers
|
||||
|
||||
if (this.articleExists(group, post_data.articleNumber)) return false // should not occur, but just in case
|
||||
return this.createArticle(group, post_data.articleNumber, post_data);
|
||||
} catch (e) {
|
||||
console.error(" * WTVNewsServer Error: postArticle: ", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
createArticle(group, articleNumber, article) {
|
||||
var g = this.getGroupPath(group);
|
||||
var file = g + this.path.sep + articleNumber + ".newz";
|
||||
try {
|
||||
this.fs.writeFileSync(file, JSON.stringify(article));
|
||||
var metadata = this.getMetadata(group);
|
||||
metadata.max_index = metadata.max_index + 1;
|
||||
metadata.total = metadata.total + 1;
|
||||
this.saveMetadata(group, metadata)
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(" * WTVNewsServer Error: createArticle: ", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getGroupPath(group) {
|
||||
return this.data_path + this.path.sep + group;
|
||||
}
|
||||
|
||||
getArticlePath(group, article) {
|
||||
return this.getGroupPath(group) + this.path.sep + article + ".newz";
|
||||
}
|
||||
|
||||
articleExists(group, article) {
|
||||
const g = this.getArticlePath(group, article);
|
||||
if (this.fs.existsSync(g)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
createGroup(group, description = null) {
|
||||
var g = this.getGroupPath(group);
|
||||
if (!this.fs.existsSync(g)) {
|
||||
this.fs.mkdirSync(g);
|
||||
return this.createMetaFile(group, description)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
getArticle(group, article) {
|
||||
const g = this.getArticlePath(group, article);
|
||||
if (!this.fs.existsSync(g)) return false;
|
||||
try {
|
||||
var data = JSON.parse(this.fs.readFileSync(g));
|
||||
if (data.article) data = data.article;
|
||||
data.index = data.articleNumber;
|
||||
if (!data.body) data.body = [''];
|
||||
if (!this.findHeaderCaseInsensitive(data.headers,'subject')) data.headers.Subject = "(No subject)";
|
||||
return data
|
||||
} catch (e) {
|
||||
console.error(" * WTVNewsServer Error: getArticle: ", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
selectGroup(group, force_update = false, initial_update = false) {
|
||||
var g = this.getGroupPath(group);
|
||||
var meta = this.getMetadata(group);
|
||||
if (!meta) force_update, initial_update = true;
|
||||
|
||||
if (initial_update) {
|
||||
var out = {
|
||||
total: 0,
|
||||
min_index: 0,
|
||||
max_index: 0,
|
||||
name: group
|
||||
}
|
||||
} else var out = { ...meta }
|
||||
|
||||
if (meta.min_index == 0) force_update = true;
|
||||
if (this.featuredGroups) {
|
||||
Object.keys(this.featuredGroups).forEach((k) => {
|
||||
if (group == this.featuredGroups[k].name) {
|
||||
out.wildmat = 'y';
|
||||
out.description = this.featuredGroups[k].description
|
||||
return false;
|
||||
}
|
||||
})
|
||||
}
|
||||
try {
|
||||
if (force_update || this.doesMetaNeedRefreshing(meta)) {
|
||||
out.total = 0;
|
||||
this.fs.readdirSync(g).forEach(file => {
|
||||
if (file == "meta.json") return;
|
||||
var articleNumber = parseInt(file.split('.')[0]);
|
||||
if (out.min_index == 0) out.min_index = articleNumber;
|
||||
else if (articleNumber < out.min_index) out.min_index = articleNumber;
|
||||
else if (articleNumber > out.max_index) out.max_index = articleNumber;
|
||||
|
||||
|
||||
out.total++;
|
||||
});
|
||||
if (initial_update) {
|
||||
out.last_scan = Math.floor(Date.now() / 1000);
|
||||
} else {
|
||||
meta = { ...meta, ...out }
|
||||
if (meta.wildmat) delete meta.wildmat;
|
||||
meta.last_scan = Math.floor(Date.now() / 1000);
|
||||
this.saveMetadata(group, meta);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
out.failed = e;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
getGroups(wildmat = null) {
|
||||
var groups = [];
|
||||
this.fs.readdirSync(this.data_path).forEach(file => {
|
||||
if (this.fs.lstatSync(this.data_path + this.path.sep + file).isDirectory()) {
|
||||
if (wildmat) {
|
||||
if (file.match(wildmat)) groups.push(this.selectGroup(file));
|
||||
} else groups.push(this.selectGroup(file));
|
||||
}
|
||||
});
|
||||
return groups;
|
||||
}
|
||||
|
||||
getLastArticle(group, current) {
|
||||
var g = this.getGroupPath(group);
|
||||
var res = null;
|
||||
try {
|
||||
var articleNumbers = [];
|
||||
this.fs.readdirSync(g).forEach(file => {
|
||||
if (file == "meta.json") return;
|
||||
var articleNumber = parseInt(file.split('.')[0]);
|
||||
articleNumbers.push(articleNumber);
|
||||
});
|
||||
articleNumbers.sort((a, b) => a - b)
|
||||
var index = articleNumbers.findIndex((e) => e == current) - 1;
|
||||
if (index >= 0) res = articleNumbers[index];
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
if (res) {
|
||||
if (res == current) return null;
|
||||
var message = this.getArticle(group, res);
|
||||
if (message.messageId) {
|
||||
res = { "articleNumber": res, "message_id": message.messageId };
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
getNextArticle(group, current) {
|
||||
var g = this.getGroupPath(group);
|
||||
var res = null;
|
||||
try {
|
||||
var articleNumbers = [];
|
||||
this.fs.readdirSync(g).forEach(file => {
|
||||
if (file == "meta.json") return;
|
||||
var articleNumber = parseInt(file.split('.')[0]);
|
||||
articleNumbers.push(articleNumber);
|
||||
});
|
||||
articleNumbers.sort((a, b) => a - b)
|
||||
var index = articleNumbers.findIndex((e) => e == current) + 1;
|
||||
if (index < articleNumbers.length) res = articleNumbers[index];
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
if (res) {
|
||||
var message = this.getArticle(group, res);
|
||||
if (message.messageId) {
|
||||
res = { "articleNumber": res, "message_id": message.messageId };
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
doesMetaNeedRefreshing(meta) {
|
||||
if (!meta) return true;
|
||||
if (!meta.max_index) return true;
|
||||
if (!meta.min_index) return true;
|
||||
if (!meta.total) return true;
|
||||
if (!meta.last_scan) return true;
|
||||
if (meta.last_scan) {
|
||||
if ((Math.floor(Date.now() / 1000) - this.scan_interval) > meta.last_scan) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
listGroup(group, start, end, force_update = false) {
|
||||
var g = this.getGroupPath(group);
|
||||
var out = {
|
||||
total: 0,
|
||||
min_index: 0,
|
||||
max_index: 0,
|
||||
name: group
|
||||
}
|
||||
var articles = [];
|
||||
try {
|
||||
var meta = this.getMetadata(group);
|
||||
this.fs.readdirSync(g).forEach(file => {
|
||||
if (file == "meta.json") return;
|
||||
var articleNumber = parseInt(file.split('.')[0]);
|
||||
if (articleNumber < start) return;
|
||||
if (articleNumber > end) return false;
|
||||
if (out.min_index == null) out.min_index = articleNumber;
|
||||
else if (articleNumber < out.min_index) out.min_index = articleNumber;
|
||||
|
||||
if (articleNumber > out.max_index) out.max_index = articleNumber;
|
||||
articles.push(this.getArticle(group, articleNumber));
|
||||
out.total++;
|
||||
});
|
||||
if (force_update || this.doesMetaNeedRefreshing(meta)) {
|
||||
meta = { ...meta, ...out }
|
||||
meta.last_scan = Math.floor(Date.now() / 1000);
|
||||
this.saveMetadata(group, meta);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(" * WTVNewsServer Error: listGroup: ", e);
|
||||
out.failed = e;
|
||||
}
|
||||
articles.sort((a, b) => a.index - b.index)
|
||||
if (out.min_index === null) out.min_index = 0;
|
||||
return {
|
||||
articles: articles,
|
||||
group_data: meta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WTVNewsServer;
|
||||
@@ -24,7 +24,6 @@ class WTVRegister {
|
||||
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);
|
||||
console.log(username, username.length, regex.test(username));
|
||||
return regex.test(username);
|
||||
}
|
||||
|
||||
@@ -37,12 +36,14 @@ class WTVRegister {
|
||||
if (directory) search_dir = directory;
|
||||
this.fs.readdirSync(search_dir).forEach(file => {
|
||||
if (self.fs.lstatSync(search_dir + self.path.sep + file).isDirectory() && !return_val) {
|
||||
return_val = self.checkUsernameAvailable(username, search_dir + self.path.sep + file);
|
||||
if (search_dir.match(/minisrv\_internal\_nntp/)) return;
|
||||
return_val = !self.checkUsernameAvailable(username, search_dir + self.path.sep + file);
|
||||
}
|
||||
if (!file.match(/.*\.json/ig)) return;
|
||||
if (!file.match(/user.*\.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);
|
||||
console.log(temp_session_data.subscriber_username.toLowerCase());
|
||||
if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) {
|
||||
return_val = true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Shared functions across all classes and apps
|
||||
*/
|
||||
|
||||
const CryptoJS = require('crypto-js');
|
||||
|
||||
class WTVShared {
|
||||
|
||||
@@ -9,11 +9,11 @@ class WTVShared {
|
||||
fs = require('fs');
|
||||
v8 = require('v8');
|
||||
zlib = require('zlib');
|
||||
CryptoJS = require('crypto-js');
|
||||
html_entities = require('html-entities'); // used externally by service scripts
|
||||
sanitizeHtml = require('sanitize-html');
|
||||
iconv = require('iconv-lite');
|
||||
parentDirectory = process.cwd()
|
||||
extend = require('util')._extend;
|
||||
|
||||
minisrv_config = [];
|
||||
|
||||
@@ -40,6 +40,25 @@ class WTVShared {
|
||||
}
|
||||
}
|
||||
|
||||
cloneObj(src) {
|
||||
if (src instanceof RegExp) {
|
||||
return new RegExp(src);
|
||||
} else if (src instanceof Date) {
|
||||
return new Date(src.getTime());
|
||||
} else if (typeof src === 'object' && src !== null) {
|
||||
var clone = null;
|
||||
if (Array.isArray(src)) clone = [];
|
||||
else clone = {};
|
||||
|
||||
var self = this;
|
||||
Object.keys(src).forEach((k )=> {
|
||||
clone[k] = self.cloneObj(src[k]);
|
||||
});
|
||||
return clone;
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
getServiceString(service, overrides = {}) {
|
||||
// used externally by service scripts
|
||||
if (service === "all") {
|
||||
@@ -67,7 +86,7 @@ class WTVShared {
|
||||
if (typeof val === 'string')
|
||||
val = val.toLowerCase();
|
||||
|
||||
return val === true || val === "true" || val === 1;
|
||||
return (val === true || val == "on" || val === "true" || val === 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +142,7 @@ class WTVShared {
|
||||
|
||||
|
||||
isASCII(str) {
|
||||
if (typeof str !== 'string') return false;
|
||||
for (var i = 0, strLen = str.length; i < strLen; ++i) {
|
||||
if (str.charCodeAt(i) > 127) return false;
|
||||
}
|
||||
@@ -130,7 +150,37 @@ class WTVShared {
|
||||
}
|
||||
|
||||
isHTML(str) {
|
||||
return /<[a-z][\s\S]*>/i.test(str);
|
||||
return /<\/?[a-z][\s\S]*>/i.test()
|
||||
}
|
||||
|
||||
isBase64(str, opts) {
|
||||
// from https://github.com/miguelmota/is-base64/blob/master/is-base64.js
|
||||
if (str instanceof Boolean || typeof str === 'boolean') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!(opts instanceof Object)) {
|
||||
opts = {}
|
||||
}
|
||||
|
||||
if (opts.allowEmpty === false && str === '') {
|
||||
return false
|
||||
}
|
||||
|
||||
var regex = '(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\/]{3}=)?'
|
||||
var mimeRegex = '(data:\\w+\\/[a-zA-Z\\+\\-\\.]+;base64,)'
|
||||
|
||||
if (opts.mimeRequired === true) {
|
||||
regex = mimeRegex + regex
|
||||
} else if (opts.allowMime === true) {
|
||||
regex = mimeRegex + '?' + regex
|
||||
}
|
||||
|
||||
if (opts.paddingRequired === false) {
|
||||
regex = '(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}(==)?|[A-Za-z0-9+\\/]{3}=?)?'
|
||||
}
|
||||
|
||||
return (new RegExp('^' + regex + '$', 'gi')).test(str)
|
||||
}
|
||||
|
||||
utf8Decode(utf8String) {
|
||||
@@ -197,6 +247,36 @@ class WTVShared {
|
||||
}
|
||||
}
|
||||
|
||||
moveObjectElement(currentKey, afterKey, obj, caseInsensitive = false) {
|
||||
var result = {};
|
||||
if (caseInsensitive) {
|
||||
Object.keys(obj).forEach((k) => {
|
||||
if (k.toLowerCase() == currentKey.toLowerCase()) {
|
||||
currentKey = k;
|
||||
return false;
|
||||
}
|
||||
})
|
||||
}
|
||||
var val = obj[currentKey];
|
||||
delete obj[currentKey];
|
||||
var next = -1;
|
||||
var i = 0;
|
||||
if (typeof afterKey == 'undefined' || afterKey == null) afterKey = '';
|
||||
Object.keys(obj).forEach(function (k) {
|
||||
var v = obj[k];
|
||||
if ((afterKey == '' && i == 0) || next == 1) {
|
||||
result[currentKey] = val;
|
||||
next = 0;
|
||||
}
|
||||
if (k == afterKey || (caseInsensitive && k.toLowerCase() == afterKey.toLowerCase())) { next = 1; }
|
||||
result[k] = v;
|
||||
++i;
|
||||
});
|
||||
if (next == 1) {
|
||||
result[currentKey] = val;
|
||||
}
|
||||
if (next !== -1) return result; else return obj;
|
||||
}
|
||||
|
||||
readMiniSrvConfig(user_config = true, notices = true, reload_notice = false) {
|
||||
if (notices || reload_notice) console.log(" *** Reading global configuration...");
|
||||
@@ -294,7 +374,8 @@ class WTVShared {
|
||||
var new_user_config = {};
|
||||
Object.assign(new_user_config, minisrv_user_config, config);
|
||||
if (this.minisrv_config.config.debug_flags.debug) console.log(" * Writing new user configuration...");
|
||||
this.fs.writeFileSync(this.getAbsolutePath("user_config.json", parentDirectory), JSON.stringify(new_user_config, null, "\t"));
|
||||
this.fs.writeFileSync(this.getAbsolutePath("user_config.json", this.parentDirectory), JSON.stringify(new_user_config, null, "\t"));
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
if (this.minisrv_config.config.debug_flags) {
|
||||
@@ -308,12 +389,48 @@ class WTVShared {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
generateString(len, extra_chars = null) {
|
||||
var result = '';
|
||||
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
if (extra_chars) characters += extra_chars;
|
||||
var charactersLength = characters.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() *
|
||||
charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
generatePassword(len, simple = false) {
|
||||
return this.generateString(len, (simple) ? null : '!@#$%&()[]-_+=?.');
|
||||
}
|
||||
|
||||
getMiniSrvConfig() {
|
||||
return this.minisrv_config;
|
||||
}
|
||||
|
||||
lineWrap(string, len = 72, join = "\n") {
|
||||
if (string.length <= len) return string;
|
||||
var split;
|
||||
|
||||
if (string.match(" ")) {
|
||||
// split if text with space, respecting words
|
||||
split = string.match(new RegExp('([\\s\\S]){1,' + len + '}?!\\S', "g"));
|
||||
}
|
||||
if (!split) {
|
||||
// fallback if above failed, or if its just a really long word (eg base64)
|
||||
split = string.match(new RegExp('.{1,' + len + '}', "g"));
|
||||
} else Object.keys(split).forEach((k) => {
|
||||
if (split[k].substr(0, 1) == ' ') split[k] = split[k].trim(' ');
|
||||
});
|
||||
|
||||
if (split) return split.join(join);
|
||||
else return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the Last-Modified date in Unix Timestamp format
|
||||
@@ -389,7 +506,7 @@ class WTVShared {
|
||||
return obj.substr(0, 6) + ('*').repeat(9);
|
||||
}
|
||||
} else {
|
||||
var newobj = Object.assign({}, obj);
|
||||
var newobj = this.cloneObj(obj);
|
||||
if (obj.post_data) newobj.post_data = obj.post_data;
|
||||
if (newobj["wtv-client-serial-number"]) {
|
||||
var ssid = newobj["wtv-client-serial-number"];
|
||||
@@ -411,18 +528,22 @@ class WTVShared {
|
||||
filterRequestLog(obj) {
|
||||
if (this.minisrv_config.config.filter_passwords_in_logs === true) {
|
||||
if (obj.query) {
|
||||
var newobj = Object.assign({}, obj);
|
||||
if (obj.post_data) newobj.post_data = obj.post_data;
|
||||
Object.keys(newobj.query).forEach(function (k) {
|
||||
var key = k.toLowerCase();
|
||||
switch (true) {
|
||||
case /passw(or)?d/.test(key):
|
||||
case /^pass$/.test(key):
|
||||
newobj.query[key] = ('*').repeat(newobj.query[key].length);
|
||||
break;
|
||||
}
|
||||
});
|
||||
return newobj;
|
||||
var newobj = this.cloneObj(obj);
|
||||
try {
|
||||
Object.keys(obj.query).forEach(function (k) {
|
||||
var key = k.toLowerCase();
|
||||
switch (true) {
|
||||
case /passw(or)?d/.test(key):
|
||||
case /^pass$/.test(key):
|
||||
newobj.query[key] = ('*').repeat(newobj.query[key].length);
|
||||
break;
|
||||
}
|
||||
});
|
||||
return newobj;
|
||||
} catch (e) {
|
||||
if (!this.minisrv_config.config.debug_flags.quiet) console.error(' *** error filtering logs', e);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
@@ -435,7 +556,7 @@ class WTVShared {
|
||||
var post_obj = {};
|
||||
post_obj.query = [];
|
||||
try {
|
||||
var post_text = obj.post_data.toString(this.CryptoJS.enc.Utf8);
|
||||
var post_text = obj.post_data.toString(CryptoJS.enc.Utf8);
|
||||
if (post_text.length > 0) {
|
||||
post_text = post_text.split("&");
|
||||
for (let i = 0; i < post_text.length; i++) {
|
||||
@@ -454,7 +575,7 @@ class WTVShared {
|
||||
post_text = post_text.substring(0, post_text.length - 1);
|
||||
obj.post_data = post_text.hexEncode();
|
||||
} catch (e) {
|
||||
obj.post_data = obj.post_data.toString(this.CryptoJS.enc.Hex);
|
||||
obj.post_data = obj.post_data.toString(CryptoJS.enc.Hex);
|
||||
}
|
||||
} else {
|
||||
// simple, no filter
|
||||
@@ -558,34 +679,36 @@ class WTVShared {
|
||||
|
||||
doErrorPage(code, data = null, details = null, pc_mode = false) {
|
||||
var headers = null;
|
||||
var minisrv_config = this.minisrv_config;
|
||||
switch (code) {
|
||||
case 401:
|
||||
if (data === null) data = "Authorization Required.";
|
||||
if (data === null) data = minisrv_config.config.errorMessages[code].replace(/\$\{(\w{1,})\}/g, function (x) { return minisrv_config.config[x.replace("${", '').replace('}', '')] });
|
||||
if (pc_mode) headers = "401 Unauthorized\n";
|
||||
else headers = code + " " + data + "\n";
|
||||
headers += "Content-Type: text/html\n";
|
||||
break;
|
||||
case 403:
|
||||
if (data === null) data = "The publisher of that page has not authorized you to view it.";
|
||||
if (data === null) data = minisrv_config.config.errorMessages[code].replace(/\$\{(\w{1,})\}/g, function (x) { return minisrv_config.config[x.replace("${", '').replace('}', '')] });
|
||||
if (pc_mode) headers = "403 Forbidden\n";
|
||||
else headers = code + " " + data + "\n";
|
||||
headers += "Content-Type: text/html\n";
|
||||
break;
|
||||
case 404:
|
||||
if (data === null) data = "The service could not find the requested page.";
|
||||
if (data === null) data = minisrv_config.config.errorMessages[code].replace(/\$\{(\w{1,})\}/g, function (x) { return minisrv_config.config[x.replace("${", '').replace('}', '')] });
|
||||
if (pc_mode) headers = "404 Not Found\n";
|
||||
else headers = code + " " + data + "\n";
|
||||
headers += "Content-Type: text/html\n";
|
||||
break;
|
||||
case 400:
|
||||
case 500:
|
||||
if (data === null) data = this.minisrv_config.config.service_name + " ran into a technical problem.";
|
||||
if (data === null) data = minisrv_config.config.errorMessages[code].replace(/\$\{(\w{1,})\}/g, function (x) { return minisrv_config.config[x.replace("${", '').replace('}', '')] });
|
||||
if (details) data += "<br>Details:<br>" + details;
|
||||
if (pc_mode) headers = "500 Internal Server Error\n";
|
||||
else headers = code + " " + data + "\n";
|
||||
headers += "Content-Type: text/html\n";
|
||||
break;
|
||||
default:
|
||||
if (data === null && this.minisrv_config.config.errorMessages[code]) data = minisrv_config.config.errorMessages[code].replace(/\$\{(.+)\}/g, function (x) { return minisrv_config.config[x.replace("${",'').replace('}','')] });
|
||||
headers = code + " " + data + "\n";
|
||||
headers += "Content-Type: text/html\n";
|
||||
break;
|
||||
@@ -652,7 +775,23 @@ class WTVShared {
|
||||
return this.zlib.deflateSync(data, { 'level': 9 }).toString('base64');
|
||||
}
|
||||
|
||||
|
||||
getTemplate(service_name, path, path_only = false) {
|
||||
var self = this;
|
||||
var outdata = null;
|
||||
var found = false
|
||||
this.minisrv_config.config.ServiceTemplates.forEach(function (template_vault_dir) {
|
||||
if (found) return;
|
||||
var search = self.getAbsolutePath(template_vault_dir + self.path.sep + service_name + self.path.sep + path);
|
||||
if (self.fs.existsSync(search)) {
|
||||
if (path_only) outdata = search;
|
||||
else outdata = self.fs.readFileSync(search);
|
||||
if (!self.minisrv_config.config.debug_flags.quiet) console.log(" * Found " + search + " to handle template");
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return outdata;
|
||||
}
|
||||
}
|
||||
|
||||
class clientShowAlert {
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
"UserServiceVault",
|
||||
"ServiceVault"
|
||||
],
|
||||
"ServiceDeps": "ServiceDeps",
|
||||
"ServiceTemplates": [
|
||||
"UserTemplates",
|
||||
"ServiceDeps/templates"
|
||||
],
|
||||
"ServiceDeps": "ServiceDeps",
|
||||
"SessionStore": "SessionStore",
|
||||
"SharedROMCache": "SharedROMCache",
|
||||
"enable_shared_romcache": true,
|
||||
@@ -31,8 +35,12 @@
|
||||
"show_detailed_splash": true,
|
||||
"show_diskmap": false,
|
||||
"unauthorized_url": "wtv-1800:/unauthorized?",
|
||||
"enable_port_isolation": true,
|
||||
"allow_guests": true,
|
||||
"domain_name": "wtv.zefie.com",
|
||||
"ssid_block_list": [
|
||||
"minisrv_internal_nntp"
|
||||
],
|
||||
"user_accounts": {
|
||||
"max_users_per_account": 6,
|
||||
"min_username_length": 5,
|
||||
@@ -43,7 +51,30 @@
|
||||
"min_length": 5,
|
||||
"max_length": 32,
|
||||
"form_size": 16
|
||||
}
|
||||
},
|
||||
"errorMessages": {
|
||||
"400": "${service_name} ran into a technical problem. Please try again.",
|
||||
"401": "Authorization Required.",
|
||||
"403": "The publisher of that page has not authorized you to view it.",
|
||||
"404": "The service could not find the requested page.",
|
||||
"500": "${service_name} ran into a technical problem. Please try again."
|
||||
},
|
||||
"lockdownWhitelist": [
|
||||
"wtv-1800:/preregister",
|
||||
"wtv-head-waiter:/login",
|
||||
"wtv-head-waiter:/ValidateLogin",
|
||||
"wtv-head-waiter:/login-stage-two",
|
||||
"wtv-head-waiter:/relogin",
|
||||
"wtv-head-waiter:/ROMCache/Spacer.gif",
|
||||
"wtv-head-waiter:/ROMCache/NameStrip.gif",
|
||||
"wtv-head-waiter:/images/PasswordBanner.gif",
|
||||
"wtv-head-waiter:/ROMCache/UtilityBullet.gif",
|
||||
"wtv-head-waiter:/images/NameBanner.gif",
|
||||
"wtv-head-waiter:/bad-disk",
|
||||
"wtv-head-waiter:/images/signin_new_mail.gif",
|
||||
"wtv-head-waiter:/images/signin_no_mail.gif",
|
||||
"wtv-log:/log"
|
||||
]
|
||||
},
|
||||
"services": {
|
||||
"wtv-head-waiter": {
|
||||
@@ -60,10 +91,34 @@
|
||||
},
|
||||
"wtv-news": {
|
||||
"port": 1605,
|
||||
"disabled": true,
|
||||
"local_nntp_port": 57319,
|
||||
"local_nntp_requires_auth": true,
|
||||
"modules": [
|
||||
"WTVNews"
|
||||
]
|
||||
],
|
||||
"featuredGroups": [
|
||||
{
|
||||
"name": "WebTV",
|
||||
"group": "webtv.users",
|
||||
"description": "A moderated discussion with WebTV customers"
|
||||
},
|
||||
{
|
||||
"name": "Hacking",
|
||||
"group": "alt.discuss.webtv.hacking",
|
||||
"description": "Not advertiser friendly"
|
||||
},
|
||||
{
|
||||
"name": "minisrv",
|
||||
"group": "minisrv.users",
|
||||
"description": "The server behind it all"
|
||||
},
|
||||
{
|
||||
"name": "MIDIs",
|
||||
"group": "alt.discuss.midis",
|
||||
"description": "Explore the sounds of Beatnik with your WebTV"
|
||||
}
|
||||
],
|
||||
"groupMetaRefreshInterval": 86400
|
||||
},
|
||||
"wtv-register": {
|
||||
"port": 1607,
|
||||
@@ -122,11 +177,17 @@
|
||||
},
|
||||
"wtv-guide": {
|
||||
"port": 1621,
|
||||
"connections": 3
|
||||
"connections": 3,
|
||||
"modules": [
|
||||
"WTVGuide"
|
||||
]
|
||||
},
|
||||
"wtv-mail": {
|
||||
"port": 1608,
|
||||
"connections": 3
|
||||
"connections": 3,
|
||||
"modules": [
|
||||
"WTVNews"
|
||||
]
|
||||
},
|
||||
"wtv-passport": {
|
||||
"port": 1654
|
||||
|
||||
Reference in New Issue
Block a user