1
0
Fork 0
forked from svrjs/svrjs

Update to SVR.JS 3.12.0

This commit is contained in:
Dorian Niemiec 2023-12-03 16:19:34 +01:00
parent a63e1a893b
commit 19f7345762
5 changed files with 395 additions and 292 deletions

View file

@ -3,7 +3,7 @@
"port": 80, "port": 80,
"pubport": 80, "pubport": 80,
"page404": "404.html", "page404": "404.html",
"timestamp": 1692493855777, "timestamp": 1701600932028,
"blacklist": [], "blacklist": [],
"nonStandardCodes": [], "nonStandardCodes": [],
"enableCompression": true, "enableCompression": true,
@ -89,14 +89,22 @@
"dontCompress": [ "dontCompress": [
"/.*\\.ipxe$/", "/.*\\.ipxe$/",
"/.*\\.img$/", "/.*\\.img$/",
"/.*\\.iso$/" "/.*\\.iso$/",
"/.*\\.png$/",
"/.*\\.jpg$/",
"/.*\\.webp$/"
], ],
"enableIPSpoofing": false, "enableIPSpoofing": true,
"secure": false, "secure": false,
"sni": {}, "sni": {},
"disableNonEncryptedServer": false, "disableNonEncryptedServer": false,
"disableToHTTPSRedirect": false, "disableToHTTPSRedirect": false,
"enableETag": true, "enableETag": true,
"disableUnusedWorkerTermination": false, "disableUnusedWorkerTermination": false,
"rewriteDirtyURLs": false "rewriteDirtyURLs": true,
"errorPages": [],
"useWebRootServerSideScript": true,
"exposeModsInErrorPages": true,
"disableTrailingSlashRedirects": false,
"environmentVariables": {}
} }

View file

@ -25,7 +25,7 @@
&nbsp;&nbsp;"port": 80,<br/> &nbsp;&nbsp;"port": 80,<br/>
&nbsp;&nbsp;"pubport": 80,<br/> &nbsp;&nbsp;"pubport": 80,<br/>
&nbsp;&nbsp;"page404": "404.html",<br/> &nbsp;&nbsp;"page404": "404.html",<br/>
&nbsp;&nbsp;"timestamp": 1680954429282,<br/> &nbsp;&nbsp;"timestamp": 1701600932028,<br/>
&nbsp;&nbsp;"blacklist": [],<br/> &nbsp;&nbsp;"blacklist": [],<br/>
&nbsp;&nbsp;"nonStandardCodes": [],<br/> &nbsp;&nbsp;"nonStandardCodes": [],<br/>
&nbsp;&nbsp;"enableCompression": true,<br/> &nbsp;&nbsp;"enableCompression": true,<br/>
@ -40,6 +40,7 @@
&nbsp;&nbsp;"exposeServerVersion": true,<br/> &nbsp;&nbsp;"exposeServerVersion": true,<br/>
&nbsp;&nbsp;"disableServerSideScriptExpose": true,<br/> &nbsp;&nbsp;"disableServerSideScriptExpose": true,<br/>
&nbsp;&nbsp;"rewriteMap": [<br/> &nbsp;&nbsp;"rewriteMap": [<br/>
&nbsp;&nbsp;&nbsp;&nbsp;{<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"definingRegex": "/^\\/serverSideScript\\.js(?:$|[#?])/",<br/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"definingRegex": "/^\\/serverSideScript\\.js(?:$|[#?])/",<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"replacements": [<br/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"replacements": [<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br/>
@ -104,23 +105,41 @@
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"replacement": "/invoke500.svr"<br/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"replacement": "/invoke500.svr"<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br/>
&nbsp;&nbsp;&nbsp;&nbsp;}<br/> &nbsp;&nbsp;],<br/> &nbsp;&nbsp;&nbsp;&nbsp;}<br/>
&nbsp;&nbsp;],<br/>
&nbsp;&nbsp;"allowStatus": true,<br/> &nbsp;&nbsp;"allowStatus": true,<br/>
&nbsp;&nbsp;"dontCompress": ["/.*\\.ipxe$/","/.*\\.img$/","/.*\\.iso$/"],<br/> &nbsp;&nbsp;"dontCompress": [<br/>
&nbsp;&nbsp;"enableIPSpoofing": false,<br/> &nbsp;&nbsp;&nbsp;&nbsp;"/.*\\.ipxe$/",<br/>
&nbsp;&nbsp;&nbsp;&nbsp;"/.*\\.img$/",<br/>
&nbsp;&nbsp;&nbsp;&nbsp;"/.*\\.iso$/",<br/>
&nbsp;&nbsp;&nbsp;&nbsp;"/.*\\.png$/",<br/>
&nbsp;&nbsp;&nbsp;&nbsp;"/.*\\.jpg$/",<br/>
&nbsp;&nbsp;&nbsp;&nbsp;"/.*\\.webp$/"<br/>
&nbsp;&nbsp;],<br/>
&nbsp;&nbsp;"enableIPSpoofing": true,<br/>
&nbsp;&nbsp;"secure": false,<br/> &nbsp;&nbsp;"secure": false,<br/>
&nbsp;&nbsp;"sni": {},<br/> &nbsp;&nbsp;"sni": {},<br/>
&nbsp;&nbsp;"disableNonEncryptedServer": false,<br/> &nbsp;&nbsp;"disableNonEncryptedServer": false,<br/>
&nbsp;&nbsp;"disableToHTTPSRedirect": false<br/> &nbsp;&nbsp;"disableToHTTPSRedirect": false,<br/>
&nbsp;&nbsp;"enableETag": true,<br/>
&nbsp;&nbsp;"disableUnusedWorkerTermination": false,<br/>
&nbsp;&nbsp;"rewriteDirtyURLs": true,<br/>
&nbsp;&nbsp;"errorPages": [],<br/>
&nbsp;&nbsp;"useWebRootServerSideScript": true,<br/>
&nbsp;&nbsp;"exposeModsInErrorPages": true,<br/>
&nbsp;&nbsp;"disableTrailingSlashRedirects": false,<br/>
&nbsp;&nbsp;"environmentVariables": {}<br/>
} }
</code> </code>
</div> </div>
<p>Changes:</p> <p>Changes:</p>
<ul> <ul>
<li>SVR.JS now sends configuration file saving request to one random good worker instead of all workers to prevent configuration file corruption.</li> <li>Added trailing slash redirect support.</li>
<li>Fixed crashes due to destroyed HTTP/2 stream (Node.JS bug: <a href="https://github.com/nodejs/node/issues/24470">https://github.com/nodejs/node/issues/24470</a>)</li> <li>Added new <i>config.json</i> property &mdash; <i>environmentVariables</i>.</li>
<li>Fixed language errors in HTTP error code descriptions, error console messages and the index page.</li> <li>Replaces base 1000 size prefixes with base 1024 ones.</li>
<li>Updated the logo in the SVR.JS log viewer.</li> <li>Invalid compression exclusion list regexes no longer crash SVR.JS.</li>
<li>Changed invalid regex error message.</li>
<li>Corrected language errors &mdash; replaced <i>recieve</i> with <i>receive</i>.</li>
</ul> </ul>
<br/> <br/>
<a href="/tests.html">Tests</a><br/> <a href="/tests.html">Tests</a><br/>

View file

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>SVR.JS 3.11.0 Licenses</title> <title>SVR.JS 3.12.0 Licenses</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<style> <style>
@ -12,8 +12,8 @@
</style> </style>
</head> </head>
<body> <body>
<h1>SVR.JS 3.11.0 Licenses</h1> <h1>SVR.JS 3.12.0 Licenses</h1>
<h2>SVR.JS 3.11.0</h2> <h2>SVR.JS 3.12.0</h2>
<div style="display: inline-block; text-align: left; border-width: 2px; border-style: solid; border-color: gray; padding: 8px;"> <div style="display: inline-block; text-align: left; border-width: 2px; border-style: solid; border-color: gray; padding: 8px;">
MIT License<br/> MIT License<br/>
<br/> <br/>
@ -37,7 +37,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE<br/> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE<br/>
SOFTWARE.<br/> SOFTWARE.<br/>
</div> </div>
<h2>Packages used by SVR.JS 3.11.0 and utilities</h2> <h2>Packages used by SVR.JS 3.12.0</h2>
<div style="width: 100%; background-color: #ccc; border: 1px solid green; text-align: left; margin: 10px 0;"> <div style="width: 100%; background-color: #ccc; border: 1px solid green; text-align: left; margin: 10px 0;">
<div style="float: right;">License: MIT</div> <div style="float: right;">License: MIT</div>
<div style="font-size: 20px;"> <div style="font-size: 20px;">

168
svr.js
View file

@ -1,4 +1,3 @@
///////////////////////////////////////// /////////////////////////////////////////
// // // //
// S V R . J S // // S V R . J S //
@ -81,7 +80,7 @@ function deleteFolderRecursive(path) {
} }
var os = require("os"); var os = require("os");
var version = "3.11.0"; var version = "3.12.0";
var singlethreaded = false; var singlethreaded = false;
if (process.versions) process.versions.svrjs = version; // Inject SVR.JS into process.versions if (process.versions) process.versions.svrjs = version; // Inject SVR.JS into process.versions
@ -312,6 +311,7 @@ if (!singlethreaded) {
// ETag-related // ETag-related
var ETagDB = {}; var ETagDB = {};
function generateETag(filePath, stat) { function generateETag(filePath, stat) {
if (!ETagDB[filePath + "-" + stat.size + "-" + stat.mtime]) ETagDB[filePath + "-" + stat.size + "-" + stat.mtime] = sha256(filePath + "-" + stat.size + "-" + stat.mtime); if (!ETagDB[filePath + "-" + stat.size + "-" + stat.mtime]) ETagDB[filePath + "-" + stat.size + "-" + stat.mtime] = sha256(filePath + "-" + stat.size + "-" + stat.mtime);
return ETagDB[filePath + "-" + stat.size + "-" + stat.mtime]; return ETagDB[filePath + "-" + stat.size + "-" + stat.mtime];
@ -500,15 +500,16 @@ if (!fs.existsSync(__dirname + "/temp")) fs.mkdirSync(__dirname + "/temp");
var modFiles = fs.readdirSync(__dirname + "/mods").sort(); var modFiles = fs.readdirSync(__dirname + "/mods").sort();
var modInfos = []; var modInfos = [];
function sizify(bytes) { function sizify(bytes, addI) {
if (bytes == 0) return "0"; if (bytes == 0) return "0";
if (bytes < 0) bytes = -bytes; if (bytes < 0) bytes = -bytes;
var prefixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"]; var prefixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"];
var exponent = Math.floor(Math.log10 ? Math.log10(bytes) : (Math.log(bytes) / Math.log(10))); var prefixIndex = Math.floor(Math.log2 ? Math.log2(bytes) / 10 : (Math.log(bytes) / (Math.log(2) * 10)));
var prefixIndex = Math.floor(exponent / 3);
if (prefixIndex >= prefixes.length - 1) prefixIndex = prefixes.length - 1; if (prefixIndex >= prefixes.length - 1) prefixIndex = prefixes.length - 1;
var prefixIndexMod = exponent - (prefixIndex * 3); var prefixIndexTranslated = Math.pow(2, 10 * prefixIndex);
return (Math.round(bytes / Math.pow(10, Math.max(0, exponent - Math.max(2, prefixIndexMod)))) / (exponent > 2 ? Math.pow(10, Math.max(0, 2 - prefixIndexMod)) : 1)).toString() + prefixes[prefixIndex]; var decimalPoints = 2 - Math.floor(Math.log10 ? Math.log10(bytes / prefixIndexTranslated) : (Math.log(bytes / prefixIndexTranslated) / Math.log(10)));
if (decimalPoints < 0) decimalPoints = 0;
return (Math.ceil((bytes / prefixIndexTranslated) * Math.pow(10, decimalPoints)) / Math.pow(10, decimalPoints)) + prefixes[prefixIndex] + ((prefixIndex > 0 && addI) ? "i" : "");
} }
function getOS() { function getOS() {
@ -542,7 +543,7 @@ function getOS() {
function createRegex(regex, isPath) { function createRegex(regex, isPath) {
var regexStrMatch = regex.match(/^\/((?:\\.|[^\/\\])*)\/([a-zA-Z0-9]*)$/); var regexStrMatch = regex.match(/^\/((?:\\.|[^\/\\])*)\/([a-zA-Z0-9]*)$/);
if (!regexStrMatch) throw new Error("Invalid regex!"); if (!regexStrMatch) throw new Error("Invalid regular expression: " + regex);
var searchString = regexStrMatch[1]; var searchString = regexStrMatch[1];
var modifiers = regexStrMatch[2]; var modifiers = regexStrMatch[2];
if (isPath && !modifiers.match(/i/i) && os.platform() == "win32") modifiers += "i"; if (isPath && !modifiers.match(/i/i) && os.platform() == "win32") modifiers += "i";
@ -1102,6 +1103,8 @@ var rewriteDirtyURLs = false;
var errorPages = []; var errorPages = [];
var useWebRootServerSideScript = true; var useWebRootServerSideScript = true;
var exposeModsInErrorPages = true; var exposeModsInErrorPages = true;
var disableTrailingSlashRedirects = false;
var environmentVariables = {};
// Get properties from config.json // Get properties from config.json
if (configJSON.blacklist != undefined) rawBlackList = configJSON.blacklist; if (configJSON.blacklist != undefined) rawBlackList = configJSON.blacklist;
@ -1153,6 +1156,8 @@ if (configJSON.rewriteDirtyURLs != undefined) rewriteDirtyURLs = configJSON.rewr
if (configJSON.errorPages != undefined) errorPages = configJSON.errorPages; if (configJSON.errorPages != undefined) errorPages = configJSON.errorPages;
if (configJSON.useWebRootServerSideScript != undefined) useWebRootServerSideScript = configJSON.useWebRootServerSideScript; if (configJSON.useWebRootServerSideScript != undefined) useWebRootServerSideScript = configJSON.useWebRootServerSideScript;
if (configJSON.exposeModsInErrorPages != undefined) exposeModsInErrorPages = configJSON.exposeModsInErrorPages; if (configJSON.exposeModsInErrorPages != undefined) exposeModsInErrorPages = configJSON.exposeModsInErrorPages;
if (configJSON.disableTrailingSlashRedirects != undefined) disableTrailingSlashRedirects = configJSON.disableTrailingSlashRedirects;
if (configJSON.environmentVariables != undefined) environmentVariables = configJSON.environmentVariables;
var wwwrootError = null; var wwwrootError = null;
try { try {
@ -1161,6 +1166,14 @@ try {
wwwrootError = err; wwwrootError = err;
} }
try {
Object.keys(environmentVariables).forEach(function (key) {
process.env[key] = environmentVariables[key];
});
} catch (err) {
// Failed to set environment variables.
}
// Compability for older mods // Compability for older mods
configJSON.version = version; configJSON.version = version;
configJSON.productName = "SVR.JS"; configJSON.productName = "SVR.JS";
@ -1545,7 +1558,10 @@ if (!disableMods) {
} }
} }
} catch (err) { } catch (err) {
modLoadingErrors.push({error: err, modName: modFileRaw}); modLoadingErrors.push({
error: err,
modName: modFileRaw
});
} }
} }
}); });
@ -2026,7 +2042,7 @@ if (!cluster.isPrimary) {
} else { } else {
try { try {
process.send("\x12ERRLIST" + attmtsRedir + err.code); process.send("\x12ERRLIST" + attmtsRedir + err.code);
} catch(ex) { } catch (err) {
// Probably main process exited // Probably main process exited
} }
} }
@ -2036,7 +2052,7 @@ if (!cluster.isPrimary) {
} else { } else {
try { try {
if (cluster.isPrimary !== undefined) process.send("\x12ERRCRASH" + err.code); if (cluster.isPrimary !== undefined) process.send("\x12ERRCRASH" + err.code);
} catch(ex) { } catch (err) {
// Probably main process exited // Probably main process exited
} }
setTimeout(function () { setTimeout(function () {
@ -2319,6 +2335,7 @@ if (!cluster.isPrimary) {
callback(errorCode.toString() + ".html"); callback(errorCode.toString() + ".html");
return; return;
} }
function medCallback(p) { function medCallback(p) {
if (p) callback(p); if (p) callback(p);
else { else {
@ -3295,14 +3312,14 @@ if (!cluster.isPrimary) {
var levelDownCount = 0; var levelDownCount = 0;
// Loop through the path components // Loop through the path components
for (var i = 0; i < pathComponents.length; i += 1) { for (var i = 0; i < pathComponents.length; i++) {
// If the component is "..", decrement the levelUpCount // If the component is "..", decrement the levelUpCount
if (".." === pathComponents[i]) { if (".." === pathComponents[i]) {
levelUpCount -= 1; levelUpCount--;
} }
// If the component is not "." or an empty string, increment the levelDownCount // If the component is not "." or an empty string, increment the levelDownCount
else if ("." !== pathComponents[i] && "" !== pathComponents[i]) { else if ("." !== pathComponents[i] && "" !== pathComponents[i]) {
levelDownCount += 1; levelDownCount++;
} }
} }
@ -3317,7 +3334,7 @@ if (!cluster.isPrimary) {
if (isProxy) { if (isProxy) {
var eheaders = getCustomHeaders(); var eheaders = getCustomHeaders();
eheaders["Content-Type"] = "text/html; charset=utf-8"; eheaders["Content-Type"] = "text/html; charset=utf-8";
res.writeHead(501, "Not implemented", eheaders); res.writeHead(501, "Not Implemented", eheaders);
res.write("<html><head><title>Proxy not implemented</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /></head><body><h1>Proxy not implemented</h1><p>SVR.JS doesn't support proxy without proxy mod. If you're administator of this server, then install this mod in order to use SVR.JS as a proxy.</p><p><i>" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + "</i></p></body></html>"); res.write("<html><head><title>Proxy not implemented</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /></head><body><h1>Proxy not implemented</h1><p>SVR.JS doesn't support proxy without proxy mod. If you're administator of this server, then install this mod in order to use SVR.JS as a proxy.</p><p><i>" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + "</i></p></body></html>");
res.end(); res.end();
serverconsole.errmessage("SVR.JS doesn't support proxy without proxy mod."); serverconsole.errmessage("SVR.JS doesn't support proxy without proxy mod.");
@ -3345,7 +3362,7 @@ if (!cluster.isPrimary) {
var hdhds = getCustomHeaders(); var hdhds = getCustomHeaders();
hdhds["Content-Type"] = "text/html; charset=utf-8"; hdhds["Content-Type"] = "text/html; charset=utf-8";
res.writeHead(200, "OK", hdhds); res.writeHead(200, "OK", hdhds);
res.end((head == "" ? "<html><head><title>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) + "</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /></head><body>" : head.replace(/<head>/i, "<head><title>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) + "</title>")) + "<h1>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) + "</h1>Server version: " + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + "<br/><hr/>Current time: " + new Date().toString() + "<br/>Thread start time: " + new Date(new Date() - (process.uptime() * 1000)).toString() + "<br/>Thread uptime: " + formatRelativeTime(Math.floor(process.uptime())) + "<br/>OS uptime: " + formatRelativeTime(os.uptime()) + "<br/>Total request count: " + reqcounter + "<br/>Average request rate: " + (Math.round((reqcounter / process.uptime()) * 100) / 100) + " requests/s" + (process.memoryUsage ? ("<br/>Memory usage of thread: " + sizify(process.memoryUsage().rss) + "B") : "") + (process.cpuUsage ? ("<br/>Total CPU usage by thread: u" + (process.cpuUsage().user / 1000) + "ms s" + (process.cpuUsage().system / 1000) + "ms - " + (Math.round((((process.cpuUsage().user + process.cpuUsage().system) / 1000000) / process.uptime()) * 1000) / 1000) + "%") : "") + "<br/>Thread PID: " + process.pid + "<br/>" + (foot == "" ? "</body></html>" : foot)); res.end((head == "" ? "<html><head><title>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) + "</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /></head><body>" : head.replace(/<head>/i, "<head><title>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) + "</title>")) + "<h1>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) + "</h1>Server version: " + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + "<br/><hr/>Current time: " + new Date().toString() + "<br/>Thread start time: " + new Date(new Date() - (process.uptime() * 1000)).toString() + "<br/>Thread uptime: " + formatRelativeTime(Math.floor(process.uptime())) + "<br/>OS uptime: " + formatRelativeTime(os.uptime()) + "<br/>Total request count: " + reqcounter + "<br/>Average request rate: " + (Math.round((reqcounter / process.uptime()) * 100) / 100) + " requests/s" + (process.memoryUsage ? ("<br/>Memory usage of thread: " + sizify(process.memoryUsage().rss, true) + "B") : "") + (process.cpuUsage ? ("<br/>Total CPU usage by thread: u" + (process.cpuUsage().user / 1000) + "ms s" + (process.cpuUsage().system / 1000) + "ms - " + (Math.round((((process.cpuUsage().user + process.cpuUsage().system) / 1000000) / process.uptime()) * 1000) / 1000) + "%") : "") + "<br/>Thread PID: " + process.pid + "<br/>" + (foot == "" ? "</body></html>" : foot));
return; return;
} }
@ -3444,26 +3461,26 @@ if (!cluster.isPrimary) {
res.writeHead(200, http.STATUS_CODES[200], customHeaders); res.writeHead(200, http.STATUS_CODES[200], customHeaders);
// Read custom header and footer content (if available) // Read custom header and footer content (if available)
var customDirListingHeader = fs.existsSync(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/")) var customDirListingHeader = fs.existsSync(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/")) ?
? fs.readFileSync(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/")).toString() fs.readFileSync(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/")).toString() :
: (fs.existsSync(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/")) && (os.platform != "win32" || href != "/")) (fs.existsSync(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/")) && (os.platform != "win32" || href != "/")) ?
? fs.readFileSync(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/")).toString() fs.readFileSync(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/")).toString() :
: ""; "";
var customDirListingFooter = fs.existsSync(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/")) var customDirListingFooter = fs.existsSync(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/")) ?
? fs.readFileSync(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/")).toString() fs.readFileSync(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/")).toString() :
: (fs.existsSync(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/")) && (os.platform != "win32" || href != "/")) (fs.existsSync(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/")) && (os.platform != "win32" || href != "/")) ?
? fs.readFileSync(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/")).toString() fs.readFileSync(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/")).toString() :
: ""; "";
// Check if custom header has HTML tag // Check if custom header has HTML tag
var headerHasHTMLTag = customDirListingHeader.replace(/<!--(?:(?:(?!--\>)[\s\S])*|)(?:-->|$)/g, "").match(/<html(?![a-zA-Z0-9])(?:"(?:\\(?:[\s\S]|$)|[^\\"])*(?:"|$)|'(?:\\(?:[\s\S]|$)|[^\\'])*(?:'|$)|[^'">])*(?:>|$)/i); var headerHasHTMLTag = customDirListingHeader.replace(/<!--(?:(?:(?!--\>)[\s\S])*|)(?:-->|$)/g, "").match(/<html(?![a-zA-Z0-9])(?:"(?:\\(?:[\s\S]|$)|[^\\"])*(?:"|$)|'(?:\\(?:[\s\S]|$)|[^\\'])*(?:'|$)|[^'">])*(?:>|$)/i);
// Generate HTML head and footer based on configuration and custom content // Generate HTML head and footer based on configuration and custom content
var htmlHead = (!configJSON.enableDirectoryListingWithDefaultHead || head == "" var htmlHead = (!configJSON.enableDirectoryListingWithDefaultHead || head == "" ?
? (!headerHasHTMLTag (!headerHasHTMLTag ?
? "<!DOCTYPE html><html><head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</title><meta charset=\"UTF-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /></head><body>" "<!DOCTYPE html><html><head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</title><meta charset=\"UTF-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /></head><body>" :
: customDirListingHeader.replace(/<head>/i, "<head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</title>")) customDirListingHeader.replace(/<head>/i, "<head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</title>")) :
: head.replace(/<head>/i, "<head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</title>")) + head.replace(/<head>/i, "<head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</title>")) +
(!headerHasHTMLTag ? customDirListingHeader : "") + (!headerHasHTMLTag ? customDirListingHeader : "") +
"<h1>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</h1><table id=\"directoryListing\"> <tr> <th></th> <th>Filename</th> <th>Size</th> <th>Date</th> </tr>" + (checkPathLevel(decodeURIComponent(origHref)) < 1 ? "" : "<tr><td style=\"width: 24px;\"><img src=\"/.dirimages/return.png\" width=\"24px\" height=\"24px\" alt=\"[RET]\" /></td><td style=\"word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;\"><a href=\"" + (origHref).replace(/\/+/g, "/").replace(/\/[^\/]*\/?$/, "/") + "\">Return</a></td><td></td><td></td></tr>"); "<h1>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</h1><table id=\"directoryListing\"> <tr> <th></th> <th>Filename</th> <th>Size</th> <th>Date</th> </tr>" + (checkPathLevel(decodeURIComponent(origHref)) < 1 ? "" : "<tr><td style=\"width: 24px;\"><img src=\"/.dirimages/return.png\" width=\"24px\" height=\"24px\" alt=\"[RET]\" /></td><td style=\"word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;\"><a href=\"" + (origHref).replace(/\/+/g, "/").replace(/\/[^\/]*\/?$/, "/") + "\">Return</a></td><td></td><td></td></tr>");
@ -3750,7 +3767,13 @@ if (!cluster.isPrimary) {
return canCompress; return canCompress;
} }
var isCompressable = canCompress(href, dontCompress); var isCompressable = true;
try {
isCompressable = canCompress(href, dontCompress);
} catch (err) {
callServerError(500, undefined, generateErrorStack(err));
return;
}
// Check for browser quirks and adjust compression accordingly // Check for browser quirks and adjust compression accordingly
if (ext != "html" && ext != "htm" && ext != "xhtml" && ext != "xht" && ext != "shtml" && /^Mozilla\/4\.[0-9]+(( *\[[^)]*\] *| *)\([^)\]]*\))? *$/.test(req.headers["user-agent"]) && !(/https?:\/\/|[bB][oO][tT]|[sS][pP][iI][dD][eE][rR]|[sS][uU][rR][vV][eE][yY]|MSI[E]/.test(req.headers["user-agent"]))) { if (ext != "html" && ext != "htm" && ext != "xhtml" && ext != "xht" && ext != "shtml" && /^Mozilla\/4\.[0-9]+(( *\[[^)]*\] *| *)\([^)\]]*\))? *$/.test(req.headers["user-agent"]) && !(/https?:\/\/|[bB][oO][tT]|[sS][pP][iI][dD][eE][rR]|[sS][uU][rR][vV][eE][yY]|MSI[E]/.test(req.headers["user-agent"]))) {
@ -3764,7 +3787,6 @@ if (!cluster.isPrimary) {
// Handle partial content request // Handle partial content request
if (ext != "html" && req.headers["range"]) { if (ext != "html" && req.headers["range"]) {
try { try {
if (err) throw err;
var rhd = getCustomHeaders(); var rhd = getCustomHeaders();
rhd["Accept-Ranges"] = "bytes"; rhd["Accept-Ranges"] = "bytes";
rhd["Content-Range"] = "bytes */" + filelen; rhd["Content-Range"] = "bytes */" + filelen;
@ -3840,7 +3862,6 @@ if (!cluster.isPrimary) {
} }
} else { } else {
try { try {
if (err) throw err;
var hdhds = getCustomHeaders(); var hdhds = getCustomHeaders();
if (configJSON.enableCompression === true && ext != "br" && filelen > 256 && isCompressable && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/)) { if (configJSON.enableCompression === true && ext != "br" && filelen > 256 && isCompressable && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/)) {
hdhds["Content-Encoding"] = "br"; hdhds["Content-Encoding"] = "br";
@ -3958,7 +3979,7 @@ if (!cluster.isPrimary) {
res.end(); res.end();
return; return;
} else { } else {
// SVR.JS doesn't understand that request, throw a 400 error // SVR.JS doesn't understand that request, so throw an 400 error
callServerError(400); callServerError(400);
return; return;
} }
@ -4288,11 +4309,40 @@ if (!cluster.isPrimary) {
return; return;
} else { } else {
callServerError(nonscode.scode); callServerError(nonscode.scode);
serverconsole.errmessage("Client fails recieving content."); serverconsole.errmessage("Client fails receiving content.");
return; return;
} }
} }
// Trailing slash redirection
function redirectTrailingSlashes(callback) {
if (!disableTrailingSlashRedirects && href[href.length - 1] != "/" && origHref[origHref.length - 1] != "/") {
fs.stat("." + decodeURIComponent(href), function (err, stats) {
if (err || !stats.isDirectory()) {
try {
callback();
} catch (err) {
callServerError(500, undefined, err);
}
} else {
var destinationURL = uobject;
destinationURL.path = null;
destinationURL.href = null;
destinationURL.pathname = origHref + "/";
destinationURL.hostname = null;
destinationURL.host = null;
destinationURL.port = null;
destinationURL.protocol = null;
destinationURL.slashes = null;
destinationURL = url.format(destinationURL);
redirect(destinationURL);
}
});
} else {
callback();
}
}
// Handle HTTP authentication // Handle HTTP authentication
if (authIndex > -1) { if (authIndex > -1) {
var authcode = nonStandardCodes[authIndex]; var authcode = nonStandardCodes[authIndex];
@ -4327,7 +4377,12 @@ if (!cluster.isPrimary) {
callServerError(500, undefined, err); callServerError(500, undefined, err);
} else { } else {
var key = derivedKey.toString("hex"); var key = derivedKey.toString("hex");
scryptCache.push({hash: key, password: hashedPassword, salt: list[_i].salt, addDate: new Date()}); scryptCache.push({
hash: key,
password: hashedPassword,
salt: list[_i].salt,
addDate: new Date()
});
cb(key); cb(key);
} }
}); });
@ -4349,7 +4404,12 @@ if (!cluster.isPrimary) {
callServerError(500, undefined, err); callServerError(500, undefined, err);
} else { } else {
var key = derivedKey.toString("hex"); var key = derivedKey.toString("hex");
pbkdf2Cache.push({hash: key, password: hashedPassword, salt: list[_i].salt, addDate: new Date()}); pbkdf2Cache.push({
hash: key,
password: hashedPassword,
salt: list[_i].salt,
addDate: new Date()
});
cb(key); cb(key);
} }
}); });
@ -4392,7 +4452,11 @@ if (!cluster.isPrimary) {
}); });
} }
if (usernameMatch.length == 0) { if (usernameMatch.length == 0) {
usernameMatch.push({name: username, pass: "FAKEPASS", salt: "FAKESALT"}); // Fake credentials usernameMatch.push({
name: username,
pass: "FAKEPASS",
salt: "FAKESALT"
}); // Fake credentials
} }
checkIfPasswordMatches(usernameMatch, password, function (authorized) { checkIfPasswordMatches(usernameMatch, password, function (authorized) {
try { try {
@ -4423,7 +4487,9 @@ if (!cluster.isPrimary) {
} }
} }
serverconsole.reqmessage("Client is logged in as \"" + username + "\""); serverconsole.reqmessage("Client is logged in as \"" + username + "\"");
redirectTrailingSlashes(function () {
modExecute(mods, vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, "", callServerError, getCustomHeaders, origHref, redirect, parsePostData)); modExecute(mods, vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, "", callServerError, getCustomHeaders, origHref, redirect, parsePostData));
});
} }
} catch (err) { } catch (err) {
callServerError(500, undefined, generateErrorStack(err)); callServerError(500, undefined, generateErrorStack(err));
@ -4470,7 +4536,9 @@ if (!cluster.isPrimary) {
process.send("\x12AUTHQ" + reqip); process.send("\x12AUTHQ" + reqip);
} }
} else { } else {
redirectTrailingSlashes(function () {
modExecute(mods, vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, "", callServerError, getCustomHeaders, origHref, redirect, parsePostData)); modExecute(mods, vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, "", callServerError, getCustomHeaders, origHref, redirect, parsePostData));
});
} }
} }
} catch (err) { } catch (err) {
@ -4524,7 +4592,7 @@ if (!cluster.isPrimary) {
} else { } else {
try { try {
process.send("\x12ERRLIST" + attmts + err.code); process.send("\x12ERRLIST" + attmts + err.code);
} catch(ex) { } catch (err) {
// Probably main process exited // Probably main process exited
} }
} }
@ -4534,7 +4602,7 @@ if (!cluster.isPrimary) {
} else { } else {
try { try {
if (cluster.isPrimary !== undefined) process.send("\x12ERRCRASH" + err.code); if (cluster.isPrimary !== undefined) process.send("\x12ERRCRASH" + err.code);
} catch(ex) { } catch (err) {
// Probably main process exited // Probably main process exited
} }
setTimeout(function () { setTimeout(function () {
@ -4739,8 +4807,12 @@ function start(init) {
if (configJSON.enableHTTP2 && !secure) serverconsole.locwarnmessage("HTTP/2 without HTTPS may not work in web browsers. Web browsers only support HTTP/2 with HTTPS!"); if (configJSON.enableHTTP2 && !secure) serverconsole.locwarnmessage("HTTP/2 without HTTPS may not work in web browsers. Web browsers only support HTTP/2 with HTTPS!");
if (process.isBun) { if (process.isBun) {
serverconsole.locwarnmessage("Bun support is experimental. Some features of SVR.JS, SVR.JS mods and SVR.JS server-side JavaScript may not work as expected."); serverconsole.locwarnmessage("Bun support is experimental. Some features of SVR.JS, SVR.JS mods and SVR.JS server-side JavaScript may not work as expected.");
if(users.some(function (entry) {return entry.pbkdf2;})) serverconsole.locwarnmessage("PBKDF2 password hashing function in Bun blocks the event loop, which may result in denial of service."); if (users.some(function (entry) {
if(users.some(function (entry) {return entry.scrypt;})) serverconsole.locwarnmessage("scrypt password hashing function in Bun blocks the event loop, which may result in denial of service."); return entry.pbkdf2;
})) serverconsole.locwarnmessage("PBKDF2 password hashing function in Bun blocks the event loop, which may result in denial of service.");
if (users.some(function (entry) {
return entry.scrypt;
})) serverconsole.locwarnmessage("scrypt password hashing function in Bun blocks the event loop, which may result in denial of service.");
} }
if (cluster.isPrimary === undefined) serverconsole.locwarnmessage("You're running SVR.JS on single thread. Reliability may suffer, as the server is stopped after crash."); if (cluster.isPrimary === undefined) serverconsole.locwarnmessage("You're running SVR.JS on single thread. Reliability may suffer, as the server is stopped after crash.");
if (crypto.__disabled__ !== undefined) serverconsole.locwarnmessage("Your Node.JS version doesn't have crypto support! The 'crypto' module is essential for providing cryptographic functionality in Node.JS. Without crypto support, certain security features may be unavailable, and some functionality may not work as expected. It's recommended to use a Node.JS version that includes crypto support to ensure the security and proper functioning of your server."); if (crypto.__disabled__ !== undefined) serverconsole.locwarnmessage("Your Node.JS version doesn't have crypto support! The 'crypto' module is essential for providing cryptographic functionality in Node.JS. Without crypto support, certain security features may be unavailable, and some functionality may not work as expected. It's recommended to use a Node.JS version that includes crypto support to ensure the security and proper functioning of your server.");
@ -4748,9 +4820,9 @@ function start(init) {
if (process.getuid && process.getuid() == 0) serverconsole.locwarnmessage("You're running SVR.JS as root. It's recommended to run SVR.JS as an non-root user. Running SVR.JS as root may increase the risks of OS command execution vulnerabilities."); if (process.getuid && process.getuid() == 0) serverconsole.locwarnmessage("You're running SVR.JS as root. It's recommended to run SVR.JS as an non-root user. Running SVR.JS as root may increase the risks of OS command execution vulnerabilities.");
if (secure && process.versions && process.versions.openssl && process.versions.openssl.substr(0, 2) == "1.") { if (secure && process.versions && process.versions.openssl && process.versions.openssl.substr(0, 2) == "1.") {
if (new Date() > new Date("11 September 2023")) { if (new Date() > new Date("11 September 2023")) {
serverconsole.locwarnmessage("OpenSSL 1.x is no longer recieving security updates after 11th September 2023. Your HTTPS communication might be vulnerable. It is recommended to update to a newer version of Node.JS that includes OpenSSL 3.0 or higher to ensure the security of your server and data."); serverconsole.locwarnmessage("OpenSSL 1.x is no longer receiving security updates after 11th September 2023. Your HTTPS communication might be vulnerable. It is recommended to update to a newer version of Node.JS that includes OpenSSL 3.0 or higher to ensure the security of your server and data.");
} else { } else {
serverconsole.locwarnmessage("OpenSSL 1.x will no longer recieve security updates after 11th September 2023. Your HTTPS communication might be vulnerable in future. It is recommended to update to a newer version of Node.JS that includes OpenSSL 3.0 or higher to ensure the security of your server and data."); serverconsole.locwarnmessage("OpenSSL 1.x will no longer receive security updates after 11th September 2023. Your HTTPS communication might be vulnerable in future. It is recommended to update to a newer version of Node.JS that includes OpenSSL 3.0 or higher to ensure the security of your server and data.");
} }
} }
if (secure && configJSON.enableOCSPStapling && ocsp._errored) serverconsole.locwarnmessage("Can't load OCSP module. OCSP stapling will be disabled. OCSP stapling is a security feature that improves the performance and security of HTTPS connections by caching the certificate status response. If you require this feature, consider updating your Node.JS version or checking for any issues with the 'ocsp' module."); if (secure && configJSON.enableOCSPStapling && ocsp._errored) serverconsole.locwarnmessage("Can't load OCSP module. OCSP stapling will be disabled. OCSP stapling is a security feature that improves the performance and security of HTTPS connections by caching the certificate status response. If you require this feature, consider updating your Node.JS version or checking for any issues with the 'ocsp' module.");
@ -4989,6 +5061,7 @@ function start(init) {
setInterval(function () { setInterval(function () {
var allClusters = Object.keys(cluster.workers); var allClusters = Object.keys(cluster.workers);
var goodWorkers = []; var goodWorkers = [];
function checkWorker(callback, _id) { function checkWorker(callback, _id) {
if (typeof _id === "undefined") _id = 0; if (typeof _id === "undefined") _id = 0;
if (_id >= allClusters.length) { if (_id >= allClusters.length) {
@ -5378,6 +5451,7 @@ function start(init) {
minClusters = Math.ceil(cpus * 0.625); minClusters = Math.ceil(cpus * 0.625);
if (minClusters < 2) minClusters = 2; if (minClusters < 2) minClusters = 2;
var goodWorkers = []; var goodWorkers = [];
function checkWorker(callback, _id) { function checkWorker(callback, _id) {
if (typeof _id === "undefined") _id = 0; if (typeof _id === "undefined") _id = 0;
if (_id >= allClusters.length) { if (_id >= allClusters.length) {
@ -5485,8 +5559,10 @@ function saveConfig() {
if (configJSONobj.errorPages === undefined) configJSONobj.errorPages = []; if (configJSONobj.errorPages === undefined) configJSONobj.errorPages = [];
if (configJSONobj.useWebRootServerSideScript === undefined) configJSONobj.useWebRootServerSideScript = true; if (configJSONobj.useWebRootServerSideScript === undefined) configJSONobj.useWebRootServerSideScript = true;
if (configJSONobj.exposeModsInErrorPages === undefined) configJSONobj.exposeModsInErrorPages = true; if (configJSONobj.exposeModsInErrorPages === undefined) configJSONobj.exposeModsInErrorPages = true;
if (configJSONobj.disableTrailingSlashRedirects === undefined) configJSONobj.disableTrailingSlashRedirects = false;
if (configJSONobj.environmentVariables === undefined) configJSONobj.environmentVariables = {};
var configString = JSON.stringify(configJSONobj, null, 2); var configString = JSON.stringify(configJSONobj, null, 2) + "\n";
fs.writeFileSync(__dirname + "/config.json", configString); fs.writeFileSync(__dirname + "/config.json", configString);
break; break;
} catch (err) { } catch (err) {

View file

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>SVR.JS 3.11.0 Tests</title> <title>SVR.JS 3.12.0 Tests</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<style> <style>
@ -12,11 +12,11 @@
</style> </style>
</head> </head>
<body> <body>
<h1>SVR.JS 3.11.0 Tests</h1> <h1>SVR.JS 3.12.0 Tests</h1>
<h2>Directory</h2> <h2>Directory (without trailing slash)</h2>
<iframe src="/testdir" width="50%" height="300px"></iframe> <iframe src="/testdir" width="50%" height="300px"></iframe>
<h2>Directory (with query)</h2> <h2>Directory (with query)</h2>
<iframe src="/testdir?query=value" width="50%" height="300px"></iframe> <iframe src="/testdir/?query=value" width="50%" height="300px"></iframe>
<h2>Directory (personalized)</h2> <h2>Directory (personalized)</h2>
<iframe src="/testdir/.personalized" width="50%" height="300px"></iframe> <iframe src="/testdir/.personalized" width="50%" height="300px"></iframe>
<h2>404 Error</h2> <h2>404 Error</h2>