= 4 || ex.name == "SyntaxError") throw ex;
+ } catch (err) {
+ if (i >= 4 || err.name == "SyntaxError") throw err;
+ // Wait for a short time before retrying
var now = Date.now();
while (Date.now() - now < 2);
- //Try reloading mod
+ // Try reloading mod
}
}
+
+ // Add the loaded server side script to the mods list
mods.push(amod);
- } catch (ex) {
- if (cluster.isPrimary || cluster.isPrimary === undefined) {
- serverconsole.locwarnmessage("There was a problem while loading server side JavaScript.");
- serverconsole.locwarnmessage("Stack:");
- serverconsole.locwarnmessage(generateErrorStack(ex));
- }
+ } catch (err) {
+ SSJSError = err;
}
}
}
+
+// SHA256 function
function sha256(s) {
if (crypto.__disabled__ === undefined) {
var hash = crypto.createHash("SHA256");
hash.update(s);
return hash.digest("hex");
} else {
-
var chrsz = 8;
var hexcase = 0;
@@ -1525,6 +1860,7 @@ function sha256(s) {
}
}
+// Function to get URL path for use in forbidden path adding.
function getInitializePath(to) {
var cwd = process.cwd();
if (os.platform() == "win32") {
@@ -1541,30 +1877,37 @@ function getInitializePath(to) {
}
}
-function checkIfForbiddenPath(decodedHref, match) {
- var mo = forbiddenPaths[match];
- if (!mo) return false;
- if (typeof mo == "string") return decodedHref == mo || (os.platform() == "win32" && decodedHref.toLowerCase() == mo.toLowerCase());
- if (typeof mo == "object") {
- for (var i = 0; i < mo.length; i++) {
- if (decodedHref == mo[i] || (os.platform() == "win32" && decodedHref.toLowerCase() == mo[i].toLowerCase())) return true;
- }
+// Function to check if URL path name is a forbidden path.
+function isForbiddenPath(decodedHref, match) {
+ var forbiddenPath = forbiddenPaths[match];
+ if (!forbiddenPath) return false;
+ if (typeof forbiddenPath === "string") {
+ return decodedHref === forbiddenPath || (os.platform() === "win32" && decodedHref.toLowerCase() === forbiddenPath.toLowerCase());
+ }
+ if (typeof forbiddenPath === "object") {
+ return forbiddenPath.some(function (forbiddenPathSingle) {
+ return (decodedHref === forbiddenPathSingle || (os.platform() === "win32" && decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase()));
+ });
}
return false;
}
-function checkIfIndexOfForbiddenPath(decodedHref, match) {
- var mo = forbiddenPaths[match];
- if (!mo) return false;
- if (typeof mo == "string") return decodedHref == mo || decodedHref.indexOf(mo + "/") == 0 || (os.platform() == "win32" && (decodedHref.toLowerCase() == mo.toLowerCase() || decodedHref.toLowerCase().indexOf(mo.toLowerCase() + "/") == 0));
- if (typeof mo == "object") {
- for (var i = 0; i < mo.length; i++) {
- if (decodedHref == mo[i] || decodedHref.indexOf(mo[i] + "/") == 0 || (os.platform() == "win32" && (decodedHref.toLowerCase() == mo[i].toLowerCase() || decodedHref.toLowerCase().indexOf(mo[i].toLowerCase() + "/") == 0))) return true;
- }
+// Function to check if URL path name is index of one of defined forbidden paths.
+function isIndexOfForbiddenPath(decodedHref, match) {
+ var forbiddenPath = forbiddenPaths[match];
+ if (!forbiddenPath) return false;
+ if (typeof forbiddenPath === "string") {
+ return decodedHref === forbiddenPath || decodedHref.indexOf(forbiddenPath + "/") === 0 || (os.platform() === "win32" && (decodedHref.toLowerCase() === forbiddenPath.toLowerCase() || decodedHref.toLowerCase().indexOf(forbiddenPath.toLowerCase() + "/") === 0));
+ }
+ if (typeof forbiddenPath === "object") {
+ return forbiddenPath.some(function (forbiddenPathSingle) {
+ return (decodedHref === forbiddenPathSingle || decodedHref.indexOf(forbiddenPathSingle + "/") === 0 || (os.platform() === "win32" && (decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase() || decodedHref.toLowerCase().indexOf(forbiddenPathSingle.toLowerCase() + "/") === 0)));
+ });
}
return false;
}
+// Set up forbidden paths
var forbiddenPaths = {};
forbiddenPaths.config = getInitializePath("./config.json");
@@ -1572,119 +1915,201 @@ forbiddenPaths.certificates = [];
if (secure) {
forbiddenPaths.certificates.push(getInitializePath(configJSON.cert));
forbiddenPaths.certificates.push(getInitializePath(configJSON.key));
- for (var i = 0; i < Object.keys(sni).length; i++) {
- forbiddenPaths.certificates.push(getInitializePath(sni[Object.keys(sni)[i]].cert));
- forbiddenPaths.certificates.push(getInitializePath(sni[Object.keys(sni)[i]].key));
- }
+ Object.keys(sni).forEach(function (sniHostName) {
+ forbiddenPaths.certificates.push(getInitializePath(sni[sniHostName].cert));
+ forbiddenPaths.certificates.push(getInitializePath(sni[sniHostName].key));
+ });
}
forbiddenPaths.svrjs = getInitializePath("./" + ((__dirname[__dirname.length - 1] != "/") ? __filename.replace(__dirname + "/", "") : __filename.replace(__dirname, "")));
forbiddenPaths.serverSideScripts = [];
-forbiddenPaths.serverSideScripts.push("/serverSideScript.js");
+if (useWebRootServerSideScript) {
+ forbiddenPaths.serverSideScripts.push("/serverSideScript.js");
+} else {
+ forbiddenPaths.serverSideScripts.push(getInitializePath("./serverSideScript.js"));
+}
forbiddenPaths.serverSideScriptDirectories = [];
forbiddenPaths.serverSideScriptDirectories.push(getInitializePath("./node_modules"));
forbiddenPaths.serverSideScriptDirectories.push(getInitializePath("./mods"));
forbiddenPaths.temp = getInitializePath("./temp");
forbiddenPaths.log = getInitializePath("./log");
-//Create server
+// HTTP error descriptions
+var serverHTTPErrorDescs = {
+ 200: "The request succeeded! :)",
+ 201: "A new resource has been created.",
+ 202: "The request has been accepted for processing, but the processing has not been completed.",
+ 400: "The request you made is invalid.",
+ 401: "You need to authenticate yourself in order to access the requested file.",
+ 402: "You need to pay in order to access the requested file.",
+ 403: "You don't have access to the requested file.",
+ 404: "The requested file doesn't exist. If you have typed the URL manually, then please check the spelling.",
+ 405: "Method used to access the requested file isn't allowed.",
+ 406: "The request is capable of generating only unacceptable content.",
+ 407: "You need to authenticate yourself in order to use the proxy.",
+ 408: "You have timed out.",
+ 409: "The request you sent conflicts with the current state of the server.",
+ 410: "The requested file is permanently deleted.",
+ 411: "Content-Length property is required.",
+ 412: "The server doesn't meet the preconditions you put in the request.",
+ 413: "The request you sent is too large.",
+ 414: "The URL you sent is too long.",
+ 415: "The media type of request you sent isn't supported by the server.",
+ 416: "The requested content range (Content-Range header) you sent is unsatisfiable.",
+ 417: "The expectation specified in the Expect property couldn't be satisfied.",
+ 418: "The server (teapot) can't brew any coffee! ;)",
+ 421: "The request you made isn't intended for this server.",
+ 422: "The server couldn't process content sent by you.",
+ 423: "The requested file is locked.",
+ 424: "The request depends on another failed request.",
+ 425: "The server is unwilling to risk processing a request that might be replayed.",
+ 426: "You need to upgrade the protocols you use to request a file.",
+ 428: "The request you sent needs to be conditional, but it isn't.",
+ 429: "You sent too many requests to the server.",
+ 431: "The request you sent contains headers that are too large.",
+ 451: "The requested file isn't accessible for legal reasons.",
+ 497: "You sent a non-TLS request to the HTTPS server.",
+ 500: "The server had an unexpected error. Below, the error stack is shown: {stack}
You may need to contact the server administrator at {contact}.",
+ 501: "The request requires the use of a function, which isn't currently implemented by the server.",
+ 502: "The server had an error while it was acting as a gateway.
You may need to contact the server administrator at {contact}.",
+ 503: "The service provided by the server is currently unavailable, possibly due to maintenance downtime or capacity problems. Please try again later.
You may need to contact the server administrator at {contact}.",
+ 504: "The server couldn't get a response in time while it was acting as a gateway.
You may need to contact the server administrator at {contact}.",
+ 505: "The server doesn't support the HTTP version used in the request.",
+ 506: "The Variant header is configured to be engaged in content negotiation.
You may need to contact the server administrator at {contact}.",
+ 507: "The server ran out of disk space necessary to complete the request.",
+ 508: "The server detected an infinite loop while processing the request.",
+ 509: "The server has its bandwidth limit exceeded.
You may need to contact the server administrator at {contact}.",
+ 510: "The server requires an extended HTTP request. The request you made isn't an extended HTTP request.",
+ 511: "You need to authenticate yourself in order to get network access.",
+ 598: "The server couldn't get a response in time while it was acting as a proxy.",
+ 599: "The server couldn't connect in time while it was acting as a proxy."
+};
+
+// Server error descriptions
+var serverErrorDescs = {
+ "EADDRINUSE": "Address is already in use by another process.",
+ "EADDRNOTAVAIL": "Address is not available on this machine.",
+ "EACCES": "Permission denied. You may not have sufficient privileges to access the requested address.",
+ "EAFNOSUPPORT": "Address family not supported. The address family (IPv4 or IPv6) of the requested address is not supported.",
+ "EALREADY": "Operation already in progress. The server is already in the process of establishing a connection on the requested address.",
+ "ECONNABORTED": "Connection aborted. The connection to the server was terminated abruptly.",
+ "ECONNREFUSED": "Connection refused. The server refused the connection attempt.",
+ "ECONNRESET": "Connection reset by peer. The connection to the server was reset by the remote host.",
+ "EDESTADDRREQ": "Destination address required. The destination address must be specified.",
+ "EINVAL": "Invalid argument (invalid IP address?).",
+ "ENETDOWN": "Network is down. The network interface used for the connection is not available.",
+ "ENETUNREACH": "Network is unreachable. The network destination is not reachable from this host.",
+ "ENOBUFS": "No buffer space available. Insufficient buffer space is available for the server to process the request.",
+ "ENOTFOUND": "Domain name doesn't exist (invalid IP address?).",
+ "ENOTSOCK": "Not a socket. The file descriptor provided is not a valid socket.",
+ "EPROTO": "Protocol error. An unspecified protocol error occurred.",
+ "EPROTONOSUPPORT": "Protocol not supported. The requested network protocol is not supported.",
+ "ETIMEDOUT": "Connection timed out. The server did not respond within the specified timeout period.",
+ "UNKNOWN": "There was an unknown error with the server."
+};
+
+// Create server instances
if (!cluster.isPrimary) {
var reqcounter = 0;
+ var malformedcounter = 0;
+ var err4xxcounter = 0;
+ var err5xxcounter = 0;
+ var reqcounterKillReq = 0;
var server = {};
var server2 = {};
try {
server2 = http.createServer({
requireHostHeader: false
});
- } catch (ex) {
+ } catch (err) {
server2 = http.createServer();
}
+ server2.on("request", function (req, res) {
+ reqhandler(req, res, false);
+ });
+ server2.on("checkExpectation", reqhandler);
+ server2.on("clientError", function (err, socket) {
+ reqerrhandler(err, socket, false);
+ });
if (!disableToHTTPSRedirect) {
- server2.on("request", redirhandler);
server2.on("connect", function (request, socket) {
- var reqIdInt = Math.round(Math.random() * 16777216);
+ var reqIdInt = Math.floor(Math.random() * 16777216);
+ if (reqIdInt == 16777216) reqIdInt = 0;
var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
var serverconsole = {
climessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.climessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.climessage(nmsg);
+ });
return;
}
- console.log("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
+ console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
reqmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.reqmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.reqmessage(nmsg);
+ });
return;
}
- console.log("\x1b[34mSERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
resmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.resmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.resmessage(nmsg);
+ });
return;
}
- console.log("\x1b[32mSERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
errmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.errmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.errmessage(nmsg);
+ });
return;
}
- console.log("\x1b[31mSERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locerrmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locerrmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locerrmessage(nmsg);
+ });
return;
}
- console.log("\x1b[41mSERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
+ console.log("\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
LOG("SERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locwarnmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locwarnmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locwarnmessage(nmsg);
+ });
return;
}
- console.log("\x1b[43mSERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
+ console.log("\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
LOG("SERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locmessage(nmsg);
+ });
return;
}
- console.log("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
+ console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
}
@@ -1696,59 +2121,21 @@ if (!cluster.isPrimary) {
socket.on("error", function () {});
var reqip = socket.remoteAddress;
var reqport = socket.remotePort;
- serverconsole.locmessage("Somebody connected to port " + port + "...");
+ serverconsole.locmessage("Somebody connected to " + (typeof port == "number" ? "port " : "socket ") + port + "...");
+ reqcounter++;
serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " wants to proxy " + request.url + " through this server");
if (request.headers["user-agent"] != undefined) serverconsole.reqmessage("Client uses " + request.headers["user-agent"]);
serverconsole.errmessage("This server will never be a proxy.");
if (!socket.destroyed) socket.end("HTTP/1.1 501 Not Implemented\n\n");
});
- server2.on("clientError", function (err, socket) {
- reqerrhandler(err, socket, false);
- });
- server2.on("checkExpectation", redirhandler);
} else {
- server2.on("request", function (req, res) {
- reqhandler(req, res, false);
- });
- server2.on("checkExpectation", reqhandler);
server2.on("connect", connhandler);
- server2.on("clientError", function (err, socket) {
- reqerrhandler(err, socket, false);
- });
-
}
server2.on("error", function (err) {
- if (err.code == "EADDRINUSE" || err.code == "EADDRNOTAVAIL" || err.code == "EACCES") {
- attmtsRedir--;
- if (cluster.isPrimary === undefined) {
- if (err.code == "EADDRINUSE") {
- serverconsole.locerrmessage("Address in use by another process.");
- } else if (err.code == "EADDRNOTAVAIL") {
- serverconsole.locerrmessage("Address not available.");
- } else if (err.code == "EACCES") {
- serverconsole.locerrmessage("Access denied.");
- }
- serverconsole.locmessage(attmtsRedir + " attempts left.");
- } else {
- process.send("\x12ERRLIST" + attmtsRedir + err.code);
- }
- if (attmtsRedir > 0) {
- server2.close();
- setTimeout(start, 900);
- } else {
- if (cluster.isPrimary !== undefined) process.send("\x12" + err.code);
- process.exit(errors[err.code]);
- }
- } else {
- serverconsole.locerrmessage("There was a problem starting SVR.JS!!!");
- serverconsole.locerrmessage("Stack:");
- serverconsole.locerrmessage(generateErrorStack(err));
- if (cluster.isPrimary !== undefined) process.send("\x12CRASH");
- process.exit(err.code ? errors[err.code] : 1);
- }
+ serverErrorHandler(err, true);
});
-
- server2.once("listening", function () {
+ server2.on("listening", function () {
+ attmtsRedir = 5;
listeningMessage();
});
@@ -1758,12 +2145,21 @@ if (!cluster.isPrimary) {
allowHTTP1: true,
requireHostHeader: false,
key: key,
- cert: cert
+ cert: cert,
+ requestCert: configJSON.useClientCertificate,
+ rejectUnauthorized: configJSON.rejectUnauthorizedClientCertificates,
+ ciphers: configJSON.cipherSuite,
+ ecdhCurve: configJSON.ecdhCurve,
+ minVersion: configJSON.tlsMinVersion,
+ maxVersion: configJSON.tlsMaxVersion,
+ sigalgs: configJSON.signatureAlgorithms,
+ settings: configJSON.http2Settings
});
} else {
server = http2.createServer({
allowHTTP1: true,
- requireHostHeader: false
+ requireHostHeader: false,
+ settings: configJSON.http2Settings
});
}
} else {
@@ -1771,24 +2167,42 @@ if (!cluster.isPrimary) {
server = https.createServer({
key: key,
cert: cert,
- requireHostHeader: false
+ requireHostHeader: false,
+ requestCert: configJSON.useClientCertificate,
+ rejectUnauthorized: configJSON.rejectUnauthorizedClientCertificates,
+ ciphers: configJSON.cipherSuite,
+ ecdhCurve: configJSON.ecdhCurve,
+ minVersion: configJSON.tlsMinVersion,
+ maxVersion: configJSON.tlsMaxVersion,
+ sigalgs: configJSON.signatureAlgorithms
});
} else {
try {
server = http.createServer({
requireHostHeader: false
});
- } catch (ex) {
+ } catch (err) {
server = http.createServer();
}
}
}
if (secure) {
- for (var i = 0; i < sniCredentials.length; i++) {
- server.addContext(sniCredentials[i].name, {
- cert: sniCredentials[i].cert,
- key: sniCredentials[i].key
+ try {
+ sniCredentials.forEach(function (sniCredentialsSingle) {
+ server.addContext(sniCredentialsSingle.name, {
+ cert: sniCredentialsSingle.cert,
+ key: sniCredentialsSingle.key
+ });
+ try {
+ var snMatches = sniCredentialsSingle.name.match(/^([^:[]*|\[[^]]*\]?)((?::.*)?)$/);
+ if (!snMatches[1][0].match(/^\.+$/)) snMatches[1][0] = snMatches[1][0].replace(/\.+$/, "");
+ server._contexts[server._contexts.length - 1][0] = new RegExp("^" + snMatches[1].replace(/([.^$+?\-\\[\]{}])/g, "\\$1").replace(/\*/g, "[^.:]*") + ((snMatches[1][0] == "[" || snMatches[1].match(/^(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/)) ? "" : "\.?") + snMatches[2].replace(/([.^$+?\-\\[\]{}])/g, "\\$1").replace(/\*/g, "[^.]*") + "$", "i");
+ } catch (ex) {
+ // Can't replace regex, ignoring...
+ }
});
+ } catch (err) {
+ // SNI error
}
}
server.on("request", reqhandler);
@@ -1828,16 +2242,16 @@ if (!cluster.isPrimary) {
try {
sock._parent.destroy(a, b, c);
sock.destroyed = sock._parent.destroyed;
- } catch (ex) {
- //Socket is probably already destroyed.
+ } catch (err) {
+ // Socket is probably already destroyed.
}
};
} else {
sock._parent.destroy = sock._parent.reallyDestroy;
try {
if (sock._parent.toDestroy) sock._parent.destroy();
- } catch (ex) {
- //Socket is probably already destroyed.
+ } catch (err) {
+ // Socket is probably already destroyed.
}
}
});
@@ -1864,407 +2278,13 @@ if (!cluster.isPrimary) {
}
}
- //Patches from Node.JS v18.0.0
+ // Patches from Node.JS v18.0.0
if (server.requestTimeout !== undefined && server.requestTimeout === 0) server.requestTimeout = 300000;
if (server2.requestTimeout !== undefined && server2.requestTimeout === 0) server2.requestTimeout = 300000;
- function redirhandler(req, res) {
- var reqIdInt = Math.round(Math.random() * 16777216);
- var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
- var serverconsole = {
- climessage: function (msg) {
- if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.climessage(nmsg[i]);
- }
- return;
- }
- console.log("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
- LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
- return;
- },
- reqmessage: function (msg) {
- if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.reqmessage(nmsg[i]);
- }
- return;
- }
- console.log("\x1b[34mSERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
- LOG("SERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg);
- return;
- },
- resmessage: function (msg) {
- if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.resmessage(nmsg[i]);
- }
- return;
- }
- console.log("\x1b[32mSERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
- LOG("SERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg);
- return;
- },
- errmessage: function (msg) {
- if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.errmessage(nmsg[i]);
- }
- return;
- }
- console.log("\x1b[31mSERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
- LOG("SERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
- return;
- },
- locerrmessage: function (msg) {
- if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locerrmessage(nmsg[i]);
- }
- return;
- }
- console.log("\x1b[41mSERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
- LOG("SERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
- return;
- },
- locwarnmessage: function (msg) {
- if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locwarnmessage(nmsg[i]);
- }
- return;
- }
- console.log("\x1b[43mSERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
- LOG("SERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg);
- return;
- },
- locmessage: function (msg) {
- if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locmessage(nmsg[i]);
- }
- return;
- }
- console.log("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
- LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
- return;
- }
- };
-
- function getCustomHeaders() {
- var ph = JSON.parse(JSON.stringify(customHeaders));
- var phk = Object.keys(ph);
- for (var i = 0; i < phk.length; i++) {
- if (typeof ph[phk[i]] == "string") ph[phk[i]] = ph[phk[i]].replace(/\{path\}/g, req.url);
- }
- return ph;
- }
- if (req.headers["x-svr-js-from-main-thread"] == "true") {
- res.writeHead(204, "No Content", getCustomHeaders());
- res.end();
- return;
- }
-
- req.url = fixNodeMojibakeURL(req.url);
-
- res.writeHeadNative = res.writeHead;
- res.writeHead = function (a, b, c) {
- if (parseInt(a) >= 400 && parseInt(a) <= 599) {
- serverconsole.errmessage("Server responded with " + a.toString() + " code.");
- } else {
- serverconsole.resmessage("Server responded with " + a.toString() + " code.");
- }
- res.writeHeadNative(a, b, c);
- };
- var finished = false;
- res.on("finish", function () {
- if (!finished) {
- finished = true;
- serverconsole.locmessage("Client disconnected.");
- }
- });
- res.on("close", function () {
- if (!finished) {
- finished = true;
- serverconsole.locmessage("Client disconnected.");
- }
- });
-
- serverconsole.locmessage("Somebody connected to port " + port + "...");
-
- if (req.socket == null) {
- serverconsole.errmessage("Client socket is null!!!");
- return;
- }
-
- var isProxy = false;
- if (req.url.indexOf("/") != 0 && req.url != "*") isProxy = true;
-
- // Header and footer placeholders
- var head = "";
- var foot = "";
- var fd = "";
-
- function responseEnd(d) {
- if (d === undefined) d = fd;
- res.write(head + d + foot);
- res.end();
- }
-
- //Error descriptions
- var serverErrorDescs = {
- 400: "The request you made is invalid.",
- 417: "Expectation in Expect property couldn't be satisfied.",
- 500: "The server had an unexpected error. Below, the error stack is shown:
{stack}
Please contact with developer/administrator at {contact}.",
- 501: "The request requires use of a function, which isn't currently implemented by the server."
- };
-
- //Server error calling method
- function callServerError(errorCode, extName, stack, ch) {
- var errorFile = errorCode.toString() + ".html";
- var errorFile2 = "." + errorCode.toString();
- if (fs.existsSync(errorFile2)) errorFile = errorFile2;
- if (errorCode == 404 && fs.existsSync(page404)) errorFile = page404;
- if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
- if (stack === undefined) stack = generateErrorStack(new Error("Unknown error"));
- if (errorCode == 500 || errorCode == 502) {
- serverconsole.errmessage("There was an error while processing the request!");
- serverconsole.errmessage("Stack:");
- serverconsole.errmessage(stack);
- }
- if (stackHidden) stack = "[error stack hidden]";
- if (serverErrorDescs[errorCode] === undefined) {
- callServerError(501, extName, stack);
- } else {
- var cheaders = getCustomHeaders();
- if (ch) {
- var chon = Object.keys(cheaders);
- var chn = Object.keys(ch);
- for (var i = 0; i < chn.length; i++) {
- var nhn = chn[i];
- for (var j = 0; j < chon.length; j++) {
- if (chon[j].toLowerCase() == chn[i].toLowerCase()) {
- nhn = chon[j];
- break;
- }
- }
- if (ch[chn[i]]) cheaders[nhn] = ch[chn[i]];
- }
- }
- cheaders["Content-Type"] = "text/html; charset=utf-8";
- if (errorCode == 405 && !cheaders["Allow"]) cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
- fs.readFile(errorFile, function (err, data) {
- try {
- if (err) throw err;
- res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
- fd += data.toString().replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{path}/g, req.url.replace(/&/g, "&").replace(//g, ">")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">") + ((req.headers.host == undefined || isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">"))).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]"));
- responseEnd();
- } catch (ex) {
- var additionalError = 500;
- if (ex.code == "ENOENT") {
- additionalError = 404;
- } else if (ex.code == "ENOTDIR") {
- additionalError = 404;
- } else if (ex.code == "EACCES") {
- additionalError = 403;
- } else if (ex.code == "ENAMETOOLONG") {
- additionalError = 414;
- } else if (ex.code == "EMFILE") {
- additionalError = 503;
- } else if (ex.code == "ELOOP") {
- additionalError = 508;
- }
- res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
- res.write(("
{errorMessage}{errorMessage}
{errorDesc}
" + ((additionalError == 404) ? "" : "Additionally, a {additionalError} error occurred while loading an error page.
") + "{server}
").replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{path}/g, req.url.replace(/&/g, "&").replace(//g, ">")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">") + ((req.headers.host == undefined || isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">"))).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")).replace(/{additionalError}/g, additionalError.toString()));
- res.end();
- }
- });
-
- }
- }
-
- var reqip = req.socket.remoteAddress;
- var reqport = req.socket.remotePort;
- var oldip = "";
- var oldport = "";
- var isForwardedValid = true;
- if(enableIPSpoofing) {
- if (req.headers["x-forwarded-for"] != undefined) {
- var preparedReqIP = req.headers["x-forwarded-for"].split(",")[0].replace(/ /g, "");
- var preparedReqIPvalid = net.isIP(preparedReqIP);
- if(preparedReqIPvalid) {
- if (preparedReqIPvalid == 4 && req.socket.remoteAddress && req.socket.remoteAddress.indexOf(":") > -1) preparedReqIP = "::ffff:" + preparedReqIP;
- reqip = preparedReqIP;
- reqport = null;
- try {
- oldport = req.socket.remotePort;
- oldip = req.socket.remoteAddress;
- req.socket.realRemotePort = reqport;
- req.socket.realRemoteAddress = reqip;
- req.socket.originalRemotePort = oldport;
- req.socket.originalRemoteAddress = oldip;
- res.socket.realRemotePort = reqport;
- res.socket.realRemoteAddress = reqip;
- res.socket.originalRemotePort = oldport;
- res.socket.originalRemoteAddress = oldip;
- } catch (err) {
- // Address setting failed
- }
- } else {
- isForwardedValid = false;
- }
- }
- }
-
- if (!isProxy) serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " wants " + (req.method == "GET" ? "content in " : (req.method == "POST" ? "to post content in " : (req.method == "PUT" ? "to add content in " : (req.method == "DELETE" ? "to delete content in " : (req.method == "PATCH" ? "to patch content in " : "to access content using " + req.method + " method in "))))) + (req.headers.host == undefined ? "" : req.headers.host) + req.url);
- else serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " wants " + (req.method == "GET" ? "content in " : (req.method == "POST" ? "to post content in " : (req.method == "PUT" ? "to add content in " : (req.method == "DELETE" ? "to delete content in " : (req.method == "PATCH" ? "to patch content in " : "to access content using " + req.method + " method in "))))) + req.url);
- if (req.headers["user-agent"] != undefined) serverconsole.reqmessage("Client uses " + req.headers["user-agent"]);
-
- try {
- head = fs.existsSync("./.head") ? fs.readFileSync("./.head").toString() : (fs.existsSync("./head.html") ? fs.readFileSync("./head.html").toString() : ""); // header
- foot = fs.existsSync("./.foot") ? fs.readFileSync("./.foot").toString() : (fs.existsSync("./foot.html") ? fs.readFileSync("./foot.html").toString() : ""); // footer
-
- if (req.headers["expect"] && req.headers["expect"] != "100-continue") {
- serverconsole.errmessage("Expectation not satified!");
- callServerError(417);
- return;
- }
- var hostx = req.headers.host;
- if (hostx === undefined || !isForwardedValid) {
- serverconsole.errmessage("Bad request!");
- callServerError(400);
- return;
- }
-
- if (req.method == "CONNECT") {
- callServerError(501);
- serverconsole.errmessage("CONNECT requests aren't supported. Your JS runtime probably doesn't support 'connect' handler for HTTP library.");
- return;
- }
-
- if (isProxy) {
- callServerError(501);
- serverconsole.errmessage("This server will never be a proxy.");
- return;
- }
-
- function urlParse(uri) {
- if (typeof URL != "undefined" && url.Url) {
- try {
- var uobject = new URL(uri, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
- var nuobject = new url.Url();
- if (uri.indexOf("/") != -1) nuobject.slashes = true;
- if (uobject.protocol != "") nuobject.protocol = uobject.protocol;
- if (uobject.username != "" && uobject.password != "") nuobject.auth = uobject.username + ":" + uobject.password;
- if (uobject.host != "") nuobject.host = uobject.host;
- if (uobject.hostname != "") nuobject.hostname = uobject.hostname;
- if (uobject.port != "") nuobject.port = uobject.port;
- if (uobject.pathname != "") nuobject.pathname = uobject.pathname;
- if (uobject.search != "") nuobject.search = uobject.search;
- if (uobject.hash != "") nuobject.hash = uobject.hash;
- if (uobject.href != "") nuobject.href = uobject.href;
- if (uri.indexOf("/") != 0) {
- if (nuobject.pathname) {
- nuobject.pathname = nuobject.pathname.substr(1);
- nuobject.href = nuobject.pathname + (nuobject.search ? nuobject.search : "");
- }
- }
- if (nuobject.pathname) {
- nuobject.path = nuobject.pathname + (nuobject.search ? nuobject.search : "");
- }
- //if(nuobject.path != "" && uobject.password != "") nuobject.path = nuobject.pathname + nuobject.href;
- nuobject.query = {};
- uobject.searchParams.forEach(function (value, key) {
- nuobject.query[key] = value;
- });
- return nuobject;
- } catch (ex) {
- return url.parse(uri, true);
- }
- } else {
- return url.parse(uri, true);
- }
- }
- var urlp = urlParse("http://" + hostx);
- try {
- if (urlp.path.indexOf("//") == 0) {
- urlp = urlParse("http:" + url.path);
- }
- } catch (ex) {
- //URL parse error...
- }
- if (urlp.host == "localhost" || urlp.host == "localhost:" + port.toString() || urlp.host == "127.0.0.1" || urlp.host == "127.0.0.1:" + port.toString() || urlp.host == "::1" || urlp.host == "::1:" + port.toString()) {
- urlp.protocol = "https:";
- if (sport == 443) {
- urlp.host = urlp.hostname;
- } else {
- urlp.host = urlp.hostname + ":" + sport.toString();
- urlp.port = sport.toString();
- }
- } else if (urlp.host == host || urlp.host == host + ":" + port.toString()) {
- urlp.protocol = "https:";
- if (sport == 443) {
- urlp.host = urlp.hostname;
- } else {
- urlp.host = urlp.hostname + ":" + sport.toString();
- urlp.port = sport.toString();
- }
- } else if (urlp.host == pubip || urlp.host == pubip + ":" + pubport.toString()) {
- urlp.protocol = "https:";
- if (spubport == 443) {
- urlp.host = urlp.hostname;
- } else {
- urlp.host = urlp.hostname + ":" + spubport.toString();
- urlp.port = spubport.toString();
- }
- } else if (urlp.hostname == domain || urlp.hostname.indexOf(domain) != -1) {
- urlp.protocol = "https:";
- if (spubport == 443) {
- urlp.host = urlp.hostname;
- } else {
- urlp.host = urlp.hostname + ":" + spubport.toString();
- urlp.port = spubport.toString();
- }
- } else {
- urlp.protocol = "https:";
- }
- urlp.path = null;
- urlp.pathname = null;
- var lloc = url.format(urlp);
- var requestURL = req.url;
- try {
- if (requestURL.split("/")[1].indexOf(".onion") != -1) {
- requestURL = requestURL.split("/");
- requestURL.shift();
- requestURL.shift();
- requestURL.unshift("");
- requestURL = requestURL.join("/");
- }
- } catch (ex) {
- //Leave URL as it is...
- }
- var rheaders = getCustomHeaders();
- rheaders["Location"] = lloc + requestURL;
- res.writeHead(301, "Redirect to HTTPS", rheaders);
- res.end();
- } catch (ex) {
- serverconsole.errmessage("There was an error while processing the request!");
- serverconsole.errmessage("Stack:");
- serverconsole.errmessage(generateErrorStack(ex));
- callServerError(500, undefined, generateErrorStack(ex));
- }
- }
-
function reqerrhandler(err, socket, fromMain) {
if (fromMain === undefined) fromMain = true;
- //Define response object similar to Node.JS native one
+ // Define response object similar to Node.JS native one
var res = {};
res.socket = socket;
res.write = function (x) {
@@ -2280,117 +2300,112 @@ if (!cluster.isPrimary) {
socket.end(x, function () {
try {
socket.destroy();
- } catch (ex) {
- //Socket is probably already destroyed
+ } catch (err) {
+ // Socket is probably already destroyed
}
});
};
res.writeHead = function (code, name, headers) {
+ if (code >= 400 && code <= 499) err4xxcounter++;
+ if (code >= 500 && code <= 599) err5xxcounter++;
var head = ("HTTP/1.1 " + code.toString() + " " + name + "\r\n");
var headers = JSON.parse(JSON.stringify(headers));
headers["Date"] = (new Date()).toGMTString();
headers["Connection"] = "close";
- var headernames = Object.keys(headers);
- for (var i = 0; i < headernames.length; i++) {
- if (headernames[i].toLowerCase() == "set-cookie") {
- for (var j = 0; j < headers[headernames[i]]; j++) {
- if (headernames[i].match(/[^\x09\x20-\x7e\x80-\xff]|.:/) || headers[headernames[i]][j].match(/[^\x09\x20-\x7e\x80-\xff]/)) throw new Error("Invalid header!!! (" + headernames[i] + ")");
- head += (headernames[i] + ": " + headers[headernames[i]][j]);
- }
+ Object.keys(headers).forEach(function (headername) {
+ if (headername.toLowerCase() == "set-cookie") {
+ headers[headername].forEach(function (headerValueS) {
+ if (headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) || headerValueS.match(/[^\x09\x20-\x7e\x80-\xff]/)) throw new Error("Invalid header!!! (" + headername + ")");
+ head += (headername + ": " + headerValueS);
+ });
} else {
- if (headernames[i].match(/[^\x09\x20-\x7e\x80-\xff]|.:/) || headers[headernames[i]].match(/[^\x09\x20-\x7e\x80-\xff]/)) throw new Error("Invalid header!!! (" + headernames[i] + ")");
- head += (headernames[i] + ": " + headers[headernames[i]]);
+ if (headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) || headers[headername].match(/[^\x09\x20-\x7e\x80-\xff]/)) throw new Error("Invalid header!!! (" + headername + ")");
+ head += (headername + ": " + headers[headername]);
}
head += "\r\n";
- }
+ });
head += ("\r\n");
res.write(head);
};
- var reqIdInt = Math.round(Math.random() * 16777216);
+ var reqIdInt = Math.floor(Math.random() * 16777216);
+ if (reqIdInt == 16777216) reqIdInt = 0;
var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
var serverconsole = {
climessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.climessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.climessage(nmsg);
+ });
return;
}
- console.log("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
+ console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
reqmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.reqmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.reqmessage(nmsg);
+ });
return;
}
- console.log("\x1b[34mSERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
resmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.resmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.resmessage(nmsg);
+ });
return;
}
- console.log("\x1b[32mSERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
errmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.errmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.errmessage(nmsg);
+ });
return;
}
- console.log("\x1b[31mSERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locerrmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locerrmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locerrmessage(nmsg);
+ });
return;
}
- console.log("\x1b[41mSERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
+ console.log("\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
LOG("SERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locwarnmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locwarnmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locwarnmessage(nmsg);
+ });
return;
}
- console.log("\x1b[43mSERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
+ console.log("\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
LOG("SERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locmessage(nmsg);
+ });
return;
}
- console.log("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
+ console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
}
@@ -2405,95 +2420,176 @@ if (!cluster.isPrimary) {
var head = "";
var foot = "";
- var fd = "";
-
- function responseEnd(d) {
- if (d === undefined) d = fd;
- res.write(head + d + foot);
+ function responseEnd(body) {
+ // If body is Buffer, then it is converted to String anyway.
+ res.write(head + body + foot);
res.end();
}
- var serverErrorDescs = {
- 400: "The request you made is invalid.",
- 405: "Method used to access the requested file isn't allowed.",
- 408: "You have timed out.",
- 414: "URL you sent is too long.",
- 431: "The request you sent contains headers, that are too large.",
- 451: "The requested file isn't accessible for legal reasons.",
- 497: "You sent non-TLS request to the HTTPS server."
- };
-
- //Server error calling method
+ // Server error calling method
function callServerError(errorCode, extName, stack, ch) {
- var errorFile = errorCode.toString() + ".html";
- var errorFile2 = "." + errorCode.toString();
- if (fs.existsSync(errorFile2)) errorFile = errorFile2;
- if (errorCode == 404 && fs.existsSync(page404)) errorFile = page404;
- if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
- if (stack === undefined) stack = generateErrorStack(new Error("Unknown error"));
- if (errorCode == 500 || errorCode == 502) {
- serverconsole.errmessage("There was an error while processing the request!");
- serverconsole.errmessage("Stack:");
- serverconsole.errmessage(stack);
+ if (typeof errorCode !== "number") {
+ throw new TypeError("HTTP error code parameter needs to be an integer.");
}
- if (stackHidden) stack = "[error stack hidden]";
- if (serverErrorDescs[errorCode] === undefined) {
- callServerError(501, extName, stack);
- } else {
- var cheaders = getCustomHeaders();
- if (ch) {
- var chon = Object.keys(cheaders);
- var chn = Object.keys(ch);
- for (var i = 0; i < chn.length; i++) {
- var nhn = chn[i];
- for (var j = 0; j < chon.length; j++) {
- if (chon[j].toLowerCase() == chn[i].toLowerCase()) {
- nhn = chon[j];
- break;
- }
+
+ // Handle optional parameters
+ if (extName && typeof extName === "object") {
+ ch = stack;
+ stack = extName;
+ extName = undefined;
+ } else if (typeof extName !== "string" && extName !== null && extName !== undefined) {
+ throw new TypeError("Extension name parameter needs to be a string.");
+ }
+
+ if (stack && typeof stack === "object" && Object.prototype.toString.call(stack) !== "[object Error]") {
+ ch = stack;
+ stack = undefined;
+ } else if (typeof stack !== "object" && typeof stack !== "string" && stack) {
+ throw new TypeError("Error stack parameter needs to be either a string or an instance of Error object.");
+ }
+
+ // Determine error file
+ function getErrorFileName(list, callback, _i) {
+ if (err.code == "ERR_SSL_HTTP_REQUEST" && process.version && parseInt(process.version.split(".")[0].substring(1)) >= 16) {
+ // Disable custom error page for HTTP SSL error
+ callback(errorCode.toString() + ".html");
+ return;
+ }
+
+ function medCallback(p) {
+ if (p) callback(p);
+ else {
+ if (errorCode == 404) {
+ fs.access(page404, fs.constants.F_OK, function (err) {
+ if (err) {
+ fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
+ try {
+ if (err) {
+ callback(errorCode.toString() + ".html");
+ } else {
+ callback("." + errorCode.toString());
+ }
+ } catch (err2) {
+ callServerError(500, err2);
+ }
+ });
+ } else {
+ try {
+ callback(page404);
+ } catch (err2) {
+ callServerError(500, err2);
+ }
+ }
+ });
+ } else {
+ fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
+ try {
+ if (err) {
+ callback(errorCode.toString() + ".html");
+ } else {
+ callback("." + errorCode.toString());
+ }
+ } catch (err2) {
+ callServerError(500, err2);
+ }
+ });
}
- if (ch[chn[i]]) cheaders[nhn] = ch[chn[i]];
}
}
- cheaders["Content-Type"] = "text/html; charset=utf-8";
- if (errorCode == 405 && !cheaders["Allow"]) cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
- fs.readFile(errorFile, function (err, data) {
- try {
- if (err) throw err;
- res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
- fd += data.toString().replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]"));
- responseEnd();
- } catch (ex) {
- var additionalError = 500;
- if (ex.code == "ENOENT") {
- additionalError = 404;
- } else if (ex.code == "ENOTDIR") {
- additionalError = 404;
- } else if (ex.code == "EACCES") {
- additionalError = 403;
- } else if (ex.code == "ENAMETOOLONG") {
- additionalError = 414;
- } else if (ex.code == "EMFILE") {
- additionalError = 503;
- } else if (ex.code == "ELOOP") {
- additionalError = 508;
- }
- res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
- res.write(("{errorMessage}{errorMessage}
{errorDesc}
" + ((additionalError == 404) ? "" : "Additionally, a {additionalError} error occurred while loading an error page.
") + "{server}
").replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")).replace(/{additionalError}/g, additionalError.toString()));
- res.end();
- }
- });
+ if (!_i) _i = 0;
+ if (_i >= list.length) {
+ medCallback(false);
+ return;
+ }
+
+ if (list[_i].scode != errorCode) {
+ getErrorFileName(list, callback, _i + 1);
+ return;
+ } else {
+ fs.access(list[_i].path, fs.constants.F_OK, function (err) {
+ if (err) {
+ getErrorFileName(list, callback, _i + 1);
+ } else {
+ medCallback(list[_i].path);
+ }
+ });
+ }
}
+
+ getErrorFileName(errorPages, function (errorFile) {
+ if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
+ if (stack === undefined) stack = generateErrorStack(new Error("Unknown error"));
+ if (errorCode == 500 || errorCode == 502) {
+ serverconsole.errmessage("There was an error while processing the request!");
+ serverconsole.errmessage("Stack:");
+ serverconsole.errmessage(stack);
+ }
+ if (stackHidden) stack = "[error stack hidden]";
+ if (serverHTTPErrorDescs[errorCode] === undefined) {
+ callServerError(501, extName, stack);
+ } else {
+ var cheaders = getCustomHeaders();
+ if (ch) {
+ var chon = Object.keys(cheaders);
+ Object.keys(ch).forEach(function (chnS) {
+ var nhn = chnS;
+ for (var j = 0; j < chon.length; j++) {
+ if (chon[j].toLowerCase() == chnS.toLowerCase()) {
+ nhn = chon[j];
+ break;
+ }
+ }
+ if (ch[chnS]) cheaders[nhn] = ch[chnS];
+ });
+ }
+ cheaders["Content-Type"] = "text/html; charset=utf-8";
+ if (errorCode == 405 && !cheaders["Allow"]) cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
+ if (err.code == "ERR_SSL_HTTP_REQUEST" && process.version && parseInt(process.version.split(".")[0].substring(1)) >= 16) {
+ // Disable custom error page for HTTP SSL error
+ res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
+ res.write(("{errorMessage}{errorMessage}
{errorDesc}
{server}
").replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")));
+ res.end();
+ } else {
+ fs.readFile(errorFile, function (err, data) {
+ try {
+ if (err) throw err;
+ res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
+ responseEnd(data.toString().replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")));
+ } catch (err) {
+ var additionalError = 500;
+ if (err.code == "ENOENT") {
+ additionalError = 404;
+ } else if (err.code == "ENOTDIR") {
+ additionalError = 404; // Assume that file doesn't exist
+ } else if (err.code == "EACCES") {
+ additionalError = 403;
+ } else if (err.code == "ENAMETOOLONG") {
+ additionalError = 414;
+ } else if (err.code == "EMFILE") {
+ additionalError = 503;
+ } else if (err.code == "ELOOP") {
+ additionalError = 508;
+ }
+ res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
+ res.write(("{errorMessage}{errorMessage}
{errorDesc}
" + ((additionalError == 404) ? "" : "Additionally, a {additionalError} error occurred while loading an error page.
") + "{server}
").replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")).replace(/{additionalError}/g, additionalError.toString()));
+ res.end();
+ }
+ });
+ }
+ }
+ });
}
var reqip = socket.remoteAddress;
var reqport = socket.remotePort;
- serverconsole.locmessage("Somebody connected to port " + (secure && fromMain ? sport : port) + "...");
+ reqcounter++;
+ malformedcounter++;
+ serverconsole.locmessage("Somebody connected to " + (secure && fromMain ? ((typeof sport == "number" ? "port " : "socket ") + sport) : ((typeof port == "number" ? "port " : "socket ") + port)) + "...");
serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " sent invalid request.");
try {
head = fs.existsSync("./.head") ? fs.readFileSync("./.head").toString() : (fs.existsSync("./head.html") ? fs.readFileSync("./head.html").toString() : ""); // header
foot = fs.existsSync("./.foot") ? fs.readFileSync("./.foot").toString() : (fs.existsSync("./foot.html") ? fs.readFileSync("./foot.html").toString() : ""); // footer
-
+
if ((err.code && (err.code.indexOf("ERR_SSL_") == 0 || err.code.indexOf("ERR_TLS_") == 0)) || (!err.code && err.message.indexOf("SSL routines") != -1)) {
if (err.code == "ERR_SSL_HTTP_REQUEST" || err.message.indexOf("http request") != -1) {
serverconsole.errmessage("Client sent HTTP request to HTTPS port.");
@@ -2506,6 +2602,12 @@ if (!cluster.isPrimary) {
}
}
+ if (err.code && err.code.indexOf("ERR_HTTP2_") == 0) {
+ serverconsole.errmessage("An HTTP/2 error occured: " + err.code);
+ callServerError(400);
+ return;
+ }
+
if (err.code && err.code == "ERR_HTTP_REQUEST_TIMEOUT") {
serverconsole.errmessage("Client timed out.");
callServerError(408);
@@ -2528,14 +2630,14 @@ if (!cluster.isPrimary) {
function checkHeaders(beginsFromFirst) {
for (var i = (beginsFromFirst ? 0 : 1); i < packetLines.length; i++) {
var header = packetLines[i];
- if (header == "") return false; //Beginning of body
+ if (header == "") return false; // Beginning of body
else if (header.indexOf(":") < 1) {
serverconsole.errmessage("Invalid header.");
callServerError(400);
return true;
} else if (header.length > 8192) {
serverconsole.errmessage("Header too large.");
- callServerError(431); //Headers too large
+ callServerError(431); // Headers too large
return true;
}
}
@@ -2546,8 +2648,8 @@ if (!cluster.isPrimary) {
var httpVersion = "HTTP/1.1";
if (String(packetLine1[0]).indexOf(":") > 0) {
if (!checkHeaders(true)) {
- serverconsole.errmessage("The request is invalid.");
- callServerError(400); //Also malformed Packet
+ serverconsole.errmessage("The request is invalid (it may be a part of larger invalid request).");
+ callServerError(400); // Also malformed Packet
return;
}
}
@@ -2555,114 +2657,108 @@ if (!cluster.isPrimary) {
if (String(packetLine1[packetLine1.length - 1]).length < 50) httpVersion = packetLine1.pop();
if (packetLine1.length != 1) {
serverconsole.errmessage("The head of request is invalid.");
- callServerError(400); //Malformed Packet
+ callServerError(400); // Malformed Packet
} else if (!httpVersion.toString().match(/^HTTP[\/]/i)) {
serverconsole.errmessage("Invalid protocol.");
- callServerError(400); //bad protocol version
+ callServerError(400); // bad protocol version
} else if (http.METHODS.indexOf(method) == -1) {
serverconsole.errmessage("Invalid method.");
- callServerError(405); //Also malformed Packet
+ callServerError(405); // Also malformed Packet
} else {
if (checkHeaders(false)) return;
if (packetLine1[0].length > 255) {
serverconsole.errmessage("URI too long.");
- callServerError(414); //Also malformed Packet
+ callServerError(414); // Also malformed Packet
} else {
- serverconsole.errmessage("Bad request.");
- callServerError(400); //Also malformed Packet
+ serverconsole.errmessage("The request is invalid.");
+ callServerError(400); // Also malformed Packet
}
}
- } catch (ex) {
+ } catch (err) {
serverconsole.errmessage("There was an error while determining type of malformed request.");
callServerError(400);
}
}
function connhandler(request, socket, head) {
- var reqIdInt = Math.round(Math.random() * 16777216);
+ var reqIdInt = Math.floor(Math.random() * 16777216);
+ if (reqIdInt == 16777216) reqIdInt = 0;
var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
var serverconsole = {
climessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.climessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.climessage(nmsg);
+ });
return;
}
- console.log("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
+ console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
reqmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.reqmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.reqmessage(nmsg);
+ });
return;
}
- console.log("\x1b[34mSERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
resmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.resmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.resmessage(nmsg);
+ });
return;
}
- console.log("\x1b[32mSERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
errmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.errmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.errmessage(nmsg);
+ });
return;
}
- console.log("\x1b[31mSERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locerrmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locerrmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locerrmessage(nmsg);
+ });
return;
}
- console.log("\x1b[41mSERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
+ console.log("\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
LOG("SERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locwarnmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locwarnmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locwarnmessage(nmsg);
+ });
return;
}
- console.log("\x1b[43mSERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
+ console.log("\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
LOG("SERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locmessage(nmsg);
+ });
return;
}
- console.log("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
+ console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
}
@@ -2676,20 +2772,21 @@ if (!cluster.isPrimary) {
var reqip = socket.remoteAddress;
var reqport = socket.remotePort;
- serverconsole.locmessage("Somebody connected to port " + (secure ? sport : port) + "...");
+ reqcounter++;
+ serverconsole.locmessage("Somebody connected to " + (secure ? ((typeof sport == "number" ? "port " : "socket ") + sport) : ((typeof port == "number" ? "port " : "socket ") + port)) + "...");
serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " wants to proxy " + request.url + " through this server");
if (request.headers["user-agent"] != undefined) serverconsole.reqmessage("Client uses " + request.headers["user-agent"]);
function modExecute(mods, ffinals) {
var proxyMods = [];
- for (var i = 0; i < mods.length; i++) {
- if (mods[i].proxyCallback !== undefined) proxyMods.push(mods[i]);
- }
+ mods.forEach(function (mod) {
+ if (mod.proxyCallback !== undefined) proxyMods.push(mod);
+ });
var modFunction = ffinals;
- for (var i = proxyMods.length - 1; i >= 0; i--) {
- modFunction = proxyMods[i].proxyCallback(req, socket, head, configJSON, serverconsole, modFunction);
- }
+ proxyMods.reverse().forEach(function (proxyMod) {
+ modFunction = proxyMod.proxyCallback(req, socket, head, configJSON, serverconsole, modFunction);
+ });
modFunction();
}
@@ -2702,389 +2799,376 @@ if (!cluster.isPrimary) {
modExecute(mods, vres(req, socket, head, serverconsole));
}
- function reqhandler(request, response, fromMain) {
+ function reqhandler(req, res, fromMain) {
if (fromMain === undefined) fromMain = true;
- var reqIdInt = Math.round(Math.random() * 16777216);
+ var reqIdInt = Math.floor(Math.random() * 16777216);
+ if (reqIdInt == 16777216) reqIdInt = 0;
var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
var serverconsole = {
climessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.climessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.climessage(nmsg);
+ });
return;
}
- console.log("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
+ console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
reqmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.reqmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.reqmessage(nmsg);
+ });
return;
}
- console.log("\x1b[34mSERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
resmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.resmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.resmessage(nmsg);
+ });
return;
}
- console.log("\x1b[32mSERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
errmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.errmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.errmessage(nmsg);
+ });
return;
}
- console.log("\x1b[31mSERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
+ console.log("\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
LOG("SERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locerrmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locerrmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locerrmessage(nmsg);
+ });
return;
}
- console.log("\x1b[41mSERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
+ console.log("\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
LOG("SERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locwarnmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locwarnmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locwarnmessage(nmsg);
+ });
return;
}
- console.log("\x1b[43mSERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
+ console.log("\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
LOG("SERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
},
locmessage: function (msg) {
if (msg.indexOf("\n") != -1) {
- var nmsg = msg.split("\n");
- for (var i = 0; i < nmsg.length; i++) {
- serverconsole.locmessage(nmsg[i]);
- }
+ msg.split("\n").forEach(function (nmsg) {
+ serverconsole.locmessage(nmsg);
+ });
return;
}
- console.log("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
+ console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
return;
}
};
+ function matchHostname(hostname) {
+ if (typeof hostname == "undefined" || hostname == "*") {
+ return true;
+ } else if (req.headers.host && hostname.indexOf("*.") == 0 && hostname != "*.") {
+ var hostnamesRoot = hostname.substring(2);
+ if (req.headers.host == hostnamesRoot || (req.headers.host.length > hostnamesRoot.length && req.headers.host.indexOf("." + hostnamesRoot) == req.headers.host.length - hostnamesRoot.length - 1)) {
+ return true;
+ }
+ } else if (req.headers.host && req.headers.host == hostname) {
+ return true;
+ }
+ return false;
+ }
+
function getCustomHeaders() {
var ph = JSON.parse(JSON.stringify(customHeaders));
- var phk = Object.keys(ph);
- for (var i = 0; i < phk.length; i++) {
- if (typeof ph[phk[i]] == "string") ph[phk[i]] = ph[phk[i]].replace(/\{path\}/g, request.url);
+ if (configJSON.customHeadersVHost) {
+ var vhostP = null;
+ configJSON.customHeadersVHost.every(function (vhost) {
+ if (matchHostname(vhost.host) && ipMatch(vhost.ip, req.socket ? req.socket.localAddress : undefined)) {
+ vhostP = vhost;
+ return false;
+ } else {
+ return true;
+ }
+ });
+ if (vhostP && vhostP.headers) {
+ var phNu = JSON.parse(JSON.stringify(vhostP.headers));
+ Object.keys(phNu).forEach(function (phNuK) {
+ ph[phNuK] = phNu[phNuK];
+ });
+ }
}
+ Object.keys(ph).forEach(function (phk) {
+ if (typeof ph[phk] == "string") ph[phk] = ph[phk].replace(/\{path\}/g, req.url);
+ });
return ph;
}
- //Make HTTP/1.x API-based scripts compatible with HTTP/2.0 API
- if (configJSON.enableHTTP2 == true && request.httpVersion == "2.0") {
- try {
- //Set HTTP/1.x methods (to prevent process warnings)
- response.writeHeadNodeApi = response.writeHead;
- response.setHeaderNodeApi = response.setHeader;
- response.writeHead = function (a, b, c) {
- var table = c;
- if (typeof (b) == "object") table = b;
- if (table == undefined) table = this.tHeaders;
- table = JSON.parse(JSON.stringify(table));
- if (table["content-type"] != undefined && table["Content-Type"] != undefined) {
- delete table["content-type"];
- }
- delete table["transfer-encoding"];
- delete table["connection"];
- delete table["keep-alive"];
- delete table["upgrade"];
- if(res.stream && res.stream.destroyed) {
- return false;
- } else {
- return res.writeHeadNodeApi(a, table);
- }
- };
+ // Make HTTP/1.x API-based scripts compatible with HTTP/2.0 API
+ if (configJSON.enableHTTP2 == true && req.httpVersion == "2.0") {
+ // Set HTTP/1.x methods (to prevent process warnings)
+ res.writeHeadNodeApi = res.writeHead;
+ res.setHeaderNodeApi = res.setHeader;
- response.setHeader = function (a, b) {
- if (a != "transfer-encoding" && a != "connection" && a != "keep-alive" && a != "upgrade") return response.setHeaderNodeApi(a, b);
+ res.writeHead = function (a, b, c) {
+ var table = c;
+ if (typeof (b) == "object") table = b;
+ if (table == undefined) table = this.tHeaders;
+ if (table == undefined) table = {};
+ table = JSON.parse(JSON.stringify(table));
+ Object.keys(table).forEach(function (key) {
+ var al = key.toLowerCase();
+ if (al == "transfer-encoding" || al == "connection" || al == "keep-alive" || al == "upgrade") delete table[key];
+ });
+ if (res.stream && res.stream.destroyed) {
return false;
- };
- //Set HTTP/1.x headers
- if (!request.headers.host) request.headers.host = request.headers[":authority"];
- (request.headers[":path"] == undefined ? (function () {})() : request.url = request.headers[":path"]);
- request.protocol = request.headers[":scheme"];
- var headers = [":path" || ":method"];
- for (var i = 0; i < headers.length; i++) {
- if (request.headers[headers[i]] == undefined) {
- var cheaders = getCustomHeaders();
- cheaders["Content-Type"] = "text/html; charset=utf-8";
- response.writeHead(400, "Bad Request", cheaders);
- response.write("400 Bad Request400 Bad Request
The request you sent is invalid.
" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(//g, ">") + (request.headers[":authority"] == undefined ? "" : " on " + request.headers[":authority"]) + "
");
- response.end();
- return;
- }
+ } else {
+ return res.writeHeadNodeApi(a, table);
}
- } catch (ex) {
- var cheaders = getCustomHeaders();
- cheaders["Content-Type"] = "text/html; charset=utf-8";
- cheaders[":status"] = "500";
- response.stream.respond(cheaders);
- response.stream.write("500 Internal Server Error500 Internal Server Error
The server had an unexpected error. Below, error stack is shown:
" + (stackHidden ? "[error stack hidden]" : generateErrorStack(ex)).replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ") + "
Please contact with developer/administrator of the website.
" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(//g, ">") + (request.headers[":authority"] == undefined ? "" : " on " + request.headers[":authority"]) + "
");
- response.stream.end();
- return;
+ };
+ res.setHeader = function (headerName, headerValue) {
+ var al = headerName.toLowerCase();
+ if (al != "transfer-encoding" && al != "connection" && al != "keep-alive" && al != "upgrade") return res.setHeaderNodeApi(headerName, headerValue);
+ return false;
+ };
+
+ // Set HTTP/1.x headers
+ if (!req.headers.host) req.headers.host = req.headers[":authority"];
+ if (!req.url) req.url = req.headers[":path"];
+ if (!req.protocol) req.protocol = req.headers[":scheme"];
+ if (!req.method) req.method = req.headers[":method"];
+ if (req.headers[":path"] == undefined || req.headers[":method"] == undefined) {
+ var err = new Error("Either \":path\" or \":method\" pseudoheader is missing.");
+ if(Buffer.alloc) err.rawPacket = Buffer.alloc(0);
+ reqerrhandler(err, req.socket, fromMain);
}
}
- if (request.headers["x-svr-js-from-main-thread"] == "true") {
- response.writeHead(204, "No Content", getCustomHeaders());
- var lastStatusCode = null;
- response.end();
+ if (req.headers["x-svr-js-from-main-thread"] == "true" && req.socket && (!req.socket.remoteAddress || req.socket.remoteAddress == "::1" || req.socket.remoteAddress == "::ffff:127.0.0.1" || req.socket.remoteAddress == "127.0.0.1" || req.socket.remoteAddress == "localhost" || req.socket.remoteAddress == host || req.socket.remoteAddress == "::ffff:" + host)) {
+ var headers = getCustomHeaders();
+ res.writeHead(204, http.STATUS_CODES[204], headers);
+ res.end();
return;
}
- request.url = fixNodeMojibakeURL(request.url);
+ req.url = fixNodeMojibakeURL(req.url);
var headWritten = false;
- response.writeHeadNative = response.writeHead;
- response.writeHead = function (a, b, c) {
- if (!(headWritten && process.isBun && a === lastStatusCode && b === undefined && c === undefined)) {
+ var lastStatusCode = null;
+ res.writeHeadNative = res.writeHead;
+ res.writeHead = function (code, codeDescription, headers) {
+ if (!(headWritten && process.isBun && code === lastStatusCode && codeDescription === undefined && codeDescription === undefined)) {
if (headWritten) {
process.emitWarning("res.writeHead called multiple times.", {
code: "WARN_SVRJS_MULTIPLE_WRITEHEAD"
});
- return response;
+ return res;
} else {
headWritten = true;
}
- if (parseInt(a) >= 400 && parseInt(a) <= 599) {
- serverconsole.errmessage("Server responded with " + a.toString() + " code.");
+ if (code >= 400 && code <= 599) {
+ if (code >= 400 && code <= 499) err4xxcounter++;
+ else if (code >= 500 && code <= 599) err5xxcounter++;
+ serverconsole.errmessage("Server responded with " + code.toString() + " code.");
} else {
- serverconsole.resmessage("Server responded with " + a.toString() + " code.");
+ serverconsole.resmessage("Server responded with " + code.toString() + " code.");
}
- lastStatusCode = a;
+ if (typeof codeDescription != "string" && http.STATUS_CODES[code]) {
+ if (!headers) headers = codeDescription;
+ codeDescription = http.STATUS_CODES[code];
+ }
+ lastStatusCode = code;
}
- response.writeHeadNative(a, b, c);
+ res.writeHeadNative(code, codeDescription, headers);
};
- if (wwwredirect) {
- var hostname = request.headers.host.split[":"];
- var hostport = null;
- if (hostname.length > 1 && (hostname[0] != "[" || hostname[hostname.length - 1] != "]")) hostport = hostname.pop();
- hostname = hostname.join(":");
+
+ var finished = false;
+ res.on("finish", function () {
+ if (!finished) {
+ finished = true;
+ serverconsole.locmessage("Client disconnected.");
+ }
+ });
+ res.on("close", function () {
+ if (!finished) {
+ finished = true;
+ serverconsole.locmessage("Client disconnected.");
+ }
+ });
+ var isProxy = false;
+ if (req.url[0] != "/" && req.url != "*") isProxy = true;
+ serverconsole.locmessage("Somebody connected to " + (secure && fromMain ? ((typeof sport == "number" ? "port " : "socket ") + sport) : ((typeof port == "number" ? "port " : "socket ") + port)) + "...");
+
+ if (req.socket == null) {
+ serverconsole.errmessage("Client socket is null!!!");
+ return;
}
- if (wwwredirect && hostname == domain && hostname.indexOf("www.") != 0) {
- var lloc = (request.socket.encrypted ? "https" : "http") + "://www." + hostname + (hostport ? ":" + hostport : "");
- try {
- var rheaders = getCustomHeaders();
- rheaders["Location"] = lloc + (request.url.replace(/\/+/g, "/"));
- response.writeHead(301, "Redirect to WWW", rheaders);
- response.end();
- } catch (ex) {
- var cheaders = getCustomHeaders();
- cheaders["Content-Type"] = "text/html; charset=utf-8";
- res.writeHead(500, "Internal Server Error", cheaders);
- res.write("500 Internal Server Error500 Internal Server Error
The server had an unexpected error. Below, error stack is shown:
" + (stackHidden ? "[error stack hidden]" : generateErrorStack(ex)).replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ") + "
Please contact with developer/administrator of the website.
" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(//g, ">") + (req.headers.host == undefined ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "
");
- response.end();
- }
- } else {
- var finished = false;
- response.on("finish", function () {
- if (!finished) {
- finished = true;
- serverconsole.locmessage("Client disconnected.");
- }
- });
- response.on("close", function () {
- if (!finished) {
- finished = true;
- serverconsole.locmessage("Client disconnected.");
- }
- });
- var isProxy = false;
- if (request.url.indexOf("/") != 0 && request.url != "*") isProxy = true;
- serverconsole.locmessage("Somebody connected to port " + (secure && fromMain ? sport : port) + "...");
- if (request.socket == null) {
- serverconsole.errmessage("Client socket is null!!!");
- return;
+ // Set up X-Forwarded-For
+ var reqip = req.socket.remoteAddress;
+ var reqport = req.socket.remotePort;
+ var oldip = "";
+ var oldport = "";
+ var isForwardedValid = true;
+ if (enableIPSpoofing) {
+ if (req.headers["x-forwarded-for"] != undefined) {
+ var preparedReqIP = req.headers["x-forwarded-for"].split(",")[0].replace(/ /g, "");
+ var preparedReqIPvalid = net.isIP(preparedReqIP);
+ if (preparedReqIPvalid) {
+ if (preparedReqIPvalid == 4 && req.socket.remoteAddress && req.socket.remoteAddress.indexOf(":") > -1) preparedReqIP = "::ffff:" + preparedReqIP;
+ reqip = preparedReqIP;
+ reqport = null;
+ try {
+ oldport = req.socket.remotePort;
+ oldip = req.socket.remoteAddress;
+ req.socket.realRemotePort = reqport;
+ req.socket.realRemoteAddress = reqip;
+ req.socket.originalRemotePort = oldport;
+ req.socket.originalRemoteAddress = oldip;
+ res.socket.realRemotePort = reqport;
+ res.socket.realRemoteAddress = reqip;
+ res.socket.originalRemotePort = oldport;
+ res.socket.originalRemoteAddress = oldip;
+ } catch (err) {
+ // Address setting failed
+ }
+ } else {
+ isForwardedValid = false;
+ }
+ }
+ }
+
+ reqcounter++;
+
+ // Process the Host header
+ var oldHostHeader = req.headers.host;
+ if (typeof req.headers.host == "string") {
+ req.headers.host = req.headers.host.toLowerCase();
+ if (!req.headers.host.match(/^\.+$/)) req.headers.host = req.headers.host.replace(/\.$/g, "");
+ }
+
+ serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " wants " + (req.method == "GET" ? "content in " : (req.method == "POST" ? "to post content in " : (req.method == "PUT" ? "to add content in " : (req.method == "DELETE" ? "to delete content in " : (req.method == "PATCH" ? "to patch content in " : "to access content using " + req.method + " method in "))))) + ((req.headers.host == undefined || isProxy) ? "" : req.headers.host) + req.url);
+ if (req.headers["user-agent"] != undefined) serverconsole.reqmessage("Client uses " + req.headers["user-agent"]);
+ if (oldHostHeader && oldHostHeader != req.headers.host) serverconsole.resmessage("Host name rewritten: " + oldHostHeader + " => " + req.headers.host);
+
+ var acceptEncoding = req.headers["accept-encoding"];
+ if (!acceptEncoding) acceptEncoding = "";
+
+ // Header and footer placeholders
+ var head = "";
+ var foot = "";
+
+ function responseEnd(body) {
+ // If body is Buffer, then it is converted to String anyway.
+ res.write(head + body + foot);
+ res.end();
+ }
+
+ // Server error calling method
+ function callServerError(errorCode, extName, stack, ch) {
+ if (typeof errorCode !== "number") {
+ throw new TypeError("HTTP error code parameter needs to be an integer.");
}
- var reqip = request.socket.remoteAddress;
- var reqport = request.socket.remotePort;
- var oldip = "";
- var oldport = "";
- var isForwardedValid = true;
- if(enableIPSpoofing) {
- if (request.headers["x-forwarded-for"] != undefined) {
- var preparedReqIP = request.headers["x-forwarded-for"].split(",")[0].replace(/ /g, "");
- var preparedReqIPvalid = net.isIP(preparedReqIP);
- if(preparedReqIPvalid) {
- if (preparedReqIPvalid == 4 && request.socket.remoteAddress && request.socket.remoteAddress.indexOf(":") > -1) preparedReqIP = "::ffff:" + preparedReqIP;
- reqip = preparedReqIP;
- reqport = null;
- try {
- oldport = request.socket.remotePort;
- oldip = request.socket.remoteAddress;
- request.socket.realRemotePort = reqport;
- request.socket.realRemoteAddress = reqip;
- request.socket.originalRemotePort = oldport;
- request.socket.originalRemoteAddress = oldip;
- response.socket.realRemotePort = reqport;
- response.socket.realRemoteAddress = reqip;
- response.socket.originalRemotePort = oldport;
- response.socket.originalRemoteAddress = oldip;
- } catch (err) {
- // Address setting failed
+ // Handle optional parameters
+ if (extName && typeof extName === "object") {
+ ch = stack;
+ stack = extName;
+ extName = undefined;
+ } else if (typeof extName !== "string" && extName !== null && extName !== undefined) {
+ throw new TypeError("Extension name parameter needs to be a string.");
+ }
+
+ if (stack && typeof stack === "object" && Object.prototype.toString.call(stack) !== "[object Error]") {
+ ch = stack;
+ stack = undefined;
+ } else if (typeof stack !== "object" && typeof stack !== "string" && stack) {
+ throw new TypeError("Error stack parameter needs to be either a string or an instance of Error object.");
+ }
+
+ // Determine error file
+ function getErrorFileName(list, callback, _i) {
+ function medCallback(p) {
+ if (p) callback(p);
+ else {
+ if (errorCode == 404) {
+ fs.access(page404, fs.constants.F_OK, function (err) {
+ if (err) {
+ fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
+ try {
+ if (err) {
+ callback(errorCode.toString() + ".html");
+ } else {
+ callback("." + errorCode.toString());
+ }
+ } catch (err2) {
+ callServerError(500, err2);
+ }
+ });
+ } else {
+ try {
+ callback(page404);
+ } catch (err2) {
+ callServerError(500, err2);
+ }
+ }
+ });
+ } else {
+ fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
+ try {
+ if (err) {
+ callback(errorCode.toString() + ".html");
+ } else {
+ callback("." + errorCode.toString());
+ }
+ } catch (err2) {
+ callServerError(500, err2);
+ }
+ });
}
- } else {
- isForwardedValid = false;
}
}
+
+ if (!_i) _i = 0;
+ if (_i >= list.length) {
+ medCallback(false);
+ return;
+ }
+
+ if (list[_i].scode != errorCode || !(matchHostname(list[_i].host) && ipMatch(list[_i].ip, req.socket ? req.socket.localAddress : undefined))) {
+ getErrorFileName(list, callback, _i + 1);
+ return;
+ } else {
+ fs.access(list[_i].path, fs.constants.F_OK, function (err) {
+ if (err) {
+ getErrorFileName(list, callback, _i + 1);
+ } else {
+ medCallback(list[_i].path);
+ }
+ });
+ }
}
- if (!isProxy) serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " wants " + (request.method == "GET" ? "content in " : (request.method == "POST" ? "to post content in " : (request.method == "PUT" ? "to add content in " : (request.method == "DELETE" ? "to delete content in " : (request.method == "PATCH" ? "to patch content in " : "to access content using " + request.method + " method in "))))) + (request.headers.host == undefined ? "" : request.headers.host) + request.url);
- else serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " wants " + (request.method == "GET" ? "content in " : (request.method == "POST" ? "to post content in " : (request.method == "PUT" ? "to add content in " : (request.method == "DELETE" ? "to delete content in " : (request.method == "PATCH" ? "to patch content in " : "to access content using " + request.method + " method in "))))) + request.url);
- if (request.headers["user-agent"] != undefined) serverconsole.reqmessage("Client uses " + request.headers["user-agent"]);
-
- var acceptEncoding = request.headers["accept-encoding"];
- if (!acceptEncoding) acceptEncoding = "";
-
- // Header and footer placeholders
- var head = "";
- var foot = "";
- var fd = "";
-
- function responseEnd(d) {
- if (d === undefined) d = fd;
- res.write(head + d + foot);
- res.end();
- }
-
- // function responseEndGzip(d) {
- // if (d === undefined) d = fd;
- // zlib.gzip(head + d + foot, function (err, buff) {
- // if (err) {
- // throw err;
- // } else {
- // res.write(buff);
- // res.end();
- // }
- // });
- // }
- //
- // function responseEndDeflate(d) {
- // if (d === undefined) d = fd;
- // zlib.deflateRaw(head + d + foot, function (err, buff) {
- // if (err) {
- // throw err;
- // } else {
- // res.write(buff);
- // res.end();
- // }
- // });
- // }
-
- var req = request; // request var is req = request
- var res = response; // response var is res = response
-
- //Error descriptions
- var serverErrorDescs = {
- 200: "The request succeeded! :)",
- 201: "New resource has been created.",
- 202: "The request has been accepted for processing, but the processing has not been completed.",
- 400: "The request you made is invalid.",
- 401: "You need to authenticate yourself in order to access the requested file.",
- 402: "You need to pay in order to access the requested file.",
- 403: "You don't have access to the requested file.",
- 404: "The requested file doesn't exist. If you have typed URL manually, then please check the spelling.",
- 405: "Method used to access the requested file isn't allowed.",
- 406: "The request is capable of generating only not acceptable content.",
- 407: "You need to authenticate yourself in order to use the proxy.",
- 408: "You have timed out.",
- 409: "The request you sent conflicts with the current state of the server.",
- 410: "The requested file is permanently deleted.",
- 411: "Content-Length property is required.",
- 412: "The server doesn't meet preconditions you put in the request.",
- 413: "The request you sent is too large.",
- 414: "URL you sent is too long.",
- 415: "The media type of request you sent isn't supported by the server.",
- 416: "Content-Range you sent is unsatisfiable.",
- 417: "Expectation in Expect property couldn't be satisfied.",
- 418: "The server (teapot) can't brew any coffee! ;)",
- 421: "The request you made isn't intended for this server.",
- 422: "The server couldn't process content sent by you.",
- 423: "The requested file is locked.",
- 424: "The request depends on another failed request.",
- 425: "The server is unwilling to risk processing a request that might be replayed.",
- 426: "You need to upgrade protocols you use to request a file.",
- 428: "The request you sent needs to be conditional, but it isn't.",
- 429: "You sent too much requests to the server.",
- 431: "The request you sent contains headers, that are too large.",
- 451: "The requested file isn't accessible for legal reasons.",
- 500: "The server had an unexpected error. Below, the error stack is shown: {stack}
Please contact with developer/administrator at {contact}.",
- 501: "The request requires use of a function, which isn't currently implemented by the server.",
- 502: "The server had an error, while it was acting as a gateway.
Please contact with developer/administrator at {contact}.",
- 503: "The service provided by the server is currently unavailable, possibly due to maintenance downtime or capacity problems. Please try again later.
Please contact with developer/administrator at {contact}.",
- 504: "The server couldn't get response in time, while it was acting as a gateway.
Please contact with developer/administrator at {contact}.",
- 505: "The server doesn't support HTTP version used in the request.",
- 506: "Variant header is configured to be engaged in content negotiation.
Please contact with developer/administrator at {contact}.",
- 507: "The server ran out of disk space neccessary to complete the request.",
- 508: "The server detected an infinite loop while processing the request.",
- 509: "The server has it's bandwidth limit exceeded.
Please contact with developer/administrator at {contact}.",
- 510: "The server requires an extended HTTP request. The request you made isn't an extended HTTP request.",
- 511: "You need to authenticate yourself in order to get network access.",
- 598: "The server couldn't get response in time, while it was acting as a proxy.",
- 599: "The server couldn't connect in time, while it was acting as a proxy."
- };
-
- // Server error calling method
- function callServerError(errorCode, extName, stack, ch) {
- if (typeof errorCode !== "number") {
- throw new TypeError("HTTP error code parameter needs to be an integer.");
- }
-
- // Handle optional parameters
- if (extName && typeof extName === "object") {
- ch = stack;
- stack = extName;
- extName = undefined;
- } else if (typeof extName !== "string" && extName !== null && extName !== undefined) {
- throw new TypeError("Extension name parameter needs to be a string.");
- }
-
- if (stack && typeof stack === "object" && Object.prototype.toString.call(stack) !== "[object Error]") {
- ch = stack;
- stack = undefined;
- } else if (typeof stack !== "object" && typeof stack !== "string" && stack) {
- throw new TypeError("Error stack parameter needs to be either a string or an instance of Error object.");
- }
-
- var errorFile = errorCode.toString() + ".html";
- var errorFile2 = "." + errorCode.toString();
- if (fs.existsSync(errorFile2)) errorFile = errorFile2;
- if (errorCode == 404 && fs.existsSync(page404)) errorFile = page404;
-
+ getErrorFileName(errorPages, function (errorFile) {
// Generate error stack if not provided
if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
if (stack === undefined) stack = generateErrorStack(new Error("Unknown error"));
@@ -3099,7 +3183,7 @@ if (!cluster.isPrimary) {
if (stackHidden) stack = "[error stack hidden]";
// Validate the error code and handle unknown codes
- if (serverErrorDescs[errorCode] === undefined) {
+ if (serverHTTPErrorDescs[errorCode] === undefined) {
callServerError(501, extName, stack);
} else {
var cheaders = getCustomHeaders();
@@ -3107,17 +3191,16 @@ if (!cluster.isPrimary) {
// Process custom headers if provided
if (ch) {
var chon = Object.keys(cheaders);
- var chn = Object.keys(ch);
- for (var i = 0; i < chn.length; i++) {
- var nhn = chn[i];
+ Object.keys(ch).forEach(function (chnS) {
+ var nhn = chnS;
for (var j = 0; j < chon.length; j++) {
- if (chon[j].toLowerCase() == chn[i].toLowerCase()) {
+ if (chon[j].toLowerCase() == chnS.toLowerCase()) {
nhn = chon[j];
break;
}
}
- if (ch[chn[i]]) cheaders[nhn] = ch[chn[i]];
- }
+ if (ch[chnS]) cheaders[nhn] = ch[chnS];
+ });
}
cheaders["Content-Type"] = "text/html; charset=utf-8";
@@ -3129,513 +3212,831 @@ if (!cluster.isPrimary) {
fs.readFile(errorFile, function (err, data) {
try {
if (err) throw err;
- response.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
- fd += data.toString().replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{path}/g, request.url.replace(/&/g, "&").replace(//g, ">")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">") + ((req.headers.host == undefined || isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">"))).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")); // Replace placeholders in error response
- responseEnd();
- } catch (ex) {
+ res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
+ responseEnd(data.toString().replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{path}/g, req.url.replace(/&/g, "&").replace(//g, ">")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">") + ((req.headers.host == undefined || isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">"))).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]"))); // Replace placeholders in error response
+ } catch (err) {
var additionalError = 500;
// Handle additional error cases
- if (ex.code == "ENOENT") {
+ if (err.code == "ENOENT") {
additionalError = 404;
- } else if (ex.code == "ENOTDIR") {
- additionalError = 404;
- } else if (ex.code == "EACCES") {
+ } else if (err.code == "ENOTDIR") {
+ additionalError = 404; // Assume that file doesn't exist
+ } else if (err.code == "EACCES") {
additionalError = 403;
- } else if (ex.code == "ENAMETOOLONG") {
+ } else if (err.code == "ENAMETOOLONG") {
additionalError = 414;
- } else if (ex.code == "EMFILE") {
+ } else if (err.code == "EMFILE") {
additionalError = 503;
- } else if (ex.code == "ELOOP") {
+ } else if (err.code == "ELOOP") {
additionalError = 508;
}
- response.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
- response.write(("
{errorMessage}{errorMessage}
{errorDesc}
" + ((additionalError == 404) ? "" : "Additionally, a {additionalError} error occurred while loading an error page.
") + "{server}
").replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{path}/g, request.url.replace(/&/g, "&").replace(//g, ">")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">") + ((req.headers.host == undefined || isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">"))).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")).replace(/{additionalError}/g, additionalError.toString())); // Replace placeholders in error response
- response.end();
+ res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
+ res.write(("{errorMessage}{errorMessage}
{errorDesc}
" + ((additionalError == 404) ? "" : "Additionally, a {additionalError} error occurred while loading an error page.
") + "{server}
").replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(//g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(//g, ">").replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ {2}/g, " ")).replace(/{path}/g, req.url.replace(/&/g, "&").replace(//g, ">")).replace(/{server}/g, "" + ((exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(//g, ">") + ((req.headers.host == undefined || isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">"))).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(//g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")).replace(/{additionalError}/g, additionalError.toString())); // Replace placeholders in error response
+ res.end();
}
});
}
- }
+ });
+ }
- try {
- head = fs.existsSync("./.head") ? fs.readFileSync("./.head").toString() : (fs.existsSync("./head.html") ? fs.readFileSync("./head.html").toString() : ""); // header
- foot = fs.existsSync("./.foot") ? fs.readFileSync("./.foot").toString() : (fs.existsSync("./foot.html") ? fs.readFileSync("./foot.html").toString() : ""); // footer
- } catch (err) {
- callServerError(500, undefined, generateErrorStack(err));
- }
-
- function redirect(dest, isTemporary, headers) {
- if (headers === undefined) headers = getCustomHeaders();
- headers["Location"] = dest;
- var scode = isTemporary ? 302 : 301;
- res.writeHead(scode, http.STATUS_CODES[scode], headers);
- serverconsole.resmessage("Client redirected to " + dest);
- res.end();
+ try {
+ head = fs.existsSync("./.head") ? fs.readFileSync("./.head").toString() : (fs.existsSync("./head.html") ? fs.readFileSync("./head.html").toString() : ""); // header
+ foot = fs.existsSync("./.foot") ? fs.readFileSync("./.foot").toString() : (fs.existsSync("./foot.html") ? fs.readFileSync("./foot.html").toString() : ""); // footer
+ } catch (err) {
+ callServerError(500, err);
+ }
+
+ // Function to perform HTTP redirection to a specified destination URL
+ function redirect(destination, isTemporary, keepMethod, customHeaders) {
+ // If keepMethod is a object, then save it to customHeaders
+ if (typeof keepMethod == "object") customHeaders = keepMethod;
+
+ // If isTemporary is a object, then save it to customHeaders
+ if (typeof isTemporary == "object") customHeaders = isTemporary;
+
+ // If customHeaders are not provided, get the default custom headers
+ if (customHeaders === undefined) customHeaders = getCustomHeaders();
+
+ // Set the "Location" header to the destination URL
+ customHeaders["Location"] = destination;
+
+ // Determine the status code for redirection based on the isTemporary and keepMethod flags
+ var statusCode = keepMethod ? (isTemporary ? 307 : 308) : (isTemporary ? 302 : 301);
+
+ // Write the response header with the appropriate status code and message
+ res.writeHead(statusCode, http.STATUS_CODES[statusCode], customHeaders);
+
+ // Log the redirection message
+ serverconsole.resmessage("Client redirected to " + destination);
+
+ // End the response
+ res.end();
+
+ // Return from the function
+ return;
+ }
+
+ // Function to parse incoming POST data from the request
+ function parsePostData(options, callback) {
+ // If the request method is not POST, return a 405 Method Not Allowed error
+ if (req.method != "POST") {
+ // Get the default custom headers and add "Allow" header with value "POST"
+ var customHeaders = getCustomHeaders();
+ customHeaders["Allow"] = "POST";
+
+ // Call the server error function with 405 status code and custom headers
+ callServerError(405, customHeaders);
return;
}
- function parsePostData(options, callback) {
- if (req.method != "POST") {
- var h = getCustomHeaders();
- h["Allow"] = "POST";
- callServerError(405, undefined, undefined, h);
+ // Set formidableOptions to options, if provided; otherwise, set it to an empty object
+ var formidableOptions = options ? options : {};
+
+ // If no callback is provided, set the callback to options and reset formidableOptions
+ if (!callback) {
+ callback = options;
+ formidableOptions = {};
+ }
+
+ // If the formidable module had an error, call the server error function with 500 status code and error stack
+ if (formidable._errored) callServerError(500, formidable._errored);
+
+ // Create a new formidable form
+ var form = formidable(formidableOptions);
+
+ // Parse the request and process the fields and files
+ form.parse(req, function (err, fields, files) {
+ // If there was an error, call the server error function with status code determined by error
+ if (err) {
+ if (err.httpCode) callServerError(err.httpCode);
+ else callServerError(400);
return;
}
- var formidableOptions = options;
- if (!callback) {
- callback = options;
- formidableOptions = {};
- }
- if (formidable._errored) callServerError(500, undefined, generateErrorStack(formidable._errored));
- var form = formidable(formidableOptions);
- form.parse(req, function (err, fields, files) {
- if (err) {
- if(err.httpCode) callServerError(err.httpCode);
- else callServerError(400);
- return;
+ // Otherwise, call the provided callback function with the parsed fields and files
+ callback(fields, files);
+ });
+ }
+
+
+ // Function to parse a URL string into a URL object
+ function parseURL(uri) {
+ // Prepare the path (remove multiple slashes)
+ var preparedURI = uri.replace(/^\/{2,}/,"/");
+ // Check if the URL API is available (Node.js version >= 10)
+ if (typeof URL !== "undefined" && url.Url) {
+ try {
+ // Create a new URL object using the provided URI and base URL
+ var uobject = new URL(preparedURI, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
+
+ // Create a new URL object (similar to deprecated url.Url)
+ var nuobject = new url.Url();
+
+ // Set properties of the new URL object from the provided URL
+ if (preparedURI.indexOf("/") != -1) nuobject.slashes = true;
+ if (uobject.protocol != "") nuobject.protocol = uobject.protocol;
+ if (uobject.username != "" && uobject.password != "") nuobject.auth = uobject.username + ":" + uobject.password;
+ if (uobject.host != "") nuobject.host = uobject.host;
+ if (uobject.hostname != "") nuobject.hostname = uobject.hostname;
+ if (uobject.port != "") nuobject.port = uobject.port;
+ if (uobject.pathname != "") nuobject.pathname = uobject.pathname;
+ if (uobject.search != "") nuobject.search = uobject.search;
+ if (uobject.hash != "") nuobject.hash = uobject.hash;
+ if (uobject.href != "") nuobject.href = uobject.href;
+
+ // Adjust the pathname and href properties if the URI doesn't start with "/"
+ if (preparedURI[0] != "/") {
+ if (nuobject.pathname) {
+ nuobject.pathname = nuobject.pathname.substring(1);
+ nuobject.href = nuobject.pathname + (nuobject.search ? nuobject.search : "");
+ }
}
- callback(fields, files);
+
+ // Set the path property as a combination of pathname and search
+ if (nuobject.pathname) {
+ nuobject.path = nuobject.pathname + (nuobject.search ? nuobject.search : "");
+ }
+
+ // Initialize the query object and copy URL search parameters to it
+ nuobject.query = {};
+ uobject.searchParams.forEach(function (value, key) {
+ nuobject.query[key] = value;
+ });
+
+ // Return the created URL object
+ return nuobject;
+ } catch (err) {
+ // If there was an error using the URL API, fall back to deprecated url.parse
+ return url.parse(preparedURI, true);
+ }
+ } else {
+ // If the URL API is not available, fall back to deprecated url.parse
+ return url.parse(preparedURI, true);
+ }
+ }
+
+ // Authenticated user variable
+ var authUser = null;
+
+ // URL-related objects.
+ var uobject = {}
+ try {
+ uobject = parseURL(req.url);
+ } catch (err) {
+ // Return an 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
+ }
+ var search = uobject.search;
+ var href = uobject.pathname;
+ var ext = path.extname(href).toLowerCase();
+ ext = ext.substring(1, ext.length + 1);
+ var decodedHref = "";
+ try {
+ decodedHref = decodeURIComponent(href);
+ } catch (err) {
+ // Return an 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
+ }
+
+ if (req.headers["expect"] && req.headers["expect"] != "100-continue") {
+ // Expectations not met.
+ callServerError(417);
+ return;
+ }
+
+ // Mod execution function
+ function modExecute(mods, ffinals) {
+ // Prepare modFunction
+ var modFunction = ffinals;
+ var useMods = mods.slice();
+
+ if (isProxy) {
+ // Get list of forward proxy mods
+ useMods = [];
+ mods.forEach(function (mod) {
+ if (mod.proxyCallback !== undefined) useMods.push(mod);
});
}
- function urlParse(uri) {
- if (typeof URL != "undefined" && url.Url) {
- try {
- var uobject = new URL(uri, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
- var nuobject = new url.Url();
- if (uri.indexOf("/") != -1) nuobject.slashes = true;
- if (uobject.protocol != "") nuobject.protocol = uobject.protocol;
- if (uobject.username != "" && uobject.password != "") nuobject.auth = uobject.username + ":" + uobject.password;
- if (uobject.host != "") nuobject.host = uobject.host;
- if (uobject.hostname != "") nuobject.hostname = uobject.hostname;
- if (uobject.port != "") nuobject.port = uobject.port;
- if (uobject.pathname != "") nuobject.pathname = uobject.pathname;
- if (uobject.search != "") nuobject.search = uobject.search;
- if (uobject.hash != "") nuobject.hash = uobject.hash;
- if (uobject.href != "") nuobject.href = uobject.href;
- if (uri.indexOf("/") != 0) {
- if (nuobject.pathname) {
- nuobject.pathname = nuobject.pathname.substr(1);
- nuobject.href = nuobject.pathname + (nuobject.search ? nuobject.search : "");
- }
- }
- if (nuobject.pathname) {
- nuobject.path = nuobject.pathname + (nuobject.search ? nuobject.search : "");
- }
- nuobject.query = {};
- uobject.searchParams.forEach(function (value, key) {
- nuobject.query[key] = value;
- });
- return nuobject;
- } catch (ex) {
- return url.parse(uri, true);
- }
+ useMods.reverse().forEach(function (modO) {
+ modFunction = modO.callback(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, "", modFunction, configJSON, callServerError, getCustomHeaders, origHref, redirect, parsePostData, authUser);
+ });
+
+ // Execute modFunction
+ modFunction();
+ }
+
+ var vresCalled = false;
+
+ function vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, defaultpage, users, page404, head, foot, fd, callServerError, getCustomHeaders, origHref, redirect, parsePostData, authUser) {
+ return function () {
+ if (vresCalled) {
+ process.emitWarning("elseCallback() invoked multiple times.", {
+ code: "WARN_SVRJS_MULTIPLE_ELSECALLBACK"
+ });
+ return;
} else {
- return url.parse(uri, true);
+ vresCalled = true;
}
- }
- var uobject = urlParse(req.url);
- var search = uobject.search;
- var href = uobject.pathname;
- var ext = path.extname(href).toLowerCase();
- ext = ext.substr(1, ext.length);
- var decodedHref = "";
- try {
- decodedHref = decodeURIComponent(href);
- } catch (ex) {
- //Return 400 error
- callServerError(400);
- serverconsole.errmessage("Bad request!");
- return;
- }
+ // Function to check the level of a path relative to the web root
+ function checkPathLevel(path) {
+ // Split the path into an array of components based on "/"
+ var pathComponents = path.split("/");
- if (req.headers["expect"] && req.headers["expect"] != "100-continue") {
- callServerError(417);
- return;
- }
+ // Initialize counters for level up (..) and level down (.)
+ var levelUpCount = 0;
+ var levelDownCount = 0;
- //MOD EXCECUTION FUNCTION
- function modExecute(mods, ffinals) {
- var modFunction = ffinals;
- var useMods = mods;
-
- if(isProxy) {
- useMods = [];
- for (var i = 0; i < mods.length; i++) {
- if (mods[i].proxyCallback !== undefined) proxyMods.push(mods[i]);
- }
- }
-
- for (var i = useMods.length - 1; i >= 0; i--) {
- modFunction = useMods[i].callback(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, fd, modFunction, configJSON, callServerError, getCustomHeaders, origHref, redirect, parsePostData);
- }
- modFunction();
- }
-
- var vresCalled = false;
-
- function vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, defaultpage, users, page404, head, foot, fd, callServerError, getCustomHeaders, origHref, redirect, parsePostData) {
- return function () {
- if (vresCalled) {
- process.emitWarning("elseCallback() invoked multiple times.", {
- code: "WARN_SVRJS_MULTIPLE_ELSECALLBACK"
- });
- return;
- } else {
- vresCalled = true;
- }
- // function responseEndGzip(d) {
- // if (d === undefined) d = fd;
- // zlib.gzip(head + d + foot, function (err, buff) {
- // if (err) {
- // throw err;
- // } else {
- // res.write(buff);
- // res.end();
- // }
- // });
- // }
- //
- // function responseEndDeflate(d) {
- // if (d === undefined) d = fd;
- // zlib.deflateRaw(head + d + foot, function (err, buff) {
- // if (err) {
- // throw err;
- // } else {
- // res.write(buff);
- // res.end();
- // }
- // });
- // }
-
- function responseEnd(d) {
- if (d === undefined) d = fd;
- res.write(head + d + foot);
- res.end();
- }
-
- if (req.socket == null) {
- serverconsole.errmessage("Client socket is null!!!");
- return;
- }
-
- var reqip = req.socket.remoteAddress;
- var reqport = req.socket.remotePort;
- var oldip = "";
- var oldport = "";
- var isForwardedValid = true;
- if(enableIPSpoofing) {
- if (req.headers["x-forwarded-for"] != undefined) {
- var preparedReqIP = req.headers["x-forwarded-for"].split(",")[0].replace(/ /g, "");
- var preparedReqIPvalid = net.isIP(preparedReqIP);
- if(preparedReqIPvalid) {
- if (preparedReqIPvalid == 4 && req.socket.remoteAddress && req.socket.remoteAddress.indexOf(":") > -1) preparedReqIP = "::ffff:" + preparedReqIP;
- reqip = preparedReqIP;
- reqport = null;
- try {
- oldport = req.socket.remotePort;
- oldip = req.socket.remoteAddress;
- req.socket.realRemotePort = reqport;
- req.socket.realRemoteAddress = reqip;
- req.socket.originalRemotePort = oldport;
- req.socket.originalRemoteAddress = oldip;
- res.socket.realRemotePort = reqport;
- res.socket.realRemoteAddress = reqip;
- res.socket.originalRemotePort = oldport;
- res.socket.originalRemoteAddress = oldip;
- } catch (err) {
- // Address setting failed
- }
- } else {
- isForwardedValid = false;
- }
+ // Loop through the path components
+ for (var i = 0; i < pathComponents.length; i++) {
+ // If the component is "..", decrement the levelUpCount
+ if (".." === pathComponents[i]) {
+ levelUpCount--;
+ }
+ // If the component is not "." or an empty string, increment the levelDownCount
+ else if ("." !== pathComponents[i] && "" !== pathComponents[i]) {
+ levelDownCount++;
}
}
- function checkLevel(e) {
- for (var n = e.split("/"), r = 0, t = 0; t < n.length; t += 1) ".." == n[t] ? r -= 1 : "." !== n[t] && "" !== n[t] && (r += 1);
- return r;
- }
+ // Calculate the overall level by subtracting levelUpCount from levelDownCount
+ var overallLevel = levelDownCount - levelUpCount;
- if (isProxy) {
- var eheaders = getCustomHeaders();
- eheaders["Content-Type"] = "text/html; charset=utf-8";
- res.writeHead(501, "Not implemented", eheaders);
- res.write("Proxy not implementedProxy not implemented
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.
" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(//g, ">") + "
");
- res.end();
- serverconsole.errmessage("SVR.JS doesn't support proxy without proxy mod.");
- return;
- }
+ // Return the overall level
+ return overallLevel;
+ }
- if (req.method == "OPTIONS") {
- var hdss = getCustomHeaders();
- hdss["Allow"] = "GET, POST, HEAD, OPTIONS";
- res.writeHead(204, "No Content", hdss);
- res.end();
- return;
- } else if (req.method != "GET" && req.method != "POST" && req.method != "HEAD") {
- callServerError(405);
- serverconsole.errmessage("Invaild method: " + req.method);
- return;
- }
- if (allowStatus && (href == "/svrjsstatus.svr" || (os.platform() == "win32" && href.toLowerCase() == "/svrjsstatus.svr"))) {
- function formatRelativeTime(relativeTime) {
- var days = Math.floor(relativeTime / 60 / (60 * 24));
- var dateDiff = new Date(relativeTime * 1000);
- return days + " days, " + dateDiff.getUTCHours() + " hours, " + dateDiff.getUTCMinutes() + " minutes, " + dateDiff.getUTCSeconds() + " seconds";
- }
- var hdhds = getCustomHeaders();
- hdhds["Content-Type"] = "text/html; charset=utf-8";
- res.writeHead(200, "OK", hdhds);
- res.end((head == "" ? "SVR.JS status" + (request.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "" : head.replace(//i, "SVR.JS status" + (request.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "")) + "SVR.JS status" + (request.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "
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").replace(/&/g, "&").replace(//g, ">") + "
Current time: " + new Date().toString() + "
Thread start time: " + new Date(new Date() - (process.uptime() * 1000)).toString() + "
Thread uptime: " + formatRelativeTime(Math.floor(process.uptime())) + "
OS uptime: " + formatRelativeTime(os.uptime()) + "
Total request count: " + reqcounter + "
Average request rate: " + (Math.round((reqcounter / process.uptime()) * 100) / 100) + " requests/s" + (process.memoryUsage ? ("
Memory usage of thread: " + sizify(process.memoryUsage().rss) + "B") : "") + (process.cpuUsage ? ("
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) + "%") : "") + "
Thread PID: " + process.pid + "
" + (foot == "" ? "" : foot));
- return;
- }
+ if (isProxy) {
+ var eheaders = getCustomHeaders();
+ eheaders["Content-Type"] = "text/html; charset=utf-8";
+ res.writeHead(501, http.STATUS_CODES[501], eheaders);
+ res.write("Proxy not implementedProxy not implemented
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.
" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(//g, ">") + "
");
+ res.end();
+ serverconsole.errmessage("SVR.JS doesn't support proxy without proxy mod.");
+ return;
+ }
- /////////////////////////////////////////////
- ////THERE IS NO MORE "THE BOOK OF ZSOIE"!////
- //// But it's in easteregg.tar.gz mod... ////
- /////////////////////////////////////////////
-
- var pth = decodeURIComponent(href).replace(/\/+/g, "/").substr(1);
- fs.stat("./" + pth, function (err, stats) {
- if (err) {
- if (err.code == "ENOENT") {
- callServerError(404);
- serverconsole.errmessage("Resource not found.");
- } else if (err.code == "ENOTDIR") {
- callServerError(404);
- serverconsole.errmessage("Resource not found.");
- } else if (err.code == "EACCES") {
- callServerError(403);
- serverconsole.errmessage("Access denied.");
- } else if (err.code == "ENAMETOOLONG") {
- callServerError(414);
- } else if (err.code == "EMFILE") {
- callServerError(503);
- } else if (err.code == "ELOOP") {
- callServerError(508);
+ if (req.method == "OPTIONS") {
+ var hdss = getCustomHeaders();
+ hdss["Allow"] = "GET, POST, HEAD, OPTIONS";
+ res.writeHead(204, http.STATUS_CODES[204], hdss);
+ res.end();
+ return;
+ } else if (req.method != "GET" && req.method != "POST" && req.method != "HEAD") {
+ callServerError(405);
+ serverconsole.errmessage("Invaild method: " + req.method);
+ return;
+ }
+
+ if (allowStatus && (href == "/svrjsstatus.svr" || (os.platform() == "win32" && href.toLowerCase() == "/svrjsstatus.svr"))) {
+ function formatRelativeTime(relativeTime) {
+ var days = Math.floor(relativeTime / 60 / (60 * 24));
+ var dateDiff = new Date(relativeTime * 1000);
+ return days + " days, " + dateDiff.getUTCHours() + " hours, " + dateDiff.getUTCMinutes() + " minutes, " + dateDiff.getUTCSeconds() + " seconds";
+ }
+ var statusBody = "";
+ statusBody += "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").replace(/&/g, "&").replace(//g, ">") + "
";
+
+ //Those entries are just dates and numbers converted/formatted to strings, so no escaping is needed.
+ statusBody += "Current time: " + new Date().toString() + "
Thread start time: " + new Date(new Date() - (process.uptime() * 1000)).toString() + "
Thread uptime: " + formatRelativeTime(Math.floor(process.uptime())) + "
";
+ statusBody += "OS uptime: " + formatRelativeTime(os.uptime()) + "
";
+ statusBody += "Total request count: " + reqcounter + "
";
+ statusBody += "Average request rate: " + (Math.round((reqcounter / process.uptime()) * 100) / 100) + " requests/s
";
+ statusBody += "Client errors (4xx): " + err4xxcounter + "
";
+ statusBody += "Server errors (5xx): " + err5xxcounter + "
";
+ statusBody += "Average error rate: " + (Math.round(((err4xxcounter + err5xxcounter) / reqcounter) * 10000) / 100) + "%
";
+ statusBody += "Malformed HTTP requests: " + malformedcounter;
+ if (process.memoryUsage) statusBody += "
Memory usage of thread: " + sizify(process.memoryUsage().rss, true) + "B";
+ if (process.cpuUsage) statusBody += "
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) + "%";
+ statusBody += "
Thread PID: " + process.pid + "
";
+
+ var hdhds = getCustomHeaders();
+ hdhds["Content-Type"] = "text/html; charset=utf-8";
+ res.writeHead(200, http.STATUS_CODES[200], hdhds);
+ res.end((head == "" ? "SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "" : head.replace(//i, "SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "")) + "SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "
" + statusBody + (foot == "" ? "" : foot));
+ return;
+ }
+
+ var pth = decodeURIComponent(href).replace(/\/+/g, "/").substring(1);
+ var readFrom = "./" + pth;
+ var dirImagesMissing = false;
+ fs.stat(readFrom, function (err, stats) {
+ if (err) {
+ if (err.code == "ENOENT") {
+ if (__dirname != process.cwd() && pth.match(/^\.dirimages\/(?:(?!\.png$).)+\.png$/)) {
+ dirImagesMissing = true;
+ readFrom = __dirname + "/" + pth;
} else {
- callServerError(500, undefined, generateErrorStack(err));
+ callServerError(404);
+ serverconsole.errmessage("Resource not found.");
+ return;
}
+ } else if (err.code == "ENOTDIR") {
+ callServerError(404); // Assume that file doesn't exist.
+ serverconsole.errmessage("Resource not found.");
+ return;
+ } else if (err.code == "EACCES") {
+ callServerError(403);
+ serverconsole.errmessage("Access denied.");
+ return;
+ } else if (err.code == "ENAMETOOLONG") {
+ callServerError(414);
+ return;
+ } else if (err.code == "EMFILE") {
+ callServerError(503);
+ return;
+ } else if (err.code == "ELOOP") {
+ callServerError(508); // The symbolic link loop is detected during file system operations.
+ serverconsole.errmessage("Symbolic link loop detected.");
+ return;
+ } else {
+ callServerError(500, err);
return;
}
-
- //Check if index file exists
- if (req.url == "/" || stats.isDirectory()) {
- fs.stat(("./" + pth + "/index.html").replace(/\/+/g, "/"), function (e, s) {
- if (e || !s.isFile()) {
- fs.stat(("./" + pth + "/index.htm").replace(/\/+/g, "/"), function (e, s) {
- if (e || !s.isFile()) {
- fs.stat(("./" + pth + "/index.xhtml").replace(/\/+/g, "/"), function (e, s) {
- if (e || !s.isFile()) {
- properServe()
+ }
+
+ // Check if index file exists
+ if (!dirImagesMissing && (req.url == "/" || stats.isDirectory())) {
+ fs.stat((readFrom + "/index.html").replace(/\/+/g, "/"), function (e, s) {
+ if (e || !s.isFile()) {
+ fs.stat((readFrom + "/index.htm").replace(/\/+/g, "/"), function (e, s) {
+ if (e || !s.isFile()) {
+ fs.stat((readFrom + "/index.xhtml").replace(/\/+/g, "/"), function (e, s) {
+ if (e || !s.isFile()) {
+ properDirectoryListingAndStaticFileServe();
+ } else {
+ stats = s;
+ pth = (pth + "/index.xhtml").replace(/\/+/g, "/");
+ ext = "xhtml";
+ readFrom = "./" + pth;
+ properDirectoryListingAndStaticFileServe();
+ }
+ });
+ } else {
+ stats = s;
+ pth = (pth + "/index.htm").replace(/\/+/g, "/");
+ ext = "htm";
+ readFrom = "./" + pth;
+ properDirectoryListingAndStaticFileServe();
+ }
+ });
+ } else {
+ stats = s;
+ pth = (pth + "/index.html").replace(/\/+/g, "/");
+ ext = "html";
+ readFrom = "./" + pth;
+ properDirectoryListingAndStaticFileServe();
+ }
+ });
+ } else if (dirImagesMissing) {
+ fs.stat(readFrom, function (e, s) {
+ if (e || !s.isFile()) {
+ properDirectoryListingAndStaticFileServe();
+ } else {
+ stats = s;
+ properDirectoryListingAndStaticFileServe();
+ }
+ });
+ } else {
+ properDirectoryListingAndStaticFileServe();
+ }
+
+ function properDirectoryListingAndStaticFileServe() {
+ if (stats.isFile()) {
+ var acceptEncoding = req.headers["accept-encoding"];
+ if (!acceptEncoding) acceptEncoding = "";
+
+ var filelen = stats.size;
+
+ // ETag code
+ var fileETag = undefined;
+ if (configJSON.enableETag == undefined || configJSON.enableETag) {
+ fileETag = generateETag(href, stats);
+ // Check if the client's request matches the ETag value (If-None-Match)
+ var clientETag = req.headers["if-none-match"];
+ if (clientETag === fileETag) {
+ var headers = getCustomHeaders();
+ headers.ETag = clientETag;
+ res.writeHead(304, http.STATUS_CODES[304], headers);
+ res.end();
+ return;
+ }
+
+ // Check if the client's request doesn't match the ETag value (If-Match)
+ var ifMatchETag = req.headers["if-match"];
+ if (ifMatchETag && ifMatchETag !== "*" && ifMatchETag !== fileETag) {
+ var headers = getCustomHeaders();
+ headers.ETag = clientETag;
+ callServerError(412, headers);
+ return;
+ }
+ }
+
+ // Handle partial content request
+ if (ext != "html" && req.headers["range"]) {
+ try {
+ var rhd = getCustomHeaders();
+ rhd["Accept-Ranges"] = "bytes";
+ rhd["Content-Range"] = "bytes */" + filelen;
+ var regexmatch = req.headers["range"].match(/bytes=([0-9]*)-([0-9]*)/);
+ if (!regexmatch) {
+ callServerError(416, rhd);
+ } else {
+ // Process the partial content request
+ var beginOrig = regexmatch[1];
+ var endOrig = regexmatch[2];
+ var begin = 0;
+ var end = filelen - 1;
+ if (beginOrig == "" && endOrig == "") {
+ callServerError(416, rhd);
+ return;
+ } else if (beginOrig == "") {
+ begin = end - parseInt(endOrig) + 1;
+ } else {
+ begin = parseInt(beginOrig);
+ if (endOrig != "") end = parseInt(endOrig);
+ }
+ if (begin > end || begin < 0 || begin > filelen - 1) {
+ callServerError(416, rhd);
+ return;
+ }
+ if (end > filelen - 1) end = filelen - 1;
+ rhd["Content-Range"] = "bytes " + begin + "-" + end + "/" + filelen;
+ rhd["Content-Length"] = end - begin + 1;
+ if (!(mime.contentType(ext) == false) && ext != "") rhd["Content-Type"] = mime.contentType(ext);
+ if (fileETag) rhd["ETag"] = fileETag;
+
+ if (req.method != "HEAD") {
+ var readStream = fs.createReadStream(readFrom, {
+ start: begin,
+ end: end
+ });
+ readStream.on("error", function (err) {
+ if (err.code == "ENOENT") {
+ callServerError(404);
+ serverconsole.errmessage("Resource not found.");
+ } else if (err.code == "ENOTDIR") {
+ callServerError(404); // Assume that file doesn't exist.
+ serverconsole.errmessage("Resource not found.");
+ } else if (err.code == "EACCES") {
+ callServerError(403);
+ serverconsole.errmessage("Access denied.");
+ } else if (err.code == "ENAMETOOLONG") {
+ callServerError(414);
+ } else if (err.code == "EMFILE") {
+ callServerError(503);
+ } else if (err.code == "ELOOP") {
+ callServerError(508); // The symbolic link loop is detected during file system operations.
+ serverconsole.errmessage("Symbolic link loop detected.");
} else {
- stats = s;
- pth = (pth + "/index.xhtml").replace(/\/+/g, "/");
- ext = "xhtml";
- properServe();
+ callServerError(500, err);
+ }
+ }).on("open", function () {
+ try {
+ res.writeHead(206, http.STATUS_CODES[206], rhd);
+ readStream.pipe(res);
+ serverconsole.resmessage("Client successfully received content.");
+ } catch (err) {
+ callServerError(500, err);
}
});
} else {
- stats = s;
- pth = (pth + "/index.htm").replace(/\/+/g, "/");
- ext = "htm";
- properServe();
+ res.writeHead(206, http.STATUS_CODES[206], rhd);
+ res.end();
}
- });
- } else {
- stats = s;
- pth = (pth + "/index.html").replace(/\/+/g, "/");
- ext = "html";
- properServe();
- }
- });
- } else {
- properServe();
- }
-
- function properServe() {
- if (stats.isDirectory()) {
- if (configJSON.enableDirectoryListing || configJSON.enableDirectoryListing === undefined) {
- var dheaders = getCustomHeaders();
- dheaders["Content-Type"] = "text/html; charset=utf-8";
- res.writeHead(200, http.STATUS_CODES[200], dheaders);
- var heada = fs.existsSync(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/")) ? fs.readFileSync(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/")).toString() : ((fs.existsSync(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/")) && (os.platform != "win32" || href != "/")) ? fs.readFileSync(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/")).toString() : ""); // header
- var foota = fs.existsSync(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/")) ? fs.readFileSync(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/")).toString() : ((fs.existsSync(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/")) && (os.platform != "win32" || href != "/")) ? fs.readFileSync(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/")).toString() : ""); // footer
- //Check if header has HTML tag
- var headerHasHTMLTag = heada.replace(/|$)/gs, "").match(/])*(?:>|$)/si);
- var htmlHead = (configJSON.enableDirectoryListingWithDefaultHead == undefined || configJSON.enableDirectoryListingWithDefaultHead == false || (fs.existsSync("./head.html") == false && fs.existsSync("./.head") == false) || head == "" || head == " " || head == "\r\n" || head == "\n" ? (!headerHasHTMLTag ? "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(//g, ">") + "" : heada.replace(//i, "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(//g, ">") + "")) : head.replace(//i, "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(//g, ">") + "")) + (!headerHasHTMLTag ? heada : "") + "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(//g, ">") + "
| Filename | Size | Date |
" + (checkLevel(decodeURIComponent(origHref)) < 1 ? "" : " | Return | | |
");
- var htmlFoot = "
" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(//g, ">") + (req.headers.host == undefined ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "
" + foota + (configJSON.enableDirectoryListingWithDefaultHead == undefined || configJSON.enableDirectoryListingWithDefaultHead == false || (fs.existsSync("./foot.html") == false && fs.existsSync("./.foot") == false) || foot == "" || foot == " " || foot == "\r\n" || foot == "\n" ? "" : foot);
- if (fs.existsSync(("." + req.url.split("?")[0] + "/.maindesc").replace(/\/+/g, "/"))) {
- htmlFoot = "
" + fs.readFileSync(("." + req.url.split("?")[0] + "/.maindesc").replace(/\/+/g, "/")) + htmlFoot;
}
- fs.readdir("." + decodeURIComponent(href), function (err, list) {
- try {
- if (err) throw err;
- list = list.sort();
+ } catch (err) {
+ callServerError(500, err);
+ }
+ } else {
+ // Helper function to check if compression is allowed for the file
+ function canCompress(path, list) {
+ var canCompress = true;
+ for (var i = 0; i < list.length; i++) {
+ if (createRegex(list[i], true).test(path)) {
+ canCompress = false;
+ break;
+ }
+ }
+ return canCompress;
+ }
- function getStatsForAllFilesI(fileList, callback, prefix, pushArray, index) {
- if (fileList.length == 0) {
- callback(pushArray);
- return;
- }
- fs.stat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) {
- if (err) {
- fs.lstat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) {
- if (err) {
- pushArray.push({
- name: fileList[index],
- stats: null,
- errored: true
- });
- if (index < fileList.length - 1) {
- getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
- } else {
- callback(pushArray);
- }
- } else {
- pushArray.push({
- name: fileList[index],
- stats: stats,
- errored: true
- });
- if (index < fileList.length - 1) {
- getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
- } else {
- callback(pushArray);
- }
- }
- });
- } else {
- pushArray.push({
- name: fileList[index],
- stats: stats,
- errored: false
- });
- if (index < fileList.length - 1) {
- getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
- } else {
- callback(pushArray);
- }
- }
- });
- }
+ var useBrotli = (ext != "br" && filelen > 256 && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/));
+ var useDeflate = (ext != "zip" && filelen > 256 && acceptEncoding.match(/\bdeflate\b/));
+ var useGzip = (ext != "gz" && filelen > 256 && acceptEncoding.match(/\bgzip\b/));
- function getStatsForAllFiles(fileList, prefix, callback) {
- if (!prefix) prefix = "";
- getStatsForAllFilesI(fileList, callback, prefix, [], 0);
- }
+ var isCompressable = true;
+ try {
+ // Check for files not to compressed and compression enabling setting. Also check for browser quirks and adjust compression accordingly
+ if((!useBrotli && !useDeflate && !useGzip) || configJSON.enableCompression !== true || !canCompress(href, dontCompress)) {
+ isCompressable = false; // Compression is disabled
+ } else if (ext != "html" && ext != "htm" && ext != "xhtml" && ext != "xht" && ext != "shtml") {
+ if (/^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]|MSIE/.test(req.headers["user-agent"]))) {
+ isCompressable = false; // Netscape 4.x doesn't handle compressed data properly outside of HTML documents.
+ } else if (/^w3m\/[^ ]*$/.test(req.headers["user-agent"])) {
+ isCompressable = false; // w3m doesn't handle compressed data properly outside of HTML documents.
+ }
+ } else {
+ if (/^Mozilla\/4\.0[6-8](( *\[[^)]*\] *| *)\([^)\]]*\))? *$/.test(req.headers["user-agent"]) && !(/https?:\/\/|[bB][oO][tT]|[sS][pP][iI][dD][eE][rR]|[sS][uU][rR][vV][eE][yY]|MSIE/.test(req.headers["user-agent"]))) {
+ isCompressable = false; // Netscape 4.06-4.08 doesn't handle compressed data properly.
+ }
+ }
+ } catch (err) {
+ callServerError(500, err);
+ return;
+ }
- getStatsForAllFiles(list, "." + decodeURIComponent(href), function (filelist) {
- function checkEXT(filename, ext) {
- if (filename.length < ext.length) return false;
- return (filename.toLowerCase().indexOf(ext) == (filename.length - ext.length));
- }
- var statsa = [];
- for (var i = 0; i < filelist.length; i++) {
- if (filelist[i].name[0] !== ".") {
- var estats = filelist[i].stats;
- var ename = filelist[i].name;
- if (filelist[i].errored) {
- if (estats) {
- statsa.push(" | " + ename.replace(/&/g, "&").replace(//g, ">") + " | - | " + estats.mtime.toDateString() + " |
\r\n");
- } else {
- statsa.push(" | " + ename.replace(/&/g, "&").replace(//g, ">") + " | - | - |
\r\n");
- }
- } else {
- var entry = " | " + ename.replace(/&/g, "&").replace(//g, ">") + " | " + (estats.isDirectory() ? "-" : sizify(estats.size.toString())) + " | " + estats.mtime.toDateString() + " |
\r\n";
- if (estats.isDirectory()) {
- entry = entry.replace("[img]", "/.dirimages/directory.png").replace("[alt]", "[DIR]");
- } else if (!estats.isFile()) {
- entry = " | " + ename.replace(/&/g, "&").replace(//g, ">") + " | - | " + estats.mtime.toDateString() + " |
\r\n";
- if (estats.isBlockDevice()) {
- entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[BLK]");
- } else if (estats.isCharacterDevice()) {
- entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[CHR]");
- } else if (estats.isFIFO()) {
- entry = entry.replace("[img]", "/.dirimages/fifo.png").replace("[alt]", "[FIF]");
- } else if (estats.isSocket()) {
- entry = entry.replace("[img]", "/.dirimages/socket.png").replace("[alt]", "[SCK]");
- }
- } else if ((/README/ig).test(ename) || (/LICEN[SC]E/ig).test(ename)) {
- entry = entry.replace("[img]", "/.dirimages/important.png").replace("[alt]", "[IMP]");
- } else if (checkEXT(ename, ".html") || checkEXT(ename, ".htm") || checkEXT(ename, ".xml") || checkEXT(ename, ".xhtml") || checkEXT(ename, ".shtml")) {
- entry = entry.replace("[img]", "/.dirimages/html.png").replace("[alt]", (checkEXT(ename, ".xml") ? "[XML]" : "[HTM]"));
- } else if (checkEXT(ename, ".js")) {
- entry = entry.replace("[img]", "/.dirimages/javascript.png").replace("[alt]", "[JS ]");
- } else if (checkEXT(ename, ".php")) {
- entry = entry.replace("[img]", "/.dirimages/php.png").replace("[alt]", "[PHP]");
- } else if (checkEXT(ename, ".css")) {
- entry = entry.replace("[img]", "/.dirimages/css.png").replace("[alt]", "[CSS]");
- } else if (checkEXT(ename, ".png") || checkEXT(ename, ".jpg") || checkEXT(ename, ".gif") || checkEXT(ename, ".bmp") || checkEXT(ename, ".webm") || checkEXT(ename, ".jpeg") || checkEXT(ename, ".svg") || checkEXT(ename, ".jfif") || checkEXT(ename, ".webp")) {
- entry = entry.replace("[img]", "/.dirimages/image.png").replace("[alt]", "[IMG]");
- } else if (checkEXT(ename, ".ico") || checkEXT(ename, ".icn")) {
- entry = entry.replace("[img]", "/.dirimages/image.png").replace("[alt]", "[ICO]");
- } else if (checkEXT(ename, ".ttf") || checkEXT(ename, ".otf") || checkEXT(ename, ".fon")) {
- entry = entry.replace("[img]", "/.dirimages/font.png").replace("[alt]", "[FON]");
- } else if (checkEXT(ename, ".mp3") || checkEXT(ename, ".ogg") || checkEXT(ename, ".aac") || checkEXT(ename, ".wav") || checkEXT(ename, ".mid") || checkEXT(ename, ".midi") || checkEXT(ename, ".mka")) {
- entry = entry.replace("[img]", "/.dirimages/audio.png").replace("[alt]", "[AUD]");
- } else if (checkEXT(ename, ".txt") || checkEXT(ename, ".log") || checkEXT(ename, ".json")) {
- entry = entry.replace("[img]", "/.dirimages/text.png").replace("[alt]", (checkEXT(ename, ".json") ? "[JSO]" : "[TXT]"));
- } else if (checkEXT(ename, ".mp5") || checkEXT(ename, ".avi") || checkEXT(ename, ".mkv") || checkEXT(ename, ".mov") || checkEXT(ename, ".mp2") || checkEXT(ename, ".mp4") || checkEXT(ename, ".ogv")) {
- entry = entry.replace("[img]", "/.dirimages/video.png").replace("[alt]", "[VID]");
- } else if (checkEXT(ename, ".zip") || checkEXT(ename, ".rar") || checkEXT(ename, ".bz2") || checkEXT(ename, ".gz") || checkEXT(ename, ".bz") || checkEXT(ename, ".7z") || checkEXT(ename, ".xz") || checkEXT(ename, ".lzma") || checkEXT(ename, ".tar")) {
- entry = entry.replace("[img]", "/.dirimages/archive.png").replace("[alt]", "[ARC]");
- } else if (checkEXT(ename, ".img") || checkEXT(ename, ".dmg") || checkEXT(ename, ".iso") || checkEXT(ename, ".flp")) {
- entry = entry.replace("[img]", "/.dirimages/diskimage.png").replace("[alt]", "[DSK]");
- } else {
- entry = entry.replace("[img]", "/.dirimages/other.png").replace("[alt]", "[OTH]");
- }
- statsa.push(entry);
- }
- }
- }
- if (statsa.length == 0) {
- statsa.push(" | No files found | | |
");
- }
- res.end(htmlHead + statsa.join("") + htmlFoot);
- serverconsole.resmessage("Client successfully recieved content.");
- });
- } catch (ex) {
- if (ex.code == "ENOENT") {
+ // Bun 1.1 has definition for zlib.createBrotliCompress, but throws an error while invoking the function.
+ if (process.isBun && useBrotli && isCompressable) {
+ try {
+ zlib.createBrotliCompress();
+ } catch (err) {
+ useBrotli = false;
+ }
+ }
+
+ try {
+ var hdhds = getCustomHeaders();
+ if (useBrotli && isCompressable) {
+ hdhds["Content-Encoding"] = "br";
+ } else if (useDeflate && isCompressable) {
+ hdhds["Content-Encoding"] = "deflate";
+ } else if (useGzip && isCompressable) {
+ hdhds["Content-Encoding"] = "gzip";
+ } else {
+ if (ext == "html") {
+ hdhds["Content-Length"] = head.length + filelen + foot.length;
+ } else {
+ hdhds["Content-Length"] = filelen;
+ }
+ }
+ if (ext != "html") hdhds["Accept-Ranges"] = "bytes";
+ delete hdhds["Content-Type"];
+ if (!(mime.contentType(ext) == false) && ext != "") hdhds["Content-Type"] = mime.contentType(ext);
+ if (fileETag) hdhds["ETag"] = fileETag;
+
+ if (req.method != "HEAD") {
+ var readStream = fs.createReadStream(readFrom);
+ readStream.on("error", function (err) {
+ if (err.code == "ENOENT") {
callServerError(404);
serverconsole.errmessage("Resource not found.");
- } else if (ex.code == "ENOTDIR") {
- callServerError(404);
+ } else if (err.code == "ENOTDIR") {
+ callServerError(404); // Assume that file doesn't exist.
serverconsole.errmessage("Resource not found.");
- } else if (ex.code == "EACCES") {
+ } else if (err.code == "EACCES") {
callServerError(403);
serverconsole.errmessage("Access denied.");
} else if (err.code == "ENAMETOOLONG") {
callServerError(414);
} else if (err.code == "EMFILE") {
callServerError(503);
- } else if (ex.code == "ELOOP") {
- callServerError(508);
+ } else if (err.code == "ELOOP") {
+ callServerError(508); // The symbolic link loop is detected during file system operations.
+ serverconsole.errmessage("Symbolic link loop detected.");
} else {
- callServerError(500, undefined, generateErrorStack(ex));
+ callServerError(500, err);
}
- }
- });
- } else {
- callServerError(403);
- serverconsole.errmessage("Directory listing disabled.");
- return;
+ }).on("open", function () {
+ try {
+ var resStream = {};
+ if (useBrotli && isCompressable) {
+ resStream = zlib.createBrotliCompress();
+ resStream.pipe(res);
+ } else if (useDeflate && isCompressable) {
+ resStream = zlib.createDeflateRaw();
+ resStream.pipe(res);
+ } else if (useGzip && isCompressable) {
+ resStream = zlib.createGzip();
+ resStream.pipe(res);
+ } else {
+ resStream = res;
+ }
+ if (ext == "html") {
+ function afterWriteCallback() {
+ readStream.on("end", function () {
+ resStream.end(foot);
+ });
+ readStream.pipe(resStream, {
+ end: false
+ });
+ }
+ res.writeHead(200, http.STATUS_CODES[200], hdhds);
+ if (!resStream.write(head)) {
+ resStream.on("drain", afterWriteCallback);
+ } else {
+ process.nextTick(afterWriteCallback);
+ }
+ } else {
+ res.writeHead(200, http.STATUS_CODES[200], hdhds);
+ readStream.pipe(resStream);
+ }
+ serverconsole.resmessage("Client successfully received content.");
+ } catch (err) {
+ callServerError(500, err);
+ }
+ });
+ } else {
+ res.writeHead(200, http.STATUS_CODES[200], hdhds);
+ res.end();
+ serverconsole.resmessage("Client successfully received content.");
+ }
+ } catch (err) {
+ callServerError(500, err);
}
- } else {
- var acceptEncoding = req.headers["accept-encoding"];
- if (!acceptEncoding) acceptEncoding = "";
+ }
+ } else if (stats.isDirectory()) {
+ // Check if directory listing is enabled in the configuration
+ if (checkForEnabledDirectoryListing(req.headers.host, req.socket ? req.socket.localAddress : undefined)) {
+ var customHeaders = getCustomHeaders();
+ customHeaders["Content-Type"] = "text/html; charset=utf-8";
+ res.writeHead(200, http.STATUS_CODES[200], customHeaders);
- // Check if the requested file exists and handle errors
- fs.stat("./" + pth, function (err, stats) {
- if (err) {
+ // Read custom header and footer content (if available)
+ var customDirListingHeader = fs.existsSync(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/")) ?
+ fs.readFileSync(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/")).toString() :
+ (fs.existsSync(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/")) && (os.platform != "win32" || href != "/")) ?
+ fs.readFileSync(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/")).toString() :
+ "";
+ var customDirListingFooter = fs.existsSync(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/")) ?
+ fs.readFileSync(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/")).toString() :
+ (fs.existsSync(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/")) && (os.platform != "win32" || href != "/")) ?
+ fs.readFileSync(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/")).toString() :
+ "";
+
+ // Check if custom header has HTML tag
+ var headerHasHTMLTag = customDirListingHeader.replace(/|$)/g, "").match(/])*(?:>|$)/i);
+
+ // Generate HTML head and footer based on configuration and custom content
+ var htmlHead = (!configJSON.enableDirectoryListingWithDefaultHead || head == "" ?
+ (!headerHasHTMLTag ?
+ "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(//g, ">") + "" :
+ customDirListingHeader.replace(//i, "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(//g, ">") + "")) :
+ head.replace(//i, "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(//g, ">") + "")) +
+ (!headerHasHTMLTag ? customDirListingHeader : "") +
+ "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(//g, ">") + "
| Filename | Size | Date |
" + (checkPathLevel(decodeURIComponent(origHref)) < 1 ? "" : " | Return | | |
");
+
+ var htmlFoot = "
" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(//g, ">") + (req.headers.host == undefined ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "
" + customDirListingFooter + (!configJSON.enableDirectoryListingWithDefaultHead || foot == "" ? "" : foot);
+
+ if (fs.existsSync("." + decodeURIComponent(href) + "/.maindesc".replace(/\/+/g, "/"))) {
+ htmlFoot = "
" + fs.readFileSync("." + decodeURIComponent(href) + "/.maindesc".replace(/\/+/g, "/")) + htmlFoot;
+ }
+
+ fs.readdir("." + decodeURIComponent(href), function (err, list) {
+ try {
+ if (err) throw err;
+ list = list.sort();
+
+ // Function to get stats for all files in the directory
+ function getStatsForAllFilesI(fileList, callback, prefix, pushArray, index) {
+ if (fileList.length == 0) {
+ callback(pushArray);
+ return;
+ }
+
+ fs.stat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) {
+ if (err) {
+ fs.lstat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) {
+ pushArray.push({
+ name: fileList[index],
+ stats: err ? null : stats,
+ errored: true
+ });
+ if (index < fileList.length - 1) {
+ getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
+ } else {
+ callback(pushArray);
+ }
+ });
+ } else {
+ pushArray.push({
+ name: fileList[index],
+ stats: stats,
+ errored: false
+ });
+ if (index < fileList.length - 1) {
+ getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
+ } else {
+ callback(pushArray);
+ }
+ }
+ });
+ }
+
+ // Wrapper function to get stats for all files
+ function getStatsForAllFiles(fileList, prefix, callback) {
+ if (!prefix) prefix = "";
+ getStatsForAllFilesI(fileList, callback, prefix, [], 0);
+ }
+
+ // Get stats for all files in the directory and generate the listing
+ getStatsForAllFiles(list, "." + decodeURIComponent(href), function (filelist) {
+ var directoryListingRows = [];
+ for (var i = 0; i < filelist.length; i++) {
+ if (filelist[i].name[0] !== ".") {
+ var estats = filelist[i].stats;
+ var ename = filelist[i].name;
+ var eext = ename.match(/\.([^.]+)$/);
+ eext = eext ? eext[1] : "";
+ var emime = eext ? mime.contentType(eext) : false;
+ if (filelist[i].errored) {
+ directoryListingRows.push(
+ " | " +
+ ename.replace(/&/g, "&").replace(//g, ">") +
+ " | - | " +
+ (estats ? estats.mtime.toDateString() : "-") +
+ " |
\r\n"
+ );
+ } else {
+ var entry = " | " +
+ ename.replace(/&/g, "&").replace(//g, ">") +
+ " | " +
+ (estats.isDirectory() ? "-" : sizify(estats.size.toString())) +
+ " | " +
+ estats.mtime.toDateString() +
+ " |
\r\n";
+
+ // Determine the file type and set the appropriate image and alt text
+ if (estats.isDirectory()) {
+ entry = entry.replace("[img]", "/.dirimages/directory.png").replace("[alt]", "[DIR]");
+ } else if (!estats.isFile()) {
+ entry = " | " +
+ ename.replace(/&/g, "&").replace(//g, ">") +
+ " | - | " +
+ estats.mtime.toDateString() +
+ " |
\r\n";
+
+ // Determine the special file types (block device, character device, etc.)
+ if (estats.isBlockDevice()) {
+ entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[BLK]");
+ } else if (estats.isCharacterDevice()) {
+ entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[CHR]");
+ } else if (estats.isFIFO()) {
+ entry = entry.replace("[img]", "/.dirimages/fifo.png").replace("[alt]", "[FIF]");
+ } else if (estats.isSocket()) {
+ entry = entry.replace("[img]", "/.dirimages/socket.png").replace("[alt]", "[SCK]");
+ }
+ } else if (ename.match(/README|LICEN[SC]E/i)) {
+ entry = entry.replace("[img]", "/.dirimages/important.png").replace("[alt]", "[IMP]");
+ } else if (eext.match(/^(?:[xs]?html?|xml)$/i)) {
+ entry = entry.replace("[img]", "/.dirimages/html.png").replace("[alt]", (eext == "xml" ? "[XML]" : "[HTM]"));
+ } else if (eext == "js") {
+ entry = entry.replace("[img]", "/.dirimages/javascript.png").replace("[alt]", "[JS ]");
+ } else if (eext == "php") {
+ entry = entry.replace("[img]", "/.dirimages/php.png").replace("[alt]", "[PHP]");
+ } else if (eext == "css") {
+ entry = entry.replace("[img]", "/.dirimages/css.png").replace("[alt]", "[CSS]");
+ } else if (emime && emime.split("/")[0] == "image") {
+ entry = entry.replace("[img]", "/.dirimages/image.png").replace("[alt]", (eext == "ico" ? "[ICO]" : "[IMG]"));
+ } else if (emime && emime.split("/")[0] == "font") {
+ entry = entry.replace("[img]", "/.dirimages/font.png").replace("[alt]", "[FON]");
+ } else if (emime && emime.split("/")[0] == "audio") {
+ entry = entry.replace("[img]", "/.dirimages/audio.png").replace("[alt]", "[AUD]");
+ } else if ((emime && emime.split("/")[0] == "text") || eext == "json") {
+ entry = entry.replace("[img]", "/.dirimages/text.png").replace("[alt]", (eext == "json" ? "[JSO]" : "[TXT]"));
+ } else if (emime && emime.split("/")[0] == "video") {
+ entry = entry.replace("[img]", "/.dirimages/video.png").replace("[alt]", "[VID]");
+ } else if (eext.match(/^(?:zip|rar|bz2|[gb7x]z|lzma|tar)$/i)) {
+ entry = entry.replace("[img]", "/.dirimages/archive.png").replace("[alt]", "[ARC]");
+ } else if (eext.match(/^(?:[id]mg|iso|flp)$/i)) {
+ entry = entry.replace("[img]", "/.dirimages/diskimage.png").replace("[alt]", "[DSK]");
+ } else {
+ entry = entry.replace("[img]", "/.dirimages/other.png").replace("[alt]", "[OTH]");
+ }
+ directoryListingRows.push(entry);
+ }
+ }
+ }
+
+ // Push the information about empty directory
+ if (directoryListingRows.length == 0) {
+ directoryListingRows.push(" | No files found | | |
");
+ }
+
+ // Send the directory listing response
+ res.end(htmlHead + directoryListingRows.join("") + htmlFoot);
+ serverconsole.resmessage("Client successfully received content.");
+ });
+
+ } catch (err) {
if (err.code == "ENOENT") {
callServerError(404);
serverconsole.errmessage("Resource not found.");
} else if (err.code == "ENOTDIR") {
- callServerError(404);
+ callServerError(404); // Assume that file doesn't exist.
serverconsole.errmessage("Resource not found.");
} else if (err.code == "EACCES") {
callServerError(403);
@@ -3645,384 +4046,443 @@ if (!cluster.isPrimary) {
} else if (err.code == "EMFILE") {
callServerError(503);
} else if (err.code == "ELOOP") {
- callServerError(508);
+ callServerError(508); // The symbolic link loop is detected during file system operations.
+ serverconsole.errmessage("Symbolic link loop detected.");
} else {
- callServerError(500, undefined, generateErrorStack(err));
- }
- return;
- }
-
- // Check if the requested resource is a directory
- if (stats.isDirectory()) {
- callServerError(501);
- serverconsole.errmessage("SVR.JS expected file but got directory instead.");
- return;
- } else if (!stats.isFile()) {
- callServerError(501);
- serverconsole.errmessage("SVR.JS doesn't support block devices, character devices, FIFOs nor sockets.");
- return;
- }
-
- var filelen = stats.size;
-
- // Helper function to check if compression is allowed for the file
- function canCompress(path, list) {
- var canCompress = true;
- for (var i = 0; i < list.length; i++) {
- if (createRegex(list[i]).test(path)) {
- canCompress = false;
- break;
- }
- }
- return canCompress;
- }
-
- var isCompressable = true;
- try {
- isCompressable = canCompress(href, dontCompress);
- } catch(err) {
- callServerError(500, undefined, generateErrorStack(err));
- return;
- }
-
- // 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"]))) {
- isCompressable = false; //Netscape 4.x doesn't handle compressed data properly outside of HTML documents.
- } else if (/^Mozilla\/4\.0[6-8](( *\[[^)]*\] *| *)\([^)\]]*\))? *$/.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"]))) {
- isCompressable = false; //Netscape 4.06-4.08 doesn't handle compressed data properly.
- } else if (ext != "html" && ext != "htm" && ext != "xhtml" && ext != "xht" && ext != "shtml" && /^w3m\/[^ ]*$/.test(req.headers["user-agent"])) {
- isCompressable = false; //w3m doesn't handle compressed data properly outside of HTML documents.
- }
-
- // Handle partial content request
- if (ext != "html" && req.headers["range"]) {
- try {
- if (err) throw err;
- var rhd = getCustomHeaders();
- rhd["Accept-Ranges"] = "bytes";
- rhd["Content-Range"] = "bytes */" + filelen;
- var regexmatch = req.headers["range"].match(/bytes=([0-9]*)-([0-9]*)/);
- if (!regexmatch) {
- callServerError(416, undefined, undefined, rhd);
- } else {
- // Process the partial content request
- var beginOrig = regexmatch[1];
- var endOrig = regexmatch[2];
- var begin = 0;
- var end = filelen - 1;
- if (beginOrig == "" && endOrig == "") {
- callServerError(416, undefined, undefined, rhd);
- return;
- } else if (beginOrig == "") {
- begin = end - parseInt(endOrig) + 1;
- } else {
- begin = parseInt(beginOrig);
- if (endOrig != "") end = parseInt(endOrig);
- }
- if (begin > end || begin < 0 || begin > filelen - 1) {
- callServerError(416, undefined, undefined, rhd);
- return;
- }
- if (end > filelen - 1) end = filelen - 1;
- rhd["Content-Range"] = "bytes " + begin + "-" + end + "/" + filelen;
- rhd["Content-Length"] = end - begin + 1;
- if (!(mime.contentType(ext) == false) && ext != "") rhd["Content-Type"] = mime.contentType(ext);
-
- if (req.method != "HEAD") {
- var readStream = fs.createReadStream("./" + pth, {
- start: begin,
- end: end
- });
- readStream.on("error", function (err) {
- if (err.code == "ENOENT") {
- callServerError(404);
- serverconsole.errmessage("Resource not found.");
- } else if (err.code == "ENOTDIR") {
- callServerError(404);
- serverconsole.errmessage("Resource not found.");
- } else if (err.code == "EACCES") {
- callServerError(403);
- serverconsole.errmessage("Access denied.");
- } else if (err.code == "ENAMETOOLONG") {
- callServerError(414);
- } else if (err.code == "EMFILE") {
- callServerError(503);
- } else if (err.code == "ELOOP") {
- callServerError(508);
- } else {
- callServerError(500, undefined, generateErrorStack(err));
- }
- }).on("open", function () {
- try {
- res.writeHead(206, http.STATUS_CODES[206], rhd);
- readStream.pipe(res);
- serverconsole.resmessage("Client successfully received content.");
- } catch (ex) {
- callServerError(500, undefined, generateErrorStack(ex));
- }
- });
- } else {
- res.writeHead(206, http.STATUS_CODES[206], rhd);
- res.end();
- }
- }
- } catch (ex) {
- callServerError(500, undefined, generateErrorStack(ex));
- }
- } else {
- try {
- if (err) throw err;
- var hdhds = getCustomHeaders();
- if (configJSON.enableCompression === true && ext != "br" && filelen > 256 && isCompressable && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/)) {
- hdhds["Content-Encoding"] = "br";
- } else if (configJSON.enableCompression === true && ext != "zip" && filelen > 256 && isCompressable && acceptEncoding.match(/\bdeflate\b/)) {
- hdhds["Content-Encoding"] = "deflate";
- } else if (configJSON.enableCompression === true && ext != "gz" && filelen > 256 && isCompressable && acceptEncoding.match(/\bgzip\b/)) {
- hdhds["Content-Encoding"] = "gzip";
- } else {
- if (ext == "html") {
- hdhds["Content-Length"] = head.length + filelen + foot.length;
- } else {
- hdhds["Content-Length"] = filelen;
- }
- }
- if (ext != "html") hdhds["Accept-Ranges"] = "bytes";
- delete hdhds["Content-Type"];
- if (!(mime.contentType(ext) == false) && ext != "") hdhds["Content-Type"] = mime.contentType(ext);
-
- if (req.method != "HEAD") {
- var readStream = fs.createReadStream("./" + pth);
- readStream.on("error", function (ex) {
- if (ex.code == "ENOENT") {
- callServerError(404);
- serverconsole.errmessage("Resource not found.");
- } else if (ex.code == "ENOTDIR") {
- callServerError(404);
- serverconsole.errmessage("Resource not found.");
- } else if (ex.code == "EACCES") {
- callServerError(403);
- serverconsole.errmessage("Access denied.");
- } else if (err.code == "ENAMETOOLONG") {
- callServerError(414);
- } else if (err.code == "EMFILE") {
- callServerError(503);
- } else if (ex.code == "ELOOP") {
- callServerError(508);
- } else {
- callServerError(500, undefined, generateErrorStack(ex));
- }
- }).on("open", function () {
- try {
- res.writeHead(200, http.STATUS_CODES[200], hdhds);
- var resStream = {};
- if (configJSON.enableCompression === true && ext != "br" && filelen > 256 && isCompressable && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/)) {
- resStream = zlib.createBrotliCompress();
- resStream.pipe(res);
- } else if (configJSON.enableCompression === true && ext != "zip" && filelen > 256 && isCompressable && acceptEncoding.match(/\bdeflate\b/)) {
- resStream = zlib.createDeflateRaw();
- resStream.pipe(res);
- } else if (configJSON.enableCompression === true && ext != "gz" && filelen > 256 && isCompressable && acceptEncoding.match(/\bgzip\b/)) {
- resStream = zlib.createGzip();
- resStream.pipe(res);
- } else {
- resStream = res;
- }
- if (ext == "html") {
- function afterWriteCallback() {
- readStream.on("end", function () {
- resStream.end(foot);
- });
- readStream.pipe(resStream, {
- end: false
- });
- }
- if (!resStream.write(head)) {
- resStream.on("drain", afterWriteCallback);
- } else {
- process.nextTick(afterWriteCallback);
- }
- } else {
- readStream.pipe(resStream);
- }
- serverconsole.resmessage("Client successfully received content.");
- } catch (ex) {
- callServerError(500, undefined, generateErrorStack(ex));
- }
- });
- } else {
- res.writeHead(200, http.STATUS_CODES[200], hdhds);
- res.end();
- serverconsole.resmessage("Client successfully received content.");
- }
- } catch (ex) {
- callServerError(500, undefined, generateErrorStack(ex));
+ callServerError(500, err);
}
}
});
+ } else {
+ // Directory listing is disabled, call 403 Forbidden error
+ callServerError(403);
+ serverconsole.errmessage("Directory listing is disabled.");
}
+ } else {
+ callServerError(501);
+ serverconsole.errmessage("SVR.JS doesn't support block devices, character devices, FIFOs nor sockets.");
+ return;
}
- });
- };
- }
- // }
-
- try {
-
- //scan blacklist
- if (blacklist.check(reqip) && href != "/favicon.ico") {
- var bheaders = getCustomHeaders();
- bheaders["Content-Type"] = "text/html; charset=utf8";
- response.writeHead(403, "Client blocked", bheaders);
- response.write("Access denied - SVR.JS
ACCESS DENIED
Request from " + reqip + " is denied. The client is now in the blacklist.
SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" + (req.headers.host == undefined ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "
");
- serverconsole.errmessage("Client blocked");
- response.end();
- return;
- }
-
- if (req.url == "*") {
- if (req.method == "OPTIONS") {
- var hdss = getCustomHeaders();
- hdss["Allow"] = "GET, POST, HEAD, OPTIONS";
- res.writeHead(204, "No Content", hdss);
- res.end();
- return;
- } else {
- callServerError(400);
- return;
}
- }
+ });
+ };
+ }
- if (req.method == "CONNECT") {
- callServerError(501);
- serverconsole.errmessage("CONNECT requests aren't supported. Your JS runtime probably doesn't support 'connect' handler for HTTP library.");
+ try {
+ // Scan the block list
+ if (blocklist.check(reqip)) {
+ // Invoke 403 Forbidden error
+ callServerError(403);
+ serverconsole.errmessage("Client is in the block list.");
+ return;
+ }
+
+ if (req.url == "*") {
+ // Handle "*" URL
+ if (req.method == "OPTIONS") {
+ // Respond with list of methods
+ var hdss = getCustomHeaders();
+ hdss["Allow"] = "GET, POST, HEAD, OPTIONS";
+ res.writeHead(204, http.STATUS_CODES[204], hdss);
+ res.end();
return;
- }
-
- if(!isForwardedValid) {
- serverconsole.errmessage("X-Forwarded-For header is invalid.");
+ } else {
+ // SVR.JS doesn't understand that request, so throw an 400 error
callServerError(400);
return;
}
-
- //SANITIZE URL
- var sanitizedHref = sanitizeURL(href);
- var preparedReqUrl = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
+ }
- if (href.toLowerCase() != sanitizedHref.toLowerCase() && !isProxy) {
- var sanitizedURL = uobject;
- sanitizedURL.path = null;
- sanitizedURL.href = null;
- sanitizedURL.pathname = sanitizedHref;
- sanitizedURL.hostname = null;
- sanitizedURL.host = null;
- sanitizedURL.port = null;
- sanitizedURL.protocol = null;
- sanitizedURL.slashes = null;
- sanitizedURL = url.format(sanitizedURL);
- serverconsole.resmessage("URL sanitized: " + req.url + " => " + sanitizedURL);
+ if (req.method == "CONNECT") {
+ // CONNECT requests should be handled in "connect" event.
+ callServerError(501);
+ serverconsole.errmessage("CONNECT requests aren't supported. Your JS runtime probably doesn't support 'connect' handler for HTTP library.");
+ return;
+ }
+
+ // Check for invalid X-Forwarded-For header
+ if (!isForwardedValid) {
+ serverconsole.errmessage("X-Forwarded-For header is invalid.");
+ callServerError(400);
+ return;
+ }
+
+ // Sanitize URL
+ var sanitizedHref = sanitizeURL(href, allowDoubleSlashes);
+ var preparedReqUrl = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
+
+ // Check if URL is "dirty"
+ if (href != sanitizedHref && !isProxy) {
+ var sanitizedURL = uobject;
+ sanitizedURL.path = null;
+ sanitizedURL.href = null;
+ sanitizedURL.pathname = sanitizedHref;
+ sanitizedURL.hostname = null;
+ sanitizedURL.host = null;
+ sanitizedURL.port = null;
+ sanitizedURL.protocol = null;
+ sanitizedURL.slashes = null;
+ sanitizedURL = url.format(sanitizedURL);
+ serverconsole.resmessage("URL sanitized: " + req.url + " => " + sanitizedURL);
+ if (rewriteDirtyURLs) {
+ req.url = sanitizedURL;
+ try {
+ uobject = parseURL(req.url);
+ } catch (err) {
+ // Return an 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
+ }
+ search = uobject.search;
+ href = uobject.pathname;
+ ext = path.extname(href).toLowerCase();
+ ext = ext.substring(1, ext.length + 1);
+ try {
+ decodedHref = decodeURIComponent(href);
+ } catch (err) {
+ // Return 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
+ }
+ } else {
redirect(sanitizedURL, false);
return;
- } else if(req.url != preparedReqUrl && !isProxy) {
- serverconsole.resmessage("URL sanitized: " + req.url + " => " + preparedReqUrl);
+ }
+ } else if (req.url != preparedReqUrl && !isProxy) {
+ serverconsole.resmessage("URL sanitized: " + req.url + " => " + preparedReqUrl);
+ if (rewriteDirtyURLs) {
+ req.url = preparedReqUrl;
+ } else {
redirect(preparedReqUrl, false);
return;
}
-
- //URL REWRITING
- function rewriteURL(address, map) {
- var rewrittenAddress = address;
- for (var i = 0; i < map.length; i++) {
- if (createRegex(map[i].definingRegex).test(address)) {
- for (var j = 0; j < map[i].replacements.length; j++) {
- rewrittenAddress = rewrittenAddress.replace(createRegex(map[i].replacements[j].regex), map[i].replacements[j].replacement);
+ }
+
+ // Handle redirects to HTTPS
+ if (secure && !fromMain && !disableNonEncryptedServer && !disableToHTTPSRedirect) {
+ var hostx = req.headers.host;
+ if (hostx === undefined) {
+ serverconsole.errmessage("Host header is missing.");
+ callServerError(400);
+ return;
+ }
+
+ if (isProxy) {
+ callServerError(501);
+ serverconsole.errmessage("This server will never be a proxy.");
+ return;
+ }
+
+ var isPublicServer = !(req.socket.realRemoteAddress ? req.socket.realRemoteAddress : req.socket.remoteAddress).match(/^(?:localhost$|::1$|f[c-d][0-9a-f]{2}:|(?:::ffff:)?(?:(?:127|10)\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|192\.168\.[0-9]{1,3}\.[0-9]{1,3}|172\.(?:1[6-9]|2[0-9]|3[0-1])\.[0-9]{1,3}\.[0-9]{1,3})$)/i);
+
+ var destinationPort = 0;
+
+ var parsedHostx = hostx.match(/(\[[^\]]*\]|[^:]*)(?::([0-9]+))?/);
+ var hostname = parsedHostx[1];
+ var hostPort = parsedHostx[2] ? parseInt(parsedHostx[2]) : 80;
+ if (isNaN(hostPort)) hostPort = 80;
+
+ if (hostPort == port || (port == pubport && !isPublicServer)) {
+ destinationPort = sport;
+ } else {
+ destinationPort = spubport;
+ }
+
+ redirect("https://" + hostname + (destinationPort == 443 ? "" : (":" + destinationPort)) + req.url);
+ return;
+ }
+
+ // Handle redirects to addresses with "www." prefix
+ if (wwwredirect) {
+ var hostname = req.headers.host.split(":");
+ var hostport = null;
+ if (hostname.length > 1 && (hostname[0] != "[" || hostname[hostname.length - 1] != "]")) hostport = hostname.pop();
+ hostname = hostname.join(":");
+ if (hostname == domain && hostname.indexOf("www.") != 0) {
+ redirect((req.socket.encrypted ? "https" : "http") + "://www." + hostname + (hostport ? ":" + hostport : "") + req.url.replace(/\/+/g, "/"));
+ return;
+ }
+ }
+
+ // Handle URL rewriting
+ function rewriteURL(address, map, callback, _fileState, _mapBegIndex) {
+ var rewrittenURL = address;
+ var doCallback = true;
+ if (!isProxy) {
+ for (var i = (_mapBegIndex ? _mapBegIndex : 0); i < map.length; i++) {
+ var mapEntry = map[i];
+ if (href != "/" && (mapEntry.isNotDirectory || mapEntry.isNotFile) && !_fileState) {
+ fs.stat("." + decodeURIComponent(href), function (err, stats) {
+ var _fileState = 3;
+ if (err) {
+ _fileState = 3;
+ } else if (stats.isDirectory()) {
+ _fileState = 2;
+ } else if (stats.isFile()) {
+ _fileState = 1;
+ } else {
+ _fileState = 3;
+ }
+ rewriteURL(address, map, callback, _fileState, i);
+ });
+ doCallback = false;
+ break;
+ }
+ var tempRewrittenURL = rewrittenURL;
+ if (!mapEntry.allowDoubleSlashes) {
+ address = address.replace(/\/+/g,"/");
+ tempRewrittenURL = address;
+ }
+ if (matchHostname(mapEntry.host) && ipMatch(mapEntry.ip, req.socket ? req.socket.localAddress : undefined) && address.match(createRegex(mapEntry.definingRegex)) && !(mapEntry.isNotDirectory && _fileState == 2) && !(mapEntry.isNotFile && _fileState == 1)) {
+ rewrittenURL = tempRewrittenURL;
+ try {
+ mapEntry.replacements.forEach(function (replacement) {
+ rewrittenURL = rewrittenURL.replace(createRegex(replacement.regex), replacement.replacement);
+ });
+ if (mapEntry.append) rewrittenURL += mapEntry.append;
+ } catch (err) {
+ doCallback = false;
+ callback(err, null);
}
- if (map[i].append) rewrittenAddress += map[i].append;
break;
}
}
- return rewrittenAddress;
}
- var origHref = href;
- if (!isProxy) {
- var rewrittenURL = rewriteURL(req.url, rewriteMap);
- if (rewrittenURL != req.url) {
- serverconsole.resmessage("URL rewritten: " + req.url + " => " + rewrittenURL);
- req.url = rewrittenURL;
- uobject = urlParse(req.url);
- search = uobject.search;
- href = uobject.pathname;
- ext = path.extname(href).toLowerCase();
- ext = ext.substr(1, ext.length);
+ if (doCallback) callback(null, rewrittenURL);
+ }
+ // Trailing slash redirection
+ function redirectTrailingSlashes(callback) {
+ if (!isProxy && !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, 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();
+ }
+ }
+
+ var origHref = href;
+
+ // Add web root postfixes
+ if (!isProxy) {
+ var preparedReqUrl3 = (allowPostfixDoubleSlashes ? (href.replace(/\/+/,"/") + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "")) : req.url);
+ var urlWithPostfix = preparedReqUrl3;
+ var postfixPrefix = "";
+ wwwrootPostfixPrefixesVHost.every(function (currentPostfixPrefix) {
+ if (preparedReqUrl3.indexOf(currentPostfixPrefix) == 0) {
+ if (currentPostfixPrefix.match(/\/+$/)) postfixPrefix = currentPostfixPrefix.replace(/\/+$/, "");
+ else if (urlWithPostfix.length == currentPostfixPrefix.length || urlWithPostfix[currentPostfixPrefix.length] == "?" || urlWithPostfix[currentPostfixPrefix.length] == "/" || urlWithPostfix[currentPostfixPrefix.length] == "#") postfixPrefix = currentPostfixPrefix;
+ else return true;
+ urlWithPostfix = urlWithPostfix.substring(postfixPrefix.length);
+ return false;
+ } else {
+ return true;
+ }
+ });
+ wwwrootPostfixesVHost.every(function (postfixEntry) {
+ if (matchHostname(postfixEntry.host) && ipMatch(postfixEntry.ip, req.socket ? req.socket.localAddress : undefined) && !(postfixEntry.skipRegex && preparedReqUrl3.match(createRegex(postfixEntry.skipRegex)))) {
+ urlWithPostfix = postfixPrefix + "/" + postfixEntry.postfix + urlWithPostfix;
+ return false;
+ } else {
+ return true;
+ }
+ });
+ if (urlWithPostfix != preparedReqUrl3) {
+ serverconsole.resmessage("Added web root postfix: " + req.url + " => " + urlWithPostfix);
+ req.url = urlWithPostfix;
+ try {
+ uobject = parseURL(req.url);
+ } catch (err) {
+ // Return an 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
+ }
+ search = uobject.search;
+ href = uobject.pathname;
+ ext = path.extname(href).toLowerCase();
+ ext = ext.substring(1, ext.length + 1);
+
+ try {
+ decodedHref = decodeURIComponent(href);
+ } catch (err) {
+ // Return 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
+ }
+
+ var sHref = sanitizeURL(href, allowDoubleSlashes);
+ var preparedReqUrl2 = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
+
+ if (req.url != preparedReqUrl2 || sHref != href.replace(/\/\.(?=\/|$)/g, "/").replace(/\/+/g, "/")) {
+ callServerError(403);
+ serverconsole.errmessage("Content blocked.");
+ return;
+ } else if (sHref != href) {
+ var rewrittenAgainURL = uobject;
+ rewrittenAgainURL.path = null;
+ rewrittenAgainURL.href = null;
+ rewrittenAgainURL.pathname = sHref;
+ rewrittenAgainURL.hostname = null;
+ rewrittenAgainURL.host = null;
+ rewrittenAgainURL.port = null;
+ rewrittenAgainURL.protocol = null;
+ rewrittenAgainURL.slashes = null;
+ rewrittenAgainURL = url.format(rewrittenAgainURL);
+ serverconsole.resmessage("URL sanitized: " + req.url + " => " + rewrittenAgainURL);
+ req.url = rewrittenAgainURL;
try {
- decodedHref = decodeURIComponent(href);
- } catch (ex) {
- //Return 400 error
+ uobject = parseURL(req.url);
+ } catch (err) {
+ // Return an 400 error
callServerError(400);
serverconsole.errmessage("Bad request!");
return;
}
-
- var sHref = sanitizeURL(href);
- var preparedReqUrl2 = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
-
- if (req.url != preparedReqUrl2 || sHref != href.replace(/\/\.(?=\/|$)/g, "/").replace(/\/+/g, "/")) {
- callServerError(403);
- serverconsole.errmessage("Content blocked.");
- return;
- } else if (sHref != href) {
- var rewrittenAgainURL = uobject;
- rewrittenAgainURL.path = null;
- rewrittenAgainURL.href = null;
- rewrittenAgainURL.pathname = sHref;
- rewrittenAgainURL = url.format(rewrittenAgainURL);
- serverconsole.resmessage("URL sanitized: " + req.url + " => " + rewrittenAgainURL);
- req.url = rewrittenAgainURL;
- uobject = urlParse(req.url);
- search = uobject.search;
- href = uobject.pathname;
- ext = path.extname(href).toLowerCase();
- ext = ext.substr(1, ext.length);
- try {
- decodedHref = decodeURIComponent(href);
- } catch (ex) {
- //Return 400 error
- callServerError(400);
- serverconsole.errmessage("Bad request!");
- return;
- }
- }
- }
- }
- if (!isProxy) {
- reqcounter++;
- var hkh = getCustomHeaders();
- var hk = Object.keys(hkh);
- for (var i = 0; i < hk.length; i++) {
+ search = uobject.search;
+ href = uobject.pathname;
+ ext = path.extname(href).toLowerCase();
+ ext = ext.substring(1, ext.length + 1);
try {
- response.setHeader(hk[i], hkh[hk[i]]);
- } catch (ex) {
- //Headers will not be set.
+ decodedHref = decodeURIComponent(href);
+ } catch (err) {
+ // Return 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
}
}
}
- if ((checkIfForbiddenPath(decodedHref, "config") || checkIfForbiddenPath(decodedHref, "certificates")) && !isProxy) {
+ }
+
+ // Rewrite URLs
+ rewriteURL(req.url, rewriteMap, function (err, rewrittenURL) {
+ if (err) {
+ callServerError(500, err);
+ return;
+ }
+ if (rewrittenURL != req.url) {
+ serverconsole.resmessage("URL rewritten: " + req.url + " => " + rewrittenURL);
+ req.url = rewrittenURL;
+ try {
+ uobject = parseURL(req.url);
+ } catch (err) {
+ // Return an 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
+ }
+ search = uobject.search;
+ href = uobject.pathname;
+ ext = path.extname(href).toLowerCase();
+ ext = ext.substring(1, ext.length + 1);
+
+ try {
+ decodedHref = decodeURIComponent(href);
+ } catch (err) {
+ // Return 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
+ }
+
+ var sHref = sanitizeURL(href, allowDoubleSlashes);
+ var preparedReqUrl2 = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
+
+ if (req.url != preparedReqUrl2 || sHref != href.replace(/\/\.(?=\/|$)/g, "/").replace(/\/+/g, "/")) {
+ callServerError(403);
+ serverconsole.errmessage("Content blocked.");
+ return;
+ } else if (sHref != href) {
+ var rewrittenAgainURL = uobject;
+ rewrittenAgainURL.path = null;
+ rewrittenAgainURL.href = null;
+ rewrittenAgainURL.pathname = sHref;
+ rewrittenAgainURL.hostname = null;
+ rewrittenAgainURL.host = null;
+ rewrittenAgainURL.port = null;
+ rewrittenAgainURL.protocol = null;
+ rewrittenAgainURL.slashes = null;
+ rewrittenAgainURL = url.format(rewrittenAgainURL);
+ serverconsole.resmessage("URL sanitized: " + req.url + " => " + rewrittenAgainURL);
+ req.url = rewrittenAgainURL;
+ try {
+ uobject = parseURL(req.url);
+ } catch (err) {
+ // Return an 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
+ }
+ search = uobject.search;
+ href = uobject.pathname;
+ ext = path.extname(href).toLowerCase();
+ ext = ext.substring(1, ext.length + 1);
+ try {
+ decodedHref = decodeURIComponent(href);
+ } catch (err) {
+ // Return 400 error
+ callServerError(400);
+ serverconsole.errmessage("Bad request!");
+ return;
+ }
+ }
+ }
+ // Set response headers
+ if (!isProxy) {
+ var hkh = getCustomHeaders();
+ Object.keys(hkh).forEach(function (hkS) {
+ try {
+ res.setHeader(hkS, hkh[hkS]);
+ } catch (err) {
+ // Headers will not be set.
+ }
+ });
+ }
+
+ // Prepare the path (remove multiple slashes)
+ var decodedHrefWithoutDuplicateSlashes = decodedHref.replace(/\/+/g,"/");
+
+ // Check if path is forbidden
+ if ((isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "config") || isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "certificates")) && !isProxy) {
callServerError(403);
serverconsole.errmessage("Access to configuration file/certificates is denied.");
return;
- } else if (checkIfIndexOfForbiddenPath(decodedHref, "temp") && !isProxy) {
+ } else if (isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "temp") && !isProxy) {
callServerError(403);
serverconsole.errmessage("Access to temporary folder is denied.");
return;
- } else if (checkIfIndexOfForbiddenPath(decodedHref, "log") && !isProxy && (configJSON.enableLogging || configJSON.enableLogging == undefined) && configJSON.enableRemoteLogBrowsing) {
+ } else if (isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "log") && !isProxy && (configJSON.enableLogging || configJSON.enableLogging == undefined) && !configJSON.enableRemoteLogBrowsing) {
callServerError(403);
serverconsole.errmessage("Access to log files is denied.");
return;
- } else if (checkIfForbiddenPath(decodedHref, "svrjs") && !isProxy && !exposeServerVersion) {
+ } else if (isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") && !isProxy && !exposeServerVersion) {
callServerError(403);
serverconsole.errmessage("Access to SVR.JS script is denied.");
return;
- } else if ((checkIfForbiddenPath(decodedHref, "svrjs") || checkIfForbiddenPath(decodedHref, "serverSideScripts") || checkIfIndexOfForbiddenPath(decodedHref, "serverSideScriptDirectories")) && !isProxy && (configJSON.disableServerSideScriptExpose || configJSON.disableServerSideScriptExpose === undefined)) {
+ } else if ((isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") || isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "serverSideScripts") || isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "serverSideScriptDirectories")) && !isProxy && (configJSON.disableServerSideScriptExpose || configJSON.disableServerSideScriptExpose === undefined)) {
callServerError(403);
serverconsole.errmessage("Access to sources is denied.");
return;
@@ -4030,38 +4490,35 @@ if (!cluster.isPrimary) {
var nonscodeIndex = -1;
var authIndex = -1;
var regexI = [];
+
+ // Scan for non-standard codes
if (!isProxy && nonStandardCodes != undefined) {
for (var i = 0; i < nonStandardCodes.length; i++) {
- var mth = false;
- if (nonStandardCodes[i].regex) {
- var regexObj = nonStandardCodes[i].regex.split("/");
- if (regexObj.length == 0) throw new Error("Invalid regex!");
- var modifiers = regexObj.pop();
- if (!modifiers.match(/i/i) && os.platform() == "win32") modifiers += "i";
- regexObj.shift();
- var searchString = regexObj.join("/");
- var rx = RegExp(searchString, modifiers);
- mth = req.url.match(rx) || href.match(rx);
- regexI[i] = rx;
- } else {
- mth = nonStandardCodes[i].url == href || (os.platform() == "win32" && nonStandardCodes[i].url.toLowerCase() == href.toLowerCase());
- }
- if (mth) {
- if (nonStandardCodes[i].scode == 401) {
- if (authIndex == -1) {
- authIndex = i;
- }
+ if (matchHostname(nonStandardCodes[i].host) && ipMatch(nonStandardCodes[i].ip, req.socket ? req.socket.localAddress : undefined)) {
+ var isMatch = false;
+ var hrefWithoutDuplicateSlashes = href.replace(/\/+/g,"/");
+ if (nonStandardCodes[i].regex) {
+ // Regex match
+ var createdRegex = createRegex(nonStandardCodes[i].regex, true);
+ isMatch = req.url.match(createdRegex) || hrefWithoutDuplicateSlashes.match(createdRegex);
+ regexI[i] = createdRegex;
} else {
- if (nonscodeIndex == -1) {
- if ((nonStandardCodes[i].scode == 403 || nonStandardCodes[i].scode == 451) && nonStandardCodes[i].users !== undefined) {
- var lpk = false;
- if (nonStandardCodes[i].users.check(reqip)) {
+ // Non-regex match
+ isMatch = nonStandardCodes[i].url == hrefWithoutDuplicateSlashes || (os.platform() == "win32" && nonStandardCodes[i].url.toLowerCase() == hrefWithoutDuplicateSlashes.toLowerCase());
+ }
+ if (isMatch) {
+ if (nonStandardCodes[i].scode == 401) {
+ // HTTP authentication
+ if (authIndex == -1) {
+ authIndex = i;
+ }
+ } else {
+ if (nonscodeIndex == -1) {
+ if ((nonStandardCodes[i].scode == 403 || nonStandardCodes[i].scode == 451) && nonStandardCodes[i].users !== undefined) {
+ if (nonStandardCodes[i].users.check(reqip)) nonscodeIndex = i;
+ } else {
nonscodeIndex = i;
- lpk = true;
}
- if (lpk) break;
- } else {
- nonscodeIndex = i;
}
}
}
@@ -4069,109 +4526,222 @@ if (!cluster.isPrimary) {
}
}
+ // Handle non-standard codes
if (nonscodeIndex > -1) {
var nonscode = nonStandardCodes[nonscodeIndex];
- if (nonscode.scode == 301 || nonscode.scode == 302) {
+ if (nonscode.scode == 301 || nonscode.scode == 302 || nonscode.scode == 307 || nonscode.scode == 308) {
var location = "";
if (regexI[nonscodeIndex]) {
location = req.url.replace(regexI[nonscodeIndex], nonscode.location);
+ if(location == req.url) {
+ // Fallback replacement
+ location = hrefWithoutDuplicateSlashes.replace(regexI[nonscodeIndex], nonscode.location);
+ }
} else if (req.url.split("?")[1] == undefined || req.url.split("?")[1] == null || req.url.split("?")[1] == "" || req.url.split("?")[1] == " ") {
location = nonscode.location;
} else {
location = nonscode.location + "?" + req.url.split("?")[1];
}
- redirect(location, nonscode.scode == 302);
- return;
- } else if (nonscode.scode == 403) {
- callServerError(403);
- serverconsole.errmessage("Content blocked.");
- return;
- } else if (nonscode.scode == 410) {
- callServerError(410);
- serverconsole.errmessage("Content is gone.");
- return;
- } else if (nonscode.scode == 418) {
- callServerError(418);
- serverconsole.errmessage("SVR.JS is always a teapot ;)");
+ redirect(location, nonscode.scode == 302 || nonscode.scode == 307, nonscode.scode == 307 || nonscode.scode == 308);
return;
} else {
callServerError(nonscode.scode);
- serverconsole.errmessage("Client fails recieving content.");
+ if (nonscode.scode == 403) {
+ serverconsole.errmessage("Content blocked.");
+ } else if (nonscode.scode == 410) {
+ serverconsole.errmessage("Content is gone.");
+ } else if (nonscode.scode == 418) {
+ serverconsole.errmessage("SVR.JS is always a teapot ;)");
+ } else {
+ serverconsole.errmessage("Client fails receiving content.");
+ }
return;
}
}
+
+ // Handle HTTP authentication
if (authIndex > -1) {
var authcode = nonStandardCodes[authIndex];
+ // Function to check if passwords match
+ function checkIfPasswordMatches(list, password, callback, _i) {
+ if (!_i) _i = 0;
+ var cb = function (hash) {
+ if (hash == list[_i].pass) {
+ callback(true);
+ } else if (_i >= list.length - 1) {
+ callback(false);
+ } else {
+ checkIfPasswordMatches(list, password, callback, _i + 1);
+ }
+ };
+ var hashedPassword = sha256(password + list[_i].salt);
+ var cacheEntry = null;
+ if (list[_i].scrypt) {
+ if (!crypto.scrypt) {
+ callServerError(500, new Error("SVR.JS doesn't support scrypt-hashed passwords on Node.JS versions without scrypt hash support."));
+ return;
+ } else {
+ cacheEntry = scryptCache.find(function (entry) {
+ return (entry.password == hashedPassword && entry.salt == list[_i].salt);
+ });
+ if (cacheEntry) {
+ cb(cacheEntry.hash);
+ } else {
+ crypto.scrypt(password, list[_i].salt, 64, function (err, derivedKey) {
+ if (err) {
+ callServerError(500, err);
+ } else {
+ var key = derivedKey.toString("hex");
+ scryptCache.push({
+ hash: key,
+ password: hashedPassword,
+ salt: list[_i].salt,
+ addDate: new Date()
+ });
+ cb(key);
+ }
+ });
+ }
+ }
+ } else if (list[_i].pbkdf2) {
+ if (crypto.__disabled__ !== undefined) {
+ callServerError(500, new Error("SVR.JS doesn't support PBKDF2-hashed passwords on Node.JS versions without crypto support."));
+ return;
+ } else {
+ cacheEntry = pbkdf2Cache.find(function (entry) {
+ return (entry.password == hashedPassword && entry.salt == list[_i].salt);
+ });
+ if (cacheEntry) {
+ cb(cacheEntry.hash);
+ } else {
+ crypto.pbkdf2(password, list[_i].salt, 36250, 64, "sha512", function (err, derivedKey) {
+ if (err) {
+ callServerError(500, err);
+ } else {
+ var key = derivedKey.toString("hex");
+ pbkdf2Cache.push({
+ hash: key,
+ password: hashedPassword,
+ salt: list[_i].salt,
+ addDate: new Date()
+ });
+ cb(key);
+ }
+ });
+ }
+ }
+ } else {
+ cb(hashedPassword);
+ }
+ }
+
function authorizedCallback(bruteProtection) {
try {
var ha = getCustomHeaders();
ha["WWW-Authenticate"] = "Basic realm=\"" + (authcode.realm ? authcode.realm.replace(/(\\|")/g, "\\$1") : "SVR.JS HTTP Basic Authorization") + "\", charset=\"UTF-8\"";
var credentials = req.headers["authorization"];
if (!credentials) {
- callServerError(401, undefined, undefined, ha);
+ callServerError(401, ha);
serverconsole.errmessage("Content needs authorization.");
return;
}
- var cmatch = credentials.match(/^Basic (.+)$/);
- if (!cmatch) {
- callServerError(401, undefined, undefined, ha);
+ var credentialsMatch = credentials.match(/^Basic (.+)$/);
+ if (!credentialsMatch) {
+ callServerError(401, ha);
serverconsole.errmessage("Malformed credentials.");
return;
}
- var c2 = Buffer.from(cmatch[1], "base64").toString("utf8");
- var c2match = c2.match(/^([^:]*):(.*)$/);
- if (!c2match) {
- callServerError(401, undefined, undefined, ha);
+ var decodedCredentials = Buffer.from(credentialsMatch[1], "base64").toString("utf8");
+ var decodedCredentialsMatch = decodedCredentials.match(/^([^:]*):(.*)$/);
+ if (!decodedCredentialsMatch) {
+ callServerError(401, ha);
serverconsole.errmessage("Malformed credentials.");
return;
}
- var username = c2match[1];
- var password = c2match[2];
- var authorized = false;
- for (var i = 0; i < users.length; i++) {
- var hash = sha256(password + users[i].salt);
- if (users[i].name == username && users[i].pass == hash) {
- authorized = true;
- break;
- }
- }
- if (!authorized) {
- if (bruteProtection) {
- if (process.send) {
- process.send("\x12AUTHW" + reqip);
+ var username = decodedCredentialsMatch[1];
+ var password = decodedCredentialsMatch[2];
+ var usernameMatch = [];
+ var sha256Count = 0;
+ var pbkdf2Count = 0;
+ var scryptCount = 0;
+ if (!authcode.userList || authcode.userList.indexOf(username) > -1) {
+ usernameMatch = users.filter(function (entry) {
+ if(entry.scrypt) {
+ scryptCount++;
+ } else if(entry.pbkdf2) {
+ pbkdf2Count++;
} else {
- if (!bruteForceDb[reqip]) bruteForceDb[reqip] = {
- invalidAttempts: 0
- };
- bruteForceDb[reqip].invalidAttempts++;
- if (bruteForceDb[reqip].invalidAttempts >= 10) {
- bruteForceDb[reqip].lastAttemptDate = new Date();
+ sha256Count++;
+ }
+ return entry.name == username;
+ });
+ }
+ if (usernameMatch.length == 0) {
+ // Pushing false user match to prevent time-based user enumeration
+ var fakeCredentials = {
+ name: username,
+ pass: "SVRJSAWebServerRunningOnNodeJS",
+ salt: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0"
+ };
+ if (!process.isBun) {
+ if (scryptCount > sha256Count && scryptCount > pbkdf2Count) {
+ fakeCredentials.scrypt = true;
+ } else if (pbkdf2Count > sha256Count) {
+ fakeCredentials.pbkdf2 = true;
+ }
+ }
+ usernameMatch.push(fakeCredentials);
+ }
+ checkIfPasswordMatches(usernameMatch, password, function (authorized) {
+ try {
+ if (!authorized) {
+ if (bruteProtection) {
+ if (process.send) {
+ process.send("\x12AUTHW" + reqip);
+ } else {
+ if (!bruteForceDb[reqip]) bruteForceDb[reqip] = {
+ invalidAttempts: 0
+ };
+ bruteForceDb[reqip].invalidAttempts++;
+ if (bruteForceDb[reqip].invalidAttempts >= 10) {
+ bruteForceDb[reqip].lastAttemptDate = new Date();
+ }
+ }
}
- }
- }
- callServerError(401, undefined, undefined, ha);
- serverconsole.errmessage("User " + String(username).replace(/[\r\n]/g, "") + " failed to log in.");
- } else {
- if (bruteProtection) {
- if (process.send) {
- process.send("\x12AUTHR" + reqip);
+ callServerError(401, ha);
+ serverconsole.errmessage("User \"" + String(username).replace(/[\r\n]/g, "") + "\" failed to log in.");
} else {
- if (bruteForceDb[reqip]) bruteForceDb[reqip] = {
- invalidAttempts: 0
- };
+ if (bruteProtection) {
+ if (process.send) {
+ process.send("\x12AUTHR" + reqip);
+ } else {
+ if (bruteForceDb[reqip]) bruteForceDb[reqip] = {
+ invalidAttempts: 0
+ };
+ }
+ }
+ serverconsole.reqmessage("Client is logged in as \"" + String(username).replace(/[\r\n]/g, "") + "\".");
+ authUser = 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, authUser));
+ });
}
+ } catch (err) {
+ callServerError(500, err);
+ return;
}
- modExecute(mods, vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, fd, callServerError, getCustomHeaders, origHref, redirect, parsePostData));
- }
- } catch(err) {
- callServerError(500, undefined, generateErrorStack(err));
+ });
+ } catch (err) {
+ callServerError(500, err);
return;
}
}
if (authcode.disableBruteProtection) {
+ // Don't brute-force protect it, just do HTTP authentication
authorizedCallback(false);
} else if (!process.send) {
+ // Query data from JS object database
if (!bruteForceDb[reqip] || !bruteForceDb[reqip].lastAttemptDate || (new Date() - 300000 >= bruteForceDb[reqip].lastAttemptDate)) {
if (bruteForceDb[reqip] && bruteForceDb[reqip].invalidAttempts >= 10) bruteForceDb[reqip] = {
invalidAttempts: 5
@@ -4184,6 +4754,7 @@ if (!cluster.isPrimary) {
} else {
var listenerEmitted = false;
+ // Listen for brute-force protection response
function authMessageListener(message) {
if (listenerEmitted) return;
if (message == "\x14AUTHA" + reqip || message == "\x14AUTHD" + reqip) {
@@ -4201,63 +4772,71 @@ if (!cluster.isPrimary) {
process.send("\x12AUTHQ" + reqip);
}
} else {
- modExecute(mods, vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, fd, callServerError, getCustomHeaders, origHref, redirect, parsePostData));
+ redirectTrailingSlashes(function () {
+ modExecute(mods, vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, "", callServerError, getCustomHeaders, origHref, redirect, parsePostData, authUser));
+ });
}
}
- } catch (ex) {
- callServerError(500, undefined, generateErrorStack(ex)); //Return 500 error
- }
- }
+ });
+ } catch (err) {
+ callServerError(500, err);
+ }
}
- //Listen port to server
- server.on("error", function (err) {
- if (err.code == "EADDRINUSE" || err.code == "EADDRNOTAVAIL" || err.code == "EACCES") {
- attmts--;
- if (cluster.isPrimary === undefined) {
- if (err.code == "EADDRINUSE") {
- serverconsole.locerrmessage("Address in use by another process.");
- } else if (err.code == "EADDRNOTAVAIL") {
- serverconsole.locerrmessage("Address not available.");
- } else if (err.code == "EACCES") {
- serverconsole.locerrmessage("Access denied.");
- }
- serverconsole.locmessage(attmts + " attempts left.");
- } else {
- process.send("\x12ERRLIST" + attmts + err.code);
- }
- if (attmts > 0) {
- server2.close();
- setTimeout(start, 900);
- } else {
- if (cluster.isPrimary !== undefined) process.send("\x12" + err.code);
- process.exit(errors[err.code]);
- }
- } else {
- serverconsole.locerrmessage("There was a problem starting SVR.JS!!!");
- serverconsole.locerrmessage("Stack:");
- serverconsole.locerrmessage(generateErrorStack(err));
- if (cluster.isPrimary !== undefined) process.send("\x12CRASH");
- process.exit(err.code ? errors[err.code] : 1);
- }
- });
- server.once("listening", function () {
+ function serverErrorHandler(err, isRedirect) {
+ if(isRedirect) attmtsRedir--;
+ else attmts--;
+ if (cluster.isPrimary === undefined && (isRedirect ? attmtsRedir : attmts)) {
+ serverconsole.locerrmessage(serverErrorDescs[err.code] ? serverErrorDescs[err.code] : serverErrorDescs["UNKNOWN"]);
+ serverconsole.locmessage((isRedirect ? attmtsRedir : attmts) + " attempts left.");
+ } else {
+ try {
+ process.send("\x12ERRLIST" + (isRedirect ? attmtsRedir : attmts) + err.code);
+ } catch (err) {
+ // Probably main process exited
+ }
+ }
+ if ((isRedirect ? attmtsRedir : attmts) > 0) {
+ (isRedirect ? server2 : server).close();
+ setTimeout(start, 900);
+ } else {
+ try {
+ if (cluster.isPrimary !== undefined) process.send("\x12ERRCRASH" + err.code);
+ } catch (err) {
+ // Probably main process exited
+ }
+ setTimeout(function () {
+ var errno = errors[err.code];
+ process.exit(errno ? errno : 1);
+ }, 50);
+ }
+ }
+
+ server.on("error", function (err) {
+ serverErrorHandler(err, false);
+ });
+ server.on("listening", function () {
+ attmts = 5;
listeningMessage();
});
}
+var closedMaster = true;
+
+// IPC listener for server listening signalization
function listenConnListener(msg) {
if (msg == "\x12LISTEN") {
listeningMessage();
}
}
+// IPC listener for brue force protection
function bruteForceListenerWrapper(worker) {
return function bruteForceListener(message) {
var ip = "";
- if (message.substr(0, 6) == "\x12AUTHQ") {
- ip = message.substr(6);
+ if (message.substring(0, 6) == "\x12AUTHQ") {
+ ip = message.substring(6);
if (!bruteForceDb[ip] || !bruteForceDb[ip].lastAttemptDate || (new Date() - 300000 >= bruteForceDb[ip].lastAttemptDate)) {
if (bruteForceDb[ip] && bruteForceDb[ip].invalidAttempts >= 10) bruteForceDb[ip] = {
invalidAttempts: 5
@@ -4266,13 +4845,13 @@ function bruteForceListenerWrapper(worker) {
} else {
worker.send("\x14AUTHD" + ip);
}
- } else if (message.substr(0, 6) == "\x12AUTHR") {
- ip = message.substr(6);
+ } else if (message.substring(0, 6) == "\x12AUTHR") {
+ ip = message.substring(6);
if (bruteForceDb[ip]) bruteForceDb[ip] = {
invalidAttempts: 0
};
- } else if (message.substr(0, 6) == "\x12AUTHW") {
- ip = message.substr(6);
+ } else if (message.substring(0, 6) == "\x12AUTHW") {
+ ip = message.substring(6);
if (!bruteForceDb[ip]) bruteForceDb[ip] = {
invalidAttempts: 0
};
@@ -4284,8 +4863,10 @@ function bruteForceListenerWrapper(worker) {
};
}
+var isWorkerHungUpBuff = true;
var isWorkerHungUpBuff2 = true;
+// General IPC message listener
function msgListener(msg) {
for (var i = 0; i < Object.keys(cluster.workers).length; i++) {
if (msg == "\x12END") {
@@ -4296,32 +4877,30 @@ function msgListener(msg) {
}
if (msg == "\x12CLOSE") {
closedMaster = true;
- } else if (msg == "\x12LISTEN" || msg.substr(0, 4) == "\x12AUTH") {
- //Do nothing!
+ } else if (msg == "\x12LISTEN" || msg.substring(0, 4) == "\x12AUTH") {
+ // Do nothing!
+ } else if (msg == "\x12KILLOK") {
+ if (typeof isWorkerHungUpBuff != "undefined") isWorkerHungUpBuff = false;
+ } else if (msg == "\x12PINGOK") {
+ if (typeof isWorkerHungUpBuff2 != "undefined") isWorkerHungUpBuff2 = false;
+ } else if (msg == "\x12KILLTERMMSG") {
+ serverconsole.locmessage("Terminating unused worker process...");
} else if (msg == "\x12SAVEGOOD") {
serverconsole.locmessage("Configuration saved.");
} else if (msg.indexOf("\x12SAVEERR") == 0) {
- serverconsole.locwarnmessage("There was a problem, while saving configuration file. Reason: " + msg.substr(8));
- } else if (msg == "\x12PINGOK") {
- if(typeof isWorkerHungUpBuff2 != "undefined") isWorkerHungUpBuff2 = false;
- } else if (msg == "\x12OPEN") {
- closedMaster = false;
+ serverconsole.locwarnmessage("There was a problem while saving configuration file. Reason: " + msg.substring(8));
} else if (msg == "\x12END") {
cluster.workers[Object.keys(cluster.workers)[0]].on("message", function (msg) {
- if (msg.length > 9 && msg.indexOf("\x12ERRLIST") == 0) {
- var tries = parseInt(msg.substr(8, 1));
- var errCode = msg.substr(9);
- if (errCode == "EADDRINUSE") {
- serverconsole.locerrmessage("Address in use by another process.");
- } else if (errCode == "EADDRNOTAVAIL") {
- serverconsole.locerrmessage("Address not available.");
- } else if (errCode == "EACCES") {
- serverconsole.locerrmessage("Access denied.");
- }
+ if (msg.length >= 8 && msg.indexOf("\x12ERRLIST") == 0) {
+ var tries = parseInt(msg.substring(8, 9));
+ var errCode = msg.substring(9);
+ serverconsole.locerrmessage(serverErrorDescs[errCode] ? serverErrorDescs[errCode] : serverErrorDescs["UNKNOWN"]);
serverconsole.locmessage(tries + " attempts left.");
}
- if (msg == "\x12CRASH") process.exit(1);
- if (msg == "\x12EADDRINUSE" || msg == "\x12EADDRNOTAVAIL" || msg == "\x12EACCES") process.exit(errors[msg.substr(1)]);
+ if (msg.length >= 9 && msg.indexOf("\x12ERRCRASH") == 0) {
+ var errno = errors[msg.substring(9)];
+ process.exit(errno ? errno : 1);
+ }
});
} else {
serverconsole.climessage(msg);
@@ -4331,94 +4910,154 @@ function msgListener(msg) {
var messageTransmitted = false;
function listeningMessage() {
- if (messageTransmitted) return;
- messageTransmitted = true;
+ if (typeof closedMaster !== "undefined") closedMaster = false;
if (!cluster.isPrimary && cluster.isPrimary !== undefined) {
process.send("\x12LISTEN");
return;
}
+ var listenToLocalhost = (listenAddress && (listenAddress == "localhost" || listenAddress.match(/^127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) || listenAddress.match(/^(?:0{0,4}:)+0{0,3}1$/)));
+ var listenToAny = (!listenAddress || listenAddress.match(/^0{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) || listenAddress.match(/^(?:0{0,4}:)+0{0,4}$/));
+ var sListenToLocalhost = (sListenAddress && (sListenAddress == "localhost" || sListenAddress.match(/^127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) || sListenAddress.match(/^(?:0{0,4}:)+0{0,3}1$/)));
+ var sListenToAny = (!sListenAddress || sListenAddress.match(/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) || sListenAddress.match(/^(?:0{0,4}:)+0{0,4}$/));
+ var accHost = host;
+ var sAccHost = host;
+ if (!listenToAny) accHost = listenAddress;
+ if (!sListenToAny) sAccHost = sListenAddress;
+ if (messageTransmitted) return;
+ messageTransmitted = true;
serverconsole.locmessage("Started server at: ");
- if (secure) serverconsole.locmessage("* https://localhost" + (sport == 443 ? "" : (":" + sport)));
- if (!(secure && disableNonEncryptedServer)) serverconsole.locmessage("* http://localhost" + (port == 80 ? "" : (":" + port)));
- if (host != "" && host != "[offline]") {
- if (secure) serverconsole.locmessage("* https://" + (host.indexOf(":") > -1 ? "[" + host + "]" : host) + (sport == 443 ? "" : (":" + sport)));
- if (!(secure && disableNonEncryptedServer)) serverconsole.locmessage("* http://" + (host.indexOf(":") > -1 ? "[" + host + "]" : host) + (port == 80 ? "" : (":" + port)));
+ if (secure && (sListenToLocalhost || sListenToAny)) {
+ if (typeof sport === "number") {
+ serverconsole.locmessage("* https://localhost" + (sport == 443 ? "" : (":" + sport)));
+ } else {
+ serverconsole.locmessage("* " + sport); // Unix socket or Windows named pipe
+ }
}
+ if (!(secure && disableNonEncryptedServer) && (listenToLocalhost || listenToAny)) {
+ if (typeof port === "number") {
+ serverconsole.locmessage("* http://localhost" + (port == 80 ? "" : (":" + port)));
+ } else {
+ serverconsole.locmessage("* " + port); // Unix socket or Windows named pipe
+ }
+ }
+ if (secure && typeof sport === "number" && !sListenToLocalhost && (!sListenToAny || (host != "" && host != "[offline]"))) serverconsole.locmessage("* https://" + (sAccHost.indexOf(":") > -1 ? "[" + sAccHost + "]" : sAccHost) + (sport == 443 ? "" : (":" + sport)));
+ if (!(secure && disableNonEncryptedServer) && !listenToLocalhost && (!listenToAny || (host != "" && host != "[offline]")) && typeof port === "number") serverconsole.locmessage("* http://" + (accHost.indexOf(":") > -1 ? "[" + accHost + "]" : accHost) + (port == 80 ? "" : (":" + port)));
ipStatusCallback(function () {
if (pubip != "") {
- if (secure) serverconsole.locmessage("* https://" + (pubip.indexOf(":") > -1 ? "[" + pubip + "]" : pubip) + (spubport == 443 ? "" : (":" + spubport)));
- if (!(secure && disableNonEncryptedServer)) serverconsole.locmessage("* http://" + (pubip.indexOf(":") > -1 ? "[" + pubip + "]" : pubip) + (pubport == 80 ? "" : (":" + pubport)));
+ if (secure && !sListenToLocalhost) serverconsole.locmessage("* https://" + (pubip.indexOf(":") > -1 ? "[" + pubip + "]" : pubip) + (spubport == 443 ? "" : (":" + spubport)));
+ if (!(secure && disableNonEncryptedServer) && !listenToLocalhost) serverconsole.locmessage("* http://" + (pubip.indexOf(":") > -1 ? "[" + pubip + "]" : pubip) + (pubport == 80 ? "" : (":" + pubport)));
}
if (domain != "") {
- if (secure) serverconsole.locmessage("* https://" + domain + (spubport == 443 ? "" : (":" + spubport)));
- if (!(secure && disableNonEncryptedServer)) serverconsole.locmessage("* http://" + domain + (pubport == 80 ? "" : (":" + pubport)));
+ if (secure && !sListenToLocalhost) serverconsole.locmessage("* https://" + domain + (spubport == 443 ? "" : (":" + spubport)));
+ if (!(secure && disableNonEncryptedServer) && !listenToLocalhost) serverconsole.locmessage("* http://" + domain + (pubport == 80 ? "" : (":" + pubport)));
}
- serverconsole.locmessage("For CLI help type \"help\"");
+ serverconsole.locmessage("For CLI help, you can type \"help\"");
});
}
-var closedMaster = false;
-
function start(init) {
init = Boolean(init);
if (cluster.isPrimary || cluster.isPrimary === undefined) {
if (init) {
- for (i = 0; i < logo.length; i++) console.log(logo[i]); //Print logo
+ for (i = 0; i < logo.length; i++) console.log(logo[i]); // Print logo
console.log();
- console.log("Welcome to SVR.JS - a web server running on Node.JS");
+ console.log("Welcome to \x1b[1mSVR.JS - a web server running on Node.JS\x1b[0m");
+
+ // Print warnings
if (version.indexOf("Nightly-") === 0) serverconsole.locwarnmessage("This version is only for test purposes and may be unstable.");
- if (vnum <= 57 && JSON.stringify(rewriteMap) != "[]") serverconsole.locwarnmessage("Some URL rewriting regexes will not work in Node.JS 8.x and earlier.");
- if (http2.__disabled__ !== undefined) serverconsole.locwarnmessage("HTTP/2 isn't supported by your Node.JS version!");
- if (svrmodpackUsed) serverconsole.locwarnmessage("The \"svrmodpack\" library is deprecated. Mods using svrmodpack format may not work in future SVR.JS versions.");
- if (process.isBun) serverconsole.locwarnmessage("Bun support is experimental.");
- if (cluster.isPrimary === undefined) serverconsole.locwarnmessage("You're running SVR.JS on single thread. Reliability may suffer.");
- if (crypto.__disabled__ !== undefined) serverconsole.locwarnmessage("Your Node.JS version doesn't have crypto support!");
- if (!process.isBun && process.version == "v8.5.0") serverconsole.locwarnmessage("Your Node.JS version is vulnerable to path validation vulnerability (CVE-2017-14849).");
- 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.");
- if (secure && process.versions && process.versions.openssl && process.versions.openssl.substr(0, 2) == "1.") {
+ 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) {
+ 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 (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 && !crypto.scrypt) serverconsole.locwarnmessage("Your JavaScript runtime doesn't have native scrypt support. HTTP authentication involving scrypt hashes will not work.");
+ if (!process.isBun && /^v(?:[0-9]\.|1[0-7]\.|18\.(?:[0-9]|1[0-8])\.|18\.19\.0|20\.(?:[0-9]|10)\.|20\.11\.0|21\.[0-5]\.|21\.6\.0|21\.6\.1(?![0-9]))/.test(process.version)) serverconsole.locwarnmessage("Your Node.JS version is vulnerable to HTTP server DoS (CVE-2024-22019).");
+ if (!process.isBun && /^v(?:[0-9]\.|1[0-7]\.|18\.(?:1?[0-9])\.|18\.20\.0|20\.(?:[0-9]|1[01])\.|20\.12\.0|21\.[0-6]\.|21\.7\.0|21\.7\.1(?![0-9]))/.test(process.version)) serverconsole.locwarnmessage("Your Node.JS version is vulnerable to HTTP server request smuggling (CVE-2024-27982).");
+ 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.isBun && secure && process.versions && process.versions.openssl && process.versions.openssl.substring(0, 2) == "1.") {
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.");
+ 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 {
- serverconsole.locwarnmessage("OpenSSL 1.x will no longer recieve security updates after 11th September 2023. Your HTTPS communication might be vulnerable in future.");
+ 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");
- if (vnum < 64) serverconsole.locwarnmessage("SVR.JS 3.5.0 and newer are no longer supported on Node.JS 8.x and 9.x");
- if (disableMods) serverconsole.locwarnmessage("SVR.JS is running without mods and server-side JavaScript enabled.");
- if (new Date() > new Date("2 February 2023")) {
- serverconsole.locwarnmessage("SVR.JS 3.4.x LTS versions are no longer supported after 2nd February 2024. Please update SVR.JS to the supported version. If you're using Node.JS 8.x or 9.x, please update your Node.JS runtime.");
- }
+ 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 (disableMods) serverconsole.locwarnmessage("SVR.JS is running without mods and server-side JavaScript enabled. Web applications may not work as expected");
console.log();
+
+ // Display mod and server-side JavaScript errors
+ if (process.isPrimary || process.isPrimary === undefined) {
+ modLoadingErrors.forEach(function (modLoadingError) {
+ serverconsole.locwarnmessage("There was a problem while loading a \"" + String(modLoadingError.modName).replace(/[\r\n]/g, "") + "\" mod.");
+ serverconsole.locwarnmessage("Stack:");
+ serverconsole.locwarnmessage(generateErrorStack(modLoadingError.error));
+ });
+ if (SSJSError) {
+ serverconsole.locwarnmessage("There was a problem while loading server-side JavaScript.");
+ serverconsole.locwarnmessage("Stack:");
+ serverconsole.locwarnmessage(generateErrorStack(SSJSError));
+ }
+ if (SSJSError || modLoadingErrors.length > 0) console.log();
+ }
+
+ // Print server information
serverconsole.locmessage("Server version: " + version);
if (process.isBun) serverconsole.locmessage("Bun version: v" + process.versions.bun);
else serverconsole.locmessage("Node.JS version: " + process.version);
var CPUs = os.cpus();
if (CPUs.length > 0) serverconsole.locmessage("CPU: " + (CPUs.length > 1 ? CPUs.length + "x " : "") + CPUs[0].model);
- if (vnum < 57) {
- throw new Error("SVR.JS requires Node.JS 8.4.0 and newer, but your Node.JS version isn't supported by SVR.JS.");
+
+ // Throw errors
+ if (vnum < 64) throw new Error("SVR.JS requires Node.JS 10.0.0 and newer, but your Node.JS version isn't supported by SVR.JS.");
+ if (configJSONRErr) throw new Error("Can't read SVR.JS configuration file: " + configJSONRErr.message);
+ if (configJSONPErr) throw new Error("SVR.JS configuration parse error: " + configJSONPErr.message);
+ if (configJSON.enableHTTP2 && !secure && (typeof port != "number")) throw new Error("HTTP/2 without HTTPS, along with Unix sockets/Windows named pipes aren't supported by SVR.JS.");
+ if (configJSON.enableHTTP2 && http2.__disabled__ !== undefined) throw new Error("HTTP/2 isn't supported by your Node.JS version! You may not be able to use HTTP/2 with SVR.JS");
+ if (listenAddress) {
+ if (listenAddress.match(/^[0-9]+$/)) throw new Error("Listening network address can't be numeric (it need to be either valid IP address, or valid domain name).");
+ if (listenAddress.match(/^(?:2(?:2[4-9]|3[0-9])\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$|ff[0-9a-f][0-9a-f]:[0-9a-f:])/i)) throw new Error("SVR.JS can't listen on multicast address.");
+ if (brdIPs.indexOf(listenAddress) > -1) throw new Error("SVR.JS can't listen on broadcast address.");
+ if (netIPs.indexOf(listenAddress) > -1) throw new Error("SVR.JS can't listen on subnet address.");
}
+ if (certificateError) throw new Error("There was a problem with SSL certificate/private key: " + certificateError.message);
+ if (wwwrootError) throw new Error("There was a problem with your web root: " + wwwrootError.message);
+ if (sniReDos) throw new Error("Refusing to start, because the current SNI configuration would make the server vulnerable to ReDoS.");
}
- if (!(secure && disableNonEncryptedServer)) serverconsole.locmessage("Starting HTTP server at localhost:" + port.toString() + "...");
- if (secure) serverconsole.locmessage("Starting HTTPS server at localhost:" + sport.toString() + "...");
+
+ // Print server startup information
+ if (!(secure && disableNonEncryptedServer)) serverconsole.locmessage("Starting HTTP server at " + (typeof port == "number" ? (listenAddress ? ((listenAddress.indexOf(":") > -1 ? "[" + listenAddress + "]" : listenAddress)) + ":" : "port ") : "") + port.toString() + "...");
+ if (secure) serverconsole.locmessage("Starting HTTPS server at " + (typeof sport == "number" ? (sListenAddress ? ((sListenAddress.indexOf(":") > -1 ? "[" + sListenAddress + "]" : sListenAddress)) + ":" : "port ") : "") + sport.toString() + "...");
}
if (!cluster.isPrimary) {
try {
- server.listen(secure ? sport : port);
- } catch(err) {
- if(err.code != "ERR_SERVER_ALREADY_LISTEN") throw err;
+ if (typeof (secure ? sport : port) == "number" && (secure ? sListenAddress : listenAddress)) {
+ server.listen(secure ? sport : port, secure ? sListenAddress : listenAddress);
+ } else {
+ server.listen(secure ? sport : port);
+ }
+ } catch (err) {
+ if (err.code != "ERR_SERVER_ALREADY_LISTEN") throw err;
}
if (secure && !disableNonEncryptedServer) {
try {
- server2.listen(port);
- } catch(err) {
- if(err.code != "ERR_SERVER_ALREADY_LISTEN") throw err;
+ if (typeof port == "number" && listenAddress) {
+ server2.listen(port, listenAddress);
+ } else {
+ server2.listen(port);
+ }
+ } catch (err) {
+ if (err.code != "ERR_SERVER_ALREADY_LISTEN") throw err;
}
}
}
-
+ // SVR.JS commmands
var commands = {
close: function () {
try {
@@ -4431,27 +5070,32 @@ function start(init) {
process.send("Server closed.");
process.send("\x12CLOSE");
}
- } catch (ex) {
- if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot close server! Reason: " + ex.message);
- else process.send("Cannot close server! Reason: " + ex.message);
+ } catch (err) {
+ if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot close server! Reason: " + err.message);
+ else process.send("Cannot close server! Reason: " + err.message);
}
},
open: function () {
try {
- if (secure) {
- server.listen(sport);
- if (!disableNonEncryptedServer) server2.listen(port);
+ if (typeof (secure ? sport : port) == "number" && (secure ? sListenAddress : listenAddress)) {
+ server.listen(secure ? sport : port, secure ? sListenAddress : listenAddress);
} else {
- server.listen(port); // ReOpen Server
+ server.listen(secure ? sport : port);
+ }
+ if (secure && !disableNonEncryptedServer) {
+ if (typeof port == "number" && listenAddress) {
+ server2.listen(port, listenAddress);
+ } else {
+ server2.listen(port);
+ }
}
if (cluster.isPrimary === undefined) serverconsole.climessage("Server opened.");
else {
process.send("Server opened.");
- process.send("\x12OPEN");
}
- } catch (ex) {
- if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot open server! Reason: " + ex.message);
- else process.send("Cannot open server! Reason: " + ex.message);
+ } catch (err) {
+ if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot open server! Reason: " + err.message);
+ else process.send("Cannot open server! Reason: " + err.message);
}
},
help: function () {
@@ -4471,10 +5115,55 @@ function start(init) {
}
},
stop: function (retcode) {
- if (typeof retcode == "number") {
- process.exit(retcode);
+ reallyExiting = true;
+ clearInterval(passwordHashCacheIntervalId);
+ if ((!cluster.isPrimary && cluster.isPrimary !== undefined) && server.listening) {
+ try {
+ server.close(function () {
+ if (server2.listening) {
+ try {
+ server2.close(function () {
+ if (!process.removeFakeIPC) {
+ if (typeof retcode == "number") {
+ process.exit(retcode);
+ } else {
+ process.exit(0);
+ }
+ }
+ });
+ } catch (err) {
+ if (!process.removeFakeIPC) {
+ if (typeof retcode == "number") {
+ process.exit(retcode);
+ } else {
+ process.exit(0);
+ }
+ }
+ }
+ } else {
+ if (!process.removeFakeIPC) {
+ if (typeof retcode == "number") {
+ process.exit(retcode);
+ } else {
+ process.exit(0);
+ }
+ }
+ }
+ });
+ } catch (err) {
+ if (typeof retcode == "number") {
+ process.exit(retcode);
+ } else {
+ process.exit(0);
+ }
+ }
+ if (process.removeFakeIPC) process.removeFakeIPC();
} else {
- process.exit(0);
+ if (typeof retcode == "number") {
+ process.exit(retcode);
+ } else {
+ process.exit(0);
+ }
}
},
clear: function () {
@@ -4486,11 +5175,11 @@ function start(init) {
else if (!cluster.isPrimary) process.send("Cannot block non-existent IP.");
} else {
for (var i = 0; i < ip.length; i++) {
- if (ip[i].indexOf(":") == -1) {
+ if (ip[i] != "localhost" && ip[i].indexOf(":") == -1) {
ip[i] = "::ffff:" + ip[i];
}
- if (!blacklist.check(ip[i])) {
- blacklist.add(ip[i]);
+ if (!blocklist.check(ip[i])) {
+ blocklist.add(ip[i]);
}
}
if (cluster.isPrimary === undefined) serverconsole.climessage("IPs successfully blocked.");
@@ -4506,7 +5195,7 @@ function start(init) {
if (ip[i].indexOf(":") == -1) {
ip[i] = "::ffff:" + ip[i];
}
- blacklist.remove(ip[i]);
+ blocklist.remove(ip[i]);
}
if (cluster.isPrimary === undefined) serverconsole.climessage("IPs successfully unblocked.");
else if (!cluster.isPrimary) process.send("IPs successfully unblocked.");
@@ -4525,17 +5214,18 @@ function start(init) {
try {
saveConfig();
serverconsole.locmessage("Configuration saved.");
- } catch (ex) {
- throw new Error(ex);
+ } catch (err) {
+ throw new Error(err);
}
}, 300000);
} else if (cluster.isPrimary) {
setInterval(function () {
var allClusters = Object.keys(cluster.workers);
var goodWorkers = [];
+
function checkWorker(callback, _id) {
- if(typeof _id === "undefined") _id = 0;
- if(_id >= allClusters.length) {
+ if (typeof _id === "undefined") _id = 0;
+ if (_id >= allClusters.length) {
callback();
return;
}
@@ -4546,14 +5236,14 @@ function start(init) {
cluster.workers[allClusters[_id]].send("\x14PINGPING");
setTimeout(function () {
if (isWorkerHungUpBuff2) {
- checkWorker(callback, _id+1);
+ checkWorker(callback, _id + 1);
} else {
goodWorkers.push(allClusters[_id]);
- checkWorker(callback, _id+1);
+ checkWorker(callback, _id + 1);
}
}, 250);
} else {
- checkWorker(callback, _id+1);
+ checkWorker(callback, _id + 1);
}
} catch (err) {
if (cluster.workers[allClusters[_id]]) {
@@ -4561,46 +5251,72 @@ function start(init) {
cluster.workers[allClusters[_id]].on("message", bruteForceListenerWrapper(cluster.workers[allClusters[_id]]));
cluster.workers[allClusters[_id]].on("message", listenConnListener);
}
- checkWorker(callback, _id+1);
+ checkWorker(callback, _id + 1);
}
}
checkWorker(function () {
- var wN = Math.floor(Math.random() * goodWorkers.length); //Send a configuration saving message to a random worker.
- try {
- if (cluster.workers[goodWorkers[wN]]) {
- isWorkerHungUpBuff2 = true;
- cluster.workers[goodWorkers[wN]].on("message", msgListener);
- cluster.workers[goodWorkers[wN]].send("\x14SAVECONF");
- }
- } catch (err) {
- if (cluster.workers[goodWorkers[wN]]) {
- cluster.workers[goodWorkers[wN]].removeAllListeners("message");
- cluster.workers[goodWorkers[wN]].on("message", bruteForceListenerWrapper(cluster.workers[goodWorkers[wN]]));
- cluster.workers[goodWorkers[wN]].on("message", listenConnListener);
- }
- serverconsole.locwarnmessage("There was a problem while saving configuration file. Reason: " + err.message);
+ var wN = Math.floor(Math.random() * goodWorkers.length); //Send a configuration saving message to a random worker.
+ try {
+ if (cluster.workers[goodWorkers[wN]]) {
+ isWorkerHungUpBuff2 = true;
+ cluster.workers[goodWorkers[wN]].on("message", msgListener);
+ cluster.workers[goodWorkers[wN]].send("\x14SAVECONF");
}
+ } catch (err) {
+ if (cluster.workers[goodWorkers[wN]]) {
+ cluster.workers[goodWorkers[wN]].removeAllListeners("message");
+ cluster.workers[goodWorkers[wN]].on("message", bruteForceListenerWrapper(cluster.workers[goodWorkers[wN]]));
+ cluster.workers[goodWorkers[wN]].on("message", listenConnListener);
+ }
+ serverconsole.locwarnmessage("There was a problem while saving configuration file. Reason: " + err.message);
+ }
});
}, 300000);
}
+ if (!cluster.isPrimary) {
+ passwordHashCacheIntervalId = setInterval(function () {
+ pbkdf2Cache = pbkdf2Cache.filter(function (entry) {
+ return entry.addDate > (new Date() - 3600000);
+ });
+ scryptCache = scryptCache.filter(function (entry) {
+ return entry.addDate > (new Date() - 3600000);
+ });
+ }, 1800000);
+ }
if (!cluster.isPrimary && cluster.isPrimary !== undefined) {
process.on("message", function (line) {
try {
if (line == "") {
- //Does Nothing
+ // Does Nothing
process.send("\x12END");
} else if (line == "\x14SAVECONF") {
- //Save configuration file
+ // Save configuration file
try {
saveConfig();
process.send("\x12SAVEGOOD");
- } catch (ex) {
- process.send("\x12SAVEERR" + ex.message);
+ } catch (err) {
+ process.send("\x12SAVEERR" + err.message);
}
process.send("\x12END");
+ } else if (line == "\x14KILLPING") {
+ if (!reallyExiting) {
+ process.send("\x12KILLOK");
+ process.send("\x12END");
+ }
+ // Refuse to send, when it's really exiting. Main process will treat the worker as hung up anyway...
} else if (line == "\x14PINGPING") {
- process.send("\x12PINGOK");
- process.send("\x12END");
+ if (!reallyExiting) {
+ process.send("\x12PINGOK");
+ process.send("\x12END");
+ }
+ // Refuse to send, when it's really exiting. Main process will treat the worker as hung up anyway...
+ } else if (line == "\x14KILLREQ") {
+ if (reqcounter - reqcounterKillReq < 2) {
+ process.send("\x12KILLTERMMSG");
+ process.nextTick(commands.stop);
+ } else {
+ reqcounterKillReq = reqcounter;
+ }
} else if (commands[line.split(" ")[0]] !== undefined && commands[line.split(" ")[0]] !== null) {
var argss = line.split(" ");
var command = argss.shift();
@@ -4610,7 +5326,7 @@ function start(init) {
process.send("Unrecognized command \"" + line.split(" ")[0] + "\".");
process.send("\x12END");
}
- } catch (ex) {
+ } catch (err) {
if (line != "") {
process.send("Can't execute command \"" + line.split(" ")[0] + "\".");
process.send("\x12END");
@@ -4641,21 +5357,22 @@ function start(init) {
if (cluster.workers[allClusters[i]]) {
cluster.workers[allClusters[i]].kill();
}
- } catch (ex) {
+ } catch (err) {
stopError = true;
}
}
if (stopError) serverconsole.climessage("Some SVR.JS workers might not be stopped.");
SVRJSInitialized = false;
- var cpus = os.cpus().length;
+ closedMaster = true;
+ var cpus = os.availableParallelism ? os.availableParallelism() : os.cpus().length;
if (cpus > 16) cpus = 16;
try {
- var useAvailableCores = Math.round((os.freemem()) / 50000000) - 1; //1 core deleted for safety...
+ var useAvailableCores = Math.round((os.freemem()) / 50000000) - 1; // 1 core deleted for safety...
if (cpus > useAvailableCores) cpus = useAvailableCores;
- } catch (ex) {
- //Nevermind... Don't want SVR.JS to fail starting, because os.freemem function is not working.
+ } catch (err) {
+ // Nevermind... Don't want SVR.JS to fail starting, because os.freemem function is not working.
}
- if (cpus < 1) cpus = 1; //If SVR.JS is run on Haiku or if useAvailableCores = 0
+ if (cpus < 1) cpus = 1; // If SVR.JS is running on Haiku or if useAvailableCores = 0
for (var i = 0; i < cpus; i++) {
if (i == 0) {
SVRJSFork();
@@ -4678,31 +5395,35 @@ function start(init) {
exiting = true;
allClusters = Object.keys(cluster.workers);
}
- for (var i = 0; i < allClusters.length; i++) {
+ allClusters.forEach(function (clusterID) {
try {
- if (cluster.workers[allClusters[i]]) {
- cluster.workers[allClusters[i]].on("message", msgListener);
- cluster.workers[allClusters[i]].send(line);
+ if (cluster.workers[clusterID]) {
+ cluster.workers[clusterID].on("message", msgListener);
+ cluster.workers[clusterID].send(line);
}
- } catch (ex) {
- if (cluster.workers[allClusters[i]]) {
- cluster.workers[allClusters[i]].removeAllListeners("message");
- cluster.workers[allClusters[i]].on("message", bruteForceListenerWrapper(cluster.workers[allClusters[i]]));
- cluster.workers[allClusters[i]].on("message", listenConnListener);
+ } catch (err) {
+ if (cluster.workers[clusterID]) {
+ cluster.workers[clusterID].removeAllListeners("message");
+ cluster.workers[clusterID].on("message", bruteForceListenerWrapper(cluster.workers[clusterID]));
+ cluster.workers[clusterID].on("message", listenConnListener);
}
serverconsole.climessage("Can't run command \"" + command + "\".");
}
- }
+ });
if (command == "stop") {
setTimeout(function () {
+ reallyExiting = true;
process.exit(0);
}, 50);
}
} else {
- if (command == "stop") process.exit(0);
+ if (command == "stop") {
+ reallyExiting = true;
+ process.exit(0);
+ }
try {
commands[command](argss);
- } catch (ex) {
+ } catch (err) {
serverconsole.climessage("Unrecognized command \"" + command + "\".");
}
}
@@ -4712,17 +5433,17 @@ function start(init) {
}
if (cluster.isPrimary || cluster.isPrimary === undefined) {
- //Cluster forking code
+ // Cluster forking code
if (cluster.isPrimary !== undefined && init) {
- var cpus = os.cpus().length;
+ var cpus = os.availableParallelism ? os.availableParallelism() : os.cpus().length;
if (cpus > 16) cpus = 16;
try {
- var useAvailableCores = Math.round((os.freemem()) / 50000000) - 1; //1 core deleted for safety...
+ var useAvailableCores = Math.round((os.freemem()) / 50000000) - 1; // 1 core deleted for safety...
if (cpus > useAvailableCores) cpus = useAvailableCores;
- } catch (ex) {
- //Nevermind... Don't want SVR.JS to fail starting, because os.freemem function is not working.
+ } catch (err) {
+ // Nevermind... Don't want SVR.JS to fail starting, because os.freemem function is not working.
}
- if (cpus < 1) cpus = 1; //If SVR.JS is run on Haiku or if useAvailableCores = 0
+ if (cpus < 1) cpus = 1; // If SVR.JS is run on Haiku (os.cpus in Haiku returns empty array) or if useAvailableCores = 0
for (var i = 0; i < cpus; i++) {
if (i == 0) {
SVRJSFork();
@@ -4736,33 +5457,32 @@ function start(init) {
}
}
cluster.workers[Object.keys(cluster.workers)[0]].on("message", function (msg) {
- if (msg.length > 9 && msg.indexOf("\x12ERRLIST") == 0) {
- var tries = parseInt(msg.substr(8, 1));
- var errCode = msg.substr(9);
- if (errCode == "EADDRINUSE") {
- serverconsole.locerrmessage("Address in use by another process.");
- } else if (errCode == "EADDRNOTAVAIL") {
- serverconsole.locerrmessage("Address not available.");
- } else if (errCode == "EACCES") {
- serverconsole.locerrmessage("Access denied.");
- }
+ if (msg.length >= 8 && msg.indexOf("\x12ERRLIST") == 0) {
+ var tries = parseInt(msg.substring(8, 9));
+ var errCode = msg.substring(9);
+ serverconsole.locerrmessage(serverErrorDescs[errCode] ? serverErrorDescs[errCode] : serverErrorDescs["UNKNOWN"]);
serverconsole.locmessage(tries + " attempts left.");
}
- if (msg == "\x12CRASH") process.exit(1);
- if (msg == "\x12EADDRINUSE" || msg == "\x12EADDRNOTAVAIL" || msg == "\x12EACCES") process.exit(errors[msg.substr(1)]);
+ if (msg.length >= 9 && msg.indexOf("\x12ERRCRASH") == 0) {
+ var errno = errors[msg.substring(9)];
+ process.exit(errno ? errno : 1);
+ }
});
+ // Hangup check and restart
setInterval(function () {
if (!closedMaster && !exiting) {
var chksocket = {};
if (secure && disableNonEncryptedServer) {
chksocket = https.get({
- port: sport,
+ hostname: (typeof sport == "number" && sListenAddress) ? sListenAddress : "localhost",
+ port: (typeof sport == "number") ? sport : undefined,
+ socketPath: (typeof sport == "number") ? undefined : sport,
headers: {
"X-SVR-JS-From-Main-Thread": "true",
"User-Agent": (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS")
},
- timeout: 2000,
+ timeout: 1620,
rejectUnauthorized: false
}, function (res) {
chksocket.removeAllListeners("timeout");
@@ -4780,14 +5500,19 @@ function start(init) {
crashed = true;
});
} else if ((configJSON.enableHTTP2 == undefined ? false : configJSON.enableHTTP2) && !secure) {
- var connection = http2.connect("http://localhost:" + port.toString());
+ // It doesn't support through Unix sockets or Windows named pipes
+ var address = ((typeof port == "number" && listenAddress) ? listenAddress : "localhost").replace(/\/@/g, "");
+ if (address.indexOf(":") > -1) {
+ address = "[" + address + "]";
+ }
+ var connection = http2.connect("http://" + address + ":" + port.toString());
connection.on("error", function () {
if (!exiting) {
if (!crashed) SVRJSFork();
else crashed = false;
}
});
- connection.setTimeout(2000, function () {
+ connection.setTimeout(1620, function () {
if (!exiting) SVRJSFork();
crashed = true;
});
@@ -4808,12 +5533,14 @@ function start(init) {
});
} else {
chksocket = http.get({
- port: port,
+ hostname: (typeof port == "number" && listenAddress) ? listenAddress : "localhost",
+ port: (typeof port == "number") ? port : undefined,
+ socketPath: (typeof port == "number") ? undefined : port,
headers: {
"X-SVR-JS-From-Main-Thread": "true",
"User-Agent": (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS")
},
- timeout: 2000
+ timeout: 1620
}, function (res) {
chksocket.removeAllListeners("timeout");
res.destroy();
@@ -4831,13 +5558,80 @@ function start(init) {
});
}
}
- }, 5000);
+ }, 4550);
+
+ // Termination of unused good workers
+ if (!disableUnusedWorkerTermination && cluster.isPrimary !== undefined) {
+ setTimeout(function () {
+ setInterval(function () {
+ if (!closedMaster && !exiting) {
+ var allClusters = Object.keys(cluster.workers);
+ var minClusters = 0;
+ minClusters = Math.ceil(cpus * 0.625);
+ if (minClusters < 2) minClusters = 2;
+ var goodWorkers = [];
+
+ function checkWorker(callback, _id) {
+ if (typeof _id === "undefined") _id = 0;
+ if (_id >= allClusters.length) {
+ callback();
+ return;
+ }
+ try {
+ if (cluster.workers[allClusters[_id]]) {
+ isWorkerHungUpBuff = true;
+ cluster.workers[allClusters[_id]].on("message", msgListener);
+ cluster.workers[allClusters[_id]].send("\x14KILLPING");
+ setTimeout(function () {
+ if (isWorkerHungUpBuff) {
+ checkWorker(callback, _id + 1);
+ } else {
+ goodWorkers.push(allClusters[_id]);
+ checkWorker(callback, _id + 1);
+ }
+ }, 250);
+ } else {
+ checkWorker(callback, _id + 1);
+ }
+ } catch (err) {
+ if (cluster.workers[allClusters[_id]]) {
+ cluster.workers[allClusters[_id]].removeAllListeners("message");
+ cluster.workers[allClusters[_id]].on("message", bruteForceListenerWrapper(cluster.workers[allClusters[_id]]));
+ cluster.workers[allClusters[_id]].on("message", listenConnListener);
+ }
+ checkWorker(callback, _id + 1);
+ }
+ }
+ checkWorker(function () {
+ if (goodWorkers.length > minClusters) {
+ var wN = Math.floor(Math.random() * goodWorkers.length);
+ if (wN == goodWorkers.length) return;
+ try {
+ if (cluster.workers[goodWorkers[wN]]) {
+ isWorkerHungUpBuff = true;
+ cluster.workers[goodWorkers[wN]].on("message", msgListener);
+ cluster.workers[goodWorkers[wN]].send("\x14KILLREQ");
+ }
+ } catch (err) {
+ if (cluster.workers[goodWorkers[wN]]) {
+ cluster.workers[goodWorkers[wN]].removeAllListeners("message");
+ cluster.workers[goodWorkers[wN]].on("message", bruteForceListenerWrapper(cluster.workers[goodWorkers[wN]]));
+ cluster.workers[goodWorkers[wN]].on("message", listenConnListener);
+ }
+ serverconsole.locwarnmessage("There was a problem while terminating unused worker process. Reason: " + err.message);
+ }
+ }
+ });
+ }
+ }, 300000);
+ }, 2000);
+ }
}
}
}
}
-//Save configuration file
+// Save configuration file
function saveConfig() {
for (var i = 0; i < 3; i++) {
try {
@@ -4858,7 +5652,7 @@ function saveConfig() {
delete configJSONobj.domian;
if (configJSONobj.page404 === undefined) configJSONobj.page404 = "404.html";
configJSONobj.timestamp = timestamp;
- configJSONobj.blacklist = blacklist.raw;
+ configJSONobj.blacklist = blocklist.raw;
if (configJSONobj.nonStandardCodes === undefined) configJSONobj.nonStandardCodes = [];
if (configJSONobj.enableCompression === undefined) configJSONobj.enableCompression = true;
if (configJSONobj.customHeaders === undefined) configJSONobj.customHeaders = {};
@@ -4873,73 +5667,84 @@ function saveConfig() {
if (configJSONobj.disableServerSideScriptExpose === undefined) configJSONobj.disableServerSideScriptExpose = true;
if (configJSONobj.allowStatus === undefined) configJSONobj.allowStatus = true;
if (configJSONobj.rewriteMap === undefined) configJSONobj.rewriteMap = [];
- if (configJSONobj.dontCompress === undefined) configJSONobj.dontCompress = [];
+ if (configJSONobj.dontCompress === undefined) configJSONobj.dontCompress = ["/.*\\.ipxe$/", "/.*\\.(?:jpe?g|png|bmp|tiff|jfif|gif|webp)$/", "/.*\\.(?:[id]mg|iso|flp)$/", "/.*\\.(?:zip|rar|bz2|[gb7x]z|lzma|tar)$/", "/.*\\.(?:mp[34]|mov|wm[av]|avi|webm|og[gv]|mk[va])$/"];
if (configJSONobj.enableIPSpoofing === undefined) configJSONobj.enableIPSpoofing = false;
if (configJSONobj.secure === undefined) configJSONobj.secure = false;
if (configJSONobj.disableNonEncryptedServer === undefined) configJSONobj.disableNonEncryptedServer = false;
if (configJSONobj.disableToHTTPSRedirect === undefined) configJSONobj.disableToHTTPSRedirect = false;
+ if (configJSONobj.enableETag === undefined) configJSONobj.enableETag = true;
+ if (configJSONobj.disableUnusedWorkerTermination === undefined) configJSONobj.disableUnusedWorkerTermination = false;
+ if (configJSONobj.rewriteDirtyURLs === undefined) configJSONobj.rewriteDirtyURLs = false;
+ if (configJSONobj.errorPages === undefined) configJSONobj.errorPages = [];
+ if (configJSONobj.useWebRootServerSideScript === undefined) configJSONobj.useWebRootServerSideScript = true;
if (configJSONobj.exposeModsInErrorPages === undefined) configJSONobj.exposeModsInErrorPages = true;
- //configJSONobj.wwwroot = configJSON.wwwroot;
+ if (configJSONobj.disableTrailingSlashRedirects === undefined) configJSONobj.disableTrailingSlashRedirects = false;
+ if (configJSONobj.environmentVariables === undefined) configJSONobj.environmentVariables = {};
+ if (configJSONobj.allowDoubleSlashes === undefined) configJSONobj.allowDoubleSlashes = false;
- var configString = JSON.stringify(configJSONobj, null, 2);
+ var configString = JSON.stringify(configJSONobj, null, 2) + "\n";
fs.writeFileSync(__dirname + "/config.json", configString);
break;
- } catch (ex) {
- if (i >= 2) throw ex;
+ } catch (err) {
+ if (i >= 2) throw err;
var now = Date.now();
while (Date.now() - now < 2);
}
}
}
-//Process event listeners
+// Process event listeners
if (cluster.isPrimary || cluster.isPrimary === undefined) {
- process.on("uncaughtException", function (ex) {
- //CRASH HANDLER
+ process.on("uncaughtException", function (err) {
+ // CRASH HANDLER
serverconsole.locerrmessage("SVR.JS master process just crashed!!!");
serverconsole.locerrmessage("Stack:");
- serverconsole.locerrmessage(generateErrorStack(ex));
- process.exit(ex.errno);
+ serverconsole.locerrmessage(generateErrorStack(err));
+ process.exit(err.errno);
});
- process.on("unhandledRejection", function (ex) {
- //CRASH HANDLER
+ process.on("unhandledRejection", function (err) {
+ // CRASH HANDLER
serverconsole.locerrmessage("SVR.JS master process just crashed!!!");
serverconsole.locerrmessage("Stack:");
- serverconsole.locerrmessage(ex.stack ? generateErrorStack(ex) : String(ex));
- process.exit(ex.errno);
+ serverconsole.locerrmessage(err.stack ? generateErrorStack(err) : String(err));
+ process.exit(err.errno);
});
process.on("exit", function (code) {
try {
- saveConfig();
- } catch (ex) {
- serverconsole.locwarnmessage("There was a problem, while saving configuration file. Reason: " + ex.message);
+ if (!configJSONRErr && !configJSONPErr) {
+ saveConfig();
+ }
+ } catch (err) {
+ serverconsole.locwarnmessage("There was a problem while saving configuration file. Reason: " + err.message);
}
try {
deleteFolderRecursive(__dirname + "/temp");
- } catch (ex) {
- //Error!
+ } catch (err) {
+ // Error!
}
try {
fs.mkdirSync(__dirname + "/temp");
- } catch (ex) {
- //Error!
+ } catch (err) {
+ // Error!
+ }
+ if (process.isBun && process.versions.bun && process.versions.bun[0] == "0") {
+ try {
+ fs.writeFileSync(__dirname + "/temp/serverSideScript.js", "// Placeholder server-side JavaScript to workaround Bun bug.\r\n");
+ } catch (err) {
+ // Error!
+ }
}
serverconsole.locmessage("Server closed with exit code: " + code);
});
process.on("warning", function (warning) {
serverconsole.locwarnmessage(warning.message);
- if (generateErrorStack(warning)) {}
- if (process.isBun) {
- try {
- fs.writeFileSync(__dirname + "/temp/serverSideScript.js", "//Placeholder server-side JavaScript to workaround Bun bug.\r\n");
- } catch (ex) {
- //Error!
- }
+ if (generateErrorStack(warning)) {
serverconsole.locwarnmessage("Stack:");
serverconsole.locwarnmessage(generateErrorStack(warning));
}
});
process.on("SIGINT", function () {
+ reallyExiting = true;
if (cluster.isPrimary !== undefined) {
exiting = true;
var allClusters = Object.keys(cluster.workers);
@@ -4948,8 +5753,8 @@ if (cluster.isPrimary || cluster.isPrimary === undefined) {
if (cluster.workers[allClusters[i]]) {
cluster.workers[allClusters[i]].send("stop");
}
- } catch (ex) {
- //Worker will crash with EPIPE anyway.
+ } catch (err) {
+ // Worker will crash with EPIPE anyway.
}
}
}
@@ -4957,20 +5762,18 @@ if (cluster.isPrimary || cluster.isPrimary === undefined) {
process.exit();
});
} else {
- process.on("uncaughtException", function (ex) {
- //CRASH HANDLER
+ // Crash handler
+ function crashHandler(err) {
serverconsole.locerrmessage("SVR.JS worker just crashed!!!");
serverconsole.locerrmessage("Stack:");
- serverconsole.locerrmessage(generateErrorStack(ex));
- process.exit(ex.errno);
- });
- process.on("unhandledRejection", function (ex) {
- //CRASH HANDLER
- serverconsole.locerrmessage("SVR.JS worker just crashed!!!");
- serverconsole.locerrmessage("Stack:");
- serverconsole.locerrmessage(ex.stack ? generateErrorStack(ex) : String(ex));
- process.exit(ex.errno);
- });
+ serverconsole.locerrmessage(err.stack ? generateErrorStack(err) : String(err));
+ process.exit(err.errno);
+ }
+
+ process.on("uncaughtException", crashHandler);
+ process.on("unhandledRejection", crashHandler);
+
+ // Warning handler
process.on("warning", function (warning) {
serverconsole.locwarnmessage(warning.message);
if (warning.stack) {
@@ -4979,12 +5782,15 @@ if (cluster.isPrimary || cluster.isPrimary === undefined) {
}
});
}
-//Call start
+
+// Start SVR.JS!
try {
start(true);
-} catch (ex) {
+} catch (err) {
serverconsole.locerrmessage("There was a problem starting SVR.JS!!!");
serverconsole.locerrmessage("Stack:");
- serverconsole.locerrmessage(generateErrorStack(ex));
- process.exit(ex.errno);
+ serverconsole.locerrmessage(generateErrorStack(err));
+ setTimeout(function () {
+ process.exit(err.errno ? err.errno : 1);
+ }, 10);
}
diff --git a/svrpasswd.js b/svrpasswd.js
index 4ad2956..ef4d241 100644
--- a/svrpasswd.js
+++ b/svrpasswd.js
@@ -7,15 +7,15 @@ try {
} catch (ex) {
var crypto = {};
crypto.__disabled__ = null;
- crypto.createHash = function(type) {
+ crypto.createHash = function (type) {
if (type != "SHA256") throw new Error("Hash type not supported!");
return {
msg: "",
- update: function(a) {
+ update: function (a) {
this.msg = a;
return this;
},
- digest: function(ty) {
+ digest: function (ty) {
var chrsz = 8;
var hexcase = 0;
@@ -163,7 +163,7 @@ try {
}
if (!crypto.randomInt) {
- crypto.randomInt = function(min, max) {
+ crypto.randomInt = function (min, max) {
return Math.round(Math.random() * (max - min)) + min;
}
}
@@ -196,42 +196,58 @@ function saveConfig() {
var args = process.argv;
var user = "";
var action = "change";
+var forcechange = false;
if (process.argv.length <= (process.argv[0].indexOf("node") > -1 || process.argv[0].indexOf("bun") > -1 ? 2 : 1)) args.push("-h");
for (var i = (process.argv[0].indexOf("node") > -1 || process.argv[0].indexOf("bun") > -1 ? 2 : 1); i < args.length; i++) {
if (args[i] == "-h" || args[i] == "--help" || args[i] == "-?" || args[i] == "/h" || args[i] == "/?") {
console.log("SVR.JS user tool usage:");
- console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-a|--add|-d|--delete] ");
+ console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] ");
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
+ console.log("-x -- Changes hash algorithm");
process.exit(0);
} else if (args[i] == "-a" || args[i] == "--add") {
if (action != "change") {
console.log("Multiple actions specified.");
- console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-a|--add|-d|--delete] ");
+ console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] ");
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
+ console.log("-x -- Changes hash algorithm");
process.exit(1);
}
action = "add";
} else if (args[i] == "-d" || args[i] == "--delete") {
if (action != "change") {
console.log("Multiple actions specified.");
- console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-a|--add|-d|--delete] ");
+ console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] ");
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
+ console.log("-x -- Changes hash algorithm");
process.exit(1);
}
action = "delete";
- } else {
- if (user != "") {
- console.log("Multiple users specified.");
- console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-a|--add|-d|--delete] ");
+ } else if (args[i] == "-x") {
+ if (forcechange) {
+ console.log("Multiple -x options specified.");
+ console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] ");
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
+ console.log("-x -- Changes hash algorithm");
+ process.exit(1);
+ }
+ forcechange = true;
+ } else {
+ if (user != "") {
+ console.log("Multiple users specified.");
+ console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] ");
+ console.log("-h -? /h /? --help -- Displays help");
+ console.log("-a --add -- Add an user");
+ console.log("-d --delete -- Deletes an user");
+ console.log("-x -- Changes hash algorithm");
process.exit(1);
}
user = args[i];
@@ -240,10 +256,11 @@ for (var i = (process.argv[0].indexOf("node") > -1 || process.argv[0].indexOf("b
if (user == "") {
console.log("No user specified.");
- console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-a|--add|-d|--delete] ");
+ console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] ");
console.log("-h -? /h /? --help -- Displays help");
console.log("-a --add -- Add an user");
console.log("-d --delete -- Deletes an user");
+ console.log("-x -- Changes hash algorithm");
process.exit(1);
}
@@ -305,6 +322,43 @@ function password(callback) {
});
}
+function promptAlgorithms(callback, bypass, pbkdf2, scrypt) {
+ if (bypass) {
+ if (scrypt) {
+ callback("scrypt");
+ } else if(pbkdf2) {
+ callback("pbkdf2");
+ } else {
+ callback("sha256");
+ }
+ return;
+ }
+ var algorithms = {
+ sha256: "Salted SHA256 (1 iteration) - fastest and uses least memory, but less secure",
+ pbkdf2: "PBKDF2 (PBKDF2-HMAC-SHA512, 36250 iterations) - more secure and uses less memory, but slower",
+ scrypt: "scrypt (N=2^14, r=8, p=1) - faster and more secure, but uses more memory"
+ }
+ if (!crypto.pbkdf2 || process.isBun) delete algorithms.pbkdf2;
+ var algorithmNames = Object.keys(algorithms);
+ if (algorithmNames.length < 2) callback(algorithmNames[0]);
+ console.log("Select password hashing algorithm. Available algorithms:");
+ for (var i = 0; i < algorithmNames.length; i++) {
+ console.log(algorithmNames[i] + " - " + algorithms[algorithmNames[i]]);
+ }
+ var rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ prompt: 'Algorithm: '
+ });
+ rl.prompt();
+ rl.on('line', function (line) {
+ rl.close();
+ line = line.trim();
+ if (!algorithms[line]) callback(false);
+ else callback(line);
+ });
+}
+
var userindex = getUserIndex(user);
if (action == "add" && userindex != -1) {
console.log("User alerady exists.");
@@ -318,35 +372,71 @@ if (action == "delete") {
saveConfig();
console.log("User deleted successfully");
} else if (action == "add") {
- password(function(password) {
- if (!password) {
- console.log("Passwords don't match!");
+ promptAlgorithms(function (algorithm) {
+ if (!algorithm) {
+ console.log("Invalid algorithm!");
process.exit(1);
} else {
- var salt = generateSalt()
- users.push({
- name: user,
- pass: sha256(password + salt),
- salt: salt
+ password(function (password) {
+ if (!password) {
+ console.log("Passwords don't match!");
+ process.exit(1);
+ } else {
+ var salt = generateSalt();
+ var hash = "";
+ if (algorithm == "scrypt") {
+ hash = crypto.scryptSync(password, salt, 64).toString("hex");
+ } else if (algorithm == "pbkdf2") {
+ hash = crypto.pbkdf2Sync(password, salt, 36250, 64, "sha512").toString("hex");
+ } else {
+ hash = sha256(password + salt);
+ }
+ users.push({
+ name: user,
+ pass: hash,
+ salt: salt,
+ pbkdf2: (algorithm == "pbkdf2" ? true : undefined),
+ scrypt: (algorithm == "scrypt" ? true : undefined),
+ __svrpasswd_l2: true
+ });
+ saveConfig();
+ console.log("User added successfully");
+ }
});
- saveConfig();
- console.log("User added successfully");
}
});
} else {
- password(function(password) {
- if (!password) {
- console.log("Passwords don't match!");
+ promptAlgorithms(function (algorithm) {
+ if (!algorithm) {
+ console.log("Invalid algorithm!");
process.exit(1);
} else {
- var salt = generateSalt()
- users[userindex] = {
- name: user,
- pass: sha256(password + salt),
- salt: salt
- };
- saveConfig();
- console.log("Password changed successfully");
+ password(function (password) {
+ if (!password) {
+ console.log("Passwords don't match!");
+ process.exit(1);
+ } else {
+ var salt = generateSalt();
+ var hash = "";
+ if (algorithm == "scrypt") {
+ hash = crypto.scryptSync(password, salt, 64).toString("hex");
+ } else if (algorithm == "pbkdf2") {
+ hash = crypto.pbkdf2Sync(password, salt, 36250, 64, "sha512").toString("hex");
+ } else {
+ hash = sha256(password + salt);
+ }
+ users[userindex] = {
+ name: user,
+ pass: hash,
+ salt: salt,
+ pbkdf2: (algorithm == "pbkdf2" ? true : undefined),
+ scrypt: (algorithm == "scrypt" ? true : undefined),
+ __svrpasswd_l2: true
+ };
+ saveConfig();
+ console.log("Password changed successfully");
+ }
+ });
}
- });
+ }, (users[userindex].__svrpasswd_l2 && !forcechange), users[userindex].pbkdf2, users[userindex].scrypt);
}
diff --git a/testdir/.personalized/folder/FOOT.html b/testdir/.personalized/folder/FOOT.html
index dd34732..b9ef4f4 100644
--- a/testdir/.personalized/folder/FOOT.html
+++ b/testdir/.personalized/folder/FOOT.html
@@ -1,6 +1,6 @@
diff --git a/tests.html b/tests.html
index e14b9cb..1ef64a6 100644
--- a/tests.html
+++ b/tests.html
@@ -1,7 +1,7 @@
- SVR.JS 3.4.42 Tests
+ SVR.JS 3.14.16 Tests
- SVR.JS 3.4.42 Tests
- Directory
+ SVR.JS 3.14.16 Tests
+ Directory (without trailing slash)
Directory (with query)
-
+
Directory (personalized)
404 Error
-
- 500 Error
-
Server Side Javascript
Proxy test
- URL rewriting test (/invoke500/aprilfools => /invoke500.svr?aprilfools)
-
+ URL rewriting test (/testdir_rewritten => /testdir)
+