1 // SVR.JS - a web server running on Node.JS
6 * Copyright (c) 2018-2024 SVR.JS
8 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 // Check if SVR.JS is running on Node.JS-compatible runtime. If not, just error it out.
16 if (typeof require === "undefined") {
17 if (typeof ActiveXObject !== "undefined" && typeof WScript !== "undefined") {
18 // If it runs on Windows Script Host, display an error message.
19 var shell = new ActiveXObject("WScript.Shell");
20 shell.Popup("SVR.JS doesn't work on Windows Script Host. SVR.JS requires use of Node.JS (or compatible JS runtime).", undefined, "Can't start SVR.JS", 16);
23 if (typeof alert !== "undefined" && typeof document !== "undefined") {
24 // If it runs on web browser, display an alert.
25 alert("SVR.JS doesn't work on a web browser. SVR.JS requires use of Node.JS (or compatible JS runtime).");
27 // If it's not, throw an error.
28 if (typeof document !== "undefined") {
29 throw new Error("SVR.JS doesn't work on a web browser. SVR.JS requires use of Node.JS (or compatible JS runtime).");
31 throw new Error("SVR.JS doesn't work on Deno/QuickJS. SVR.JS requires use of Node.JS (or compatible JS runtime).");
37 var disableMods = false;
39 // ASCII art SVR.JS logo ;)
40 var logo = ["", "", "", " \x1b[38;5;002m&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", " &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", " &&&\x1b[38;5;243m(((((((((((((((((((((((((((((((((((((((((((((((((((\x1b[38;5;002m&&&", " \x1b[38;5;002m&&\x1b[38;5;243m((((((\x1b[38;5;241m###########\x1b[38;5;243m(((((((((((((((((((((((\x1b[38;5;011m***\x1b[38;5;243m(\x1b[38;5;011m***\x1b[38;5;243m(\x1b[38;5;011m***\x1b[38;5;243m((\x1b[38;5;002m&&", " \x1b[38;5;002m&&&\x1b[38;5;243m(((((((((((((((((((((((((((((((((((((((((((((((((((\x1b[38;5;002m&&&", " \x1b[38;5;002m&&&\x1b[38;5;243m(((((((((((((((((((((((((((((((((((((((((((((((((((\x1b[38;5;002m&&&", " \x1b[38;5;002m&&\x1b[38;5;243m((((((\x1b[38;5;241m###########\x1b[38;5;243m(((((((((((((((((((((((\x1b[38;5;011m***\x1b[38;5;243m(\x1b[38;5;015m \x1b[38;5;243m(\x1b[38;5;011m***\x1b[38;5;243m((\x1b[38;5;002m&&", " \x1b[38;5;002m&&&\x1b[38;5;243m(((((((((((((((((((((((((((((((((((((((((((((((((((\x1b[38;5;002m&&&", " \x1b[38;5;002m&&&\x1b[38;5;243m(((((((((((((((((((((((((((((((((((((((((((((((((((\x1b[38;5;002m&&&", " \x1b[38;5;002m&&\x1b[38;5;243m((((((\x1b[38;5;241m###########\x1b[38;5;243m(((((((((((((((((((((((\x1b[38;5;015m \x1b[38;5;243m(\x1b[38;5;015m \x1b[38;5;243m(\x1b[38;5;015m \x1b[38;5;243m((\x1b[38;5;002m&&", " \x1b[38;5;002m&&&\x1b[38;5;243m(((((((((((((((((((((((((((((((((((((((((((((((((((\x1b[38;5;002m&&&", " \x1b[38;5;002m&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", " \x1b[38;5;002m&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", " \x1b[38;5;002m&&&&&&&&\x1b[38;5;010m#########################################\x1b[38;5;002m&&&&&&&&", " \x1b[38;5;002m&&&&&\x1b[38;5;010m###############################################\x1b[38;5;002m&&&&&", " \x1b[38;5;002m&&&\x1b[38;5;010m###################################################\x1b[38;5;002m&&&", " \x1b[38;5;002m&&\x1b[38;5;010m####\x1b[38;5;016m@@@@@@\x1b[38;5;010m#\x1b[38;5;016m@@@\x1b[38;5;010m###\x1b[38;5;016m@@@\x1b[38;5;010m#\x1b[38;5;016m@@@@@@@\x1b[38;5;010m###########\x1b[38;5;016m@@\x1b[38;5;010m##\x1b[38;5;016m@@@@@@\x1b[38;5;010m####\x1b[38;5;002m&&", " \x1b[38;5;002m&&\x1b[38;5;010m###\x1b[38;5;016m@@\x1b[38;5;010m#######\x1b[38;5;016m@@\x1b[38;5;010m###\x1b[38;5;016m@@\x1b[38;5;010m##\x1b[38;5;016m@@\x1b[38;5;010m####\x1b[38;5;016m@@\x1b[38;5;010m##########\x1b[38;5;016m@@\x1b[38;5;010m#\x1b[38;5;016m@@\x1b[38;5;010m#########\x1b[38;5;002m&&", " \x1b[38;5;002m&&\x1b[38;5;010m######\x1b[38;5;040m#\x1b[38;5;016m@@@@\x1b[38;5;010m##\x1b[38;5;016m@@\x1b[38;5;010m#\x1b[38;5;016m@@\x1b[38;5;010m###\x1b[38;5;016m@@@@@@@\x1b[38;5;010m#######\x1b[38;5;016m@@\x1b[38;5;010m##\x1b[38;5;016m@@\x1b[38;5;010m####\x1b[38;5;040m#\x1b[38;5;016m@@@@\x1b[38;5;010m###\x1b[38;5;002m&&", " \x1b[38;5;002m&&\x1b[38;5;010m###\x1b[38;5;016m@@\x1b[38;5;034m%\x1b[38;5;010m###\x1b[38;5;016m@@\x1b[38;5;010m###\x1b[38;5;016m@@@\x1b[38;5;010m####\x1b[38;5;016m@@\x1b[38;5;010m####\x1b[38;5;016m@@\x1b[38;5;010m##\x1b[38;5;016m@@\x1b[38;5;010m###\x1b[38;5;016m@@@@\x1b[38;5;010m##\x1b[38;5;016m@@\x1b[38;5;034m%\x1b[38;5;010m###\x1b[38;5;016m@@\x1b[38;5;010m###\x1b[38;5;002m&&", " \x1b[38;5;002m&&\x1b[38;5;010m#####################################################\x1b[38;5;002m&&", " \x1b[38;5;002m&&&\x1b[38;5;010m###################################################\x1b[38;5;002m&&&", " \x1b[38;5;002m&&&&&\x1b[38;5;010m###############################################\x1b[38;5;002m&&&&&", " \x1b[38;5;002m&&&&&&&&\x1b[38;5;010m#########################################\x1b[38;5;002m&&&&&&&&", " \x1b[38;5;002m&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", " &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", " &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", " \x1b[38;5;246m///////", " ///////", " \x1b[38;5;208m((((/))))", " \x1b[38;5;208m(((((/)))))", " \x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m///\x1b[38;5;247m*\x1b[38;5;246m///\x1b[38;5;247m*\x1b[38;5;246m///\x1b[38;5;247m*\x1b[38;5;246m///\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m/\x1b[38;5;208m(((((/)))))\x1b[38;5;246m//\x1b[38;5;247m*\x1b[38;5;246m///\x1b[38;5;247m*\x1b[38;5;246m///\x1b[38;5;247m*\x1b[38;5;246m///\x1b[38;5;247m*\x1b[38;5;246m///\x1b[38;5;247m*\x1b[38;5;246m/", " //\x1b[38;5;247m*\x1b[38;5;246m///////\x1b[38;5;247m*\x1b[38;5;246m///////\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m/\x1b[38;5;208m(((((/)))))\x1b[38;5;246m//\x1b[38;5;247m*\x1b[38;5;246m///////\x1b[38;5;247m*\x1b[38;5;246m///////\x1b[38;5;247m*\x1b[38;5;246m//", " *\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;208m(((((/)))))\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*\x1b[38;5;246m/\x1b[38;5;247m*", " \x1b[38;5;208m((((/))))", "", "", "", "\x1b[0m"];
42 var fs = require("fs");
44 function factoryReset() {
45 console.log("Removing logs...");
46 deleteFolderRecursive(__dirname + "/log");
47 fs.mkdirSync(__dirname + "/log");
48 console.log("Removing temp folder...");
49 deleteFolderRecursive(__dirname + "/temp");
50 fs.mkdirSync(__dirname + "/temp");
51 console.log("Removing configuration file...");
52 fs.unlinkSync("config.json");
57 function deleteFolderRecursive(path) {
58 if (fs.existsSync(path)) {
59 fs.readdirSync(path).forEach(function (file) {
60 var curPath = path + "/" + file;
61 if (fs.statSync(curPath).isDirectory()) { // recurse
62 deleteFolderRecursive(curPath);
63 } else { // delete file
64 fs.unlinkSync(curPath);
71 var os = require("os");
72 var version = "Nightly-GitMain";
73 var singlethreaded = false;
75 if (process.versions) process.versions.svrjs = version; // Inject SVR.JS into process.versions
77 var args = process.argv;
78 for (var i = (process.argv[0].indexOf("node") > -1 || process.argv[0].indexOf("bun") > -1 ? 2 : 1); i < args.length; i++) {
79 if (args[i] == "-h" || args[i] == "--help" || args[i] == "-?" || args[i] == "/h" || args[i] == "/?") {
80 console.log("SVR.JS usage:");
81 console.log("node svr.js [-h] [--help] [-?] [/h] [/?] [--secure] [--reset] [--clean] [--disable-mods] [--single-threaded] [-v] [--version]");
82 console.log("-h -? /h /? --help -- Displays help");
83 console.log("--clean -- Cleans up files created by SVR.JS");
84 console.log("--reset -- Resets SVR.JS to default settings (WARNING: DANGEROUS)");
85 console.log("--secure -- Runs HTTPS server");
86 console.log("--disable-mods -- Disables mods (safe mode)");
87 console.log("--single-threaded -- Run single-threaded");
88 console.log("-v --version -- Display server version");
90 } else if (args[i] == "--secure") {
92 } else if (args[i] == "-v" || args[i] == "--version") {
93 console.log("SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")");
95 } else if (args[i] == "--clean") {
96 console.log("Removing logs...");
97 deleteFolderRecursive(__dirname + "/log");
98 fs.mkdirSync(__dirname + "/log");
99 console.log("Removing temp folder...");
100 deleteFolderRecursive(__dirname + "/temp");
101 fs.mkdirSync(__dirname + "/temp");
102 console.log("Done!");
104 } else if (args[i] == "--reset") {
106 } else if (args[i] == "--disable-mods") {
108 } else if (args[i] == "--single-threaded") {
109 singlethreaded = true;
111 console.log("Unrecognized argument: " + args[i]);
112 console.log("SVR.JS usage:");
113 console.log("node svr.js [-h] [--help] [-?] [/h] [/?] [--secure] [--reset] [--clean] [--disable-mods] [--single-threaded] [-v] [--version]");
114 console.log("-h -? /h /? --help -- Displays help");
115 console.log("--clean -- Cleans up files created by SVR.JS");
116 console.log("--reset -- Resets SVR.JS to default settings (WARNING: DANGEROUS)");
117 console.log("--secure -- Runs HTTPS server");
118 console.log("--disable-mods -- Disables mods (safe mode)");
119 console.log("--single-threaded -- Run single-threaded");
120 console.log("-v --version -- Display server version");
125 var readline = require("readline");
126 var net = require("net");
128 if (!singlethreaded) {
130 // Import cluster module
131 var cluster = require("cluster");
133 // Clustering is not supported!
136 // Cluster & IPC shim for Bun
138 cluster.bunShim = function () {
139 cluster.isMaster = !process.env.NODE_UNIQUE_ID;
140 cluster.isPrimary = cluster.isMaster;
141 cluster.isWorker = !cluster.isMaster;
142 cluster.__shimmed__ = true;
144 if (cluster.isWorker) {
145 // Shim the cluster.worker object for worker processes
147 id: parseInt(process.env.NODE_UNIQUE_ID),
149 isDead: function () {
152 send: function (message, b, c, d) {
153 process.send(message, b, c, d);
158 // Shim the process.send function for worker processes
159 var net = require("net");
160 var os = require("os");
161 var path = require("path");
163 // Create a fake IPC server to receive messages
164 var fakeIPCServer = net.createServer(function (socket) {
165 var receivedData = "";
167 socket.on("data", function (data) {
168 receivedData += data.toString();
171 socket.on("end", function () {
172 process.emit("message", receivedData);
175 fakeIPCServer.listen(os.platform() === "win32" ? path.join("\\\\?\\pipe", __dirname, "temp/.W" + process.pid + ".ipc") : (__dirname + "/temp/.W" + process.pid + ".ipc"));
177 process.send = function (message) {
178 // Create a fake IPC connection to send messages
179 var fakeIPCConnection = net.createConnection(os.platform() === "win32" ? path.join("\\\\?\\pipe", __dirname, "temp/.P" + process.pid + ".ipc") : (__dirname + "/temp/.P" + process.pid + ".ipc"), function () {
180 fakeIPCConnection.end(message);
184 process.removeFakeIPC = function () {
186 process.send = function () {};
187 fakeIPCServer.close();
192 // Custom implementation for cluster.fork()
193 cluster._workersCounter = 1;
194 cluster.workers = {};
195 cluster.fork = function (env) {
196 var child_process = require("child_process");
197 var newEnvironment = JSON.parse(JSON.stringify(env ? env : process.env));
198 newEnvironment.NODE_UNIQUE_ID = cluster._workersCounter;
199 var newArguments = JSON.parse(JSON.stringify(process.argv));
200 var command = newArguments.shift();
201 var newWorker = child_process.spawn(command, newArguments, {
203 stdio: ["inherit", "inherit", "inherit", "ipc"]
206 newWorker.process = newWorker;
207 newWorker.isDead = function () {
208 return newWorker.exitCode !== null || newWorker.killed;
210 newWorker.id = newEnvironment.NODE_UNIQUE_ID;
212 function checkSendImplementation(worker) {
213 var sendImplemented = true;
215 if (!(process.versions && process.versions.bun && process.versions.bun[0] != "0")) {
217 sendImplemented = false;
220 var oldLog = console.log;
221 console.log = function (a, b, c, d, e, f) {
222 if (a == "ChildProcess.prototype.send() - Sorry! Not implemented yet") {
223 throw new Error("NOT IMPLEMENTED");
225 oldLog(a, b, c, d, e, f);
230 worker.send(undefined);
232 if (err.message === "NOT IMPLEMENTED") {
233 sendImplemented = false;
238 console.log = oldLog;
241 return sendImplemented;
244 if (!checkSendImplementation(newWorker)) {
245 var net = require("net");
246 var os = require("os");
248 // Create a fake IPC server for worker process to receive messages
249 var fakeWorkerIPCServer = net.createServer(function (socket) {
250 var receivedData = "";
252 socket.on("data", function (data) {
253 receivedData += data.toString();
256 socket.on("end", function () {
257 newWorker.emit("message", receivedData);
260 fakeWorkerIPCServer.listen(os.platform() === "win32" ? path.join("\\\\?\\pipe", __dirname, "temp/.P" + newWorker.process.pid + ".ipc") : (__dirname + "/temp/.P" + newWorker.process.pid + ".ipc"));
262 // Cleanup when worker process exits
263 newWorker.on("exit", function () {
264 fakeWorkerIPCServer.close();
265 delete cluster.workers[newWorker.id];
268 newWorker.send = function (message, fakeParam2, fakeParam3, fakeParam4, tries) {
269 if (!tries) tries = 0;
272 // Create a fake IPC connection to send messages to worker process
273 var fakeWorkerIPCConnection = net.createConnection(os.platform() === "win32" ? path.join("\\\\?\\pipe", __dirname, "temp/.W" + newWorker.process.pid + ".ipc") : (__dirname + "/temp/.W" + newWorker.process.pid + ".ipc"), function () {
274 fakeWorkerIPCConnection.end(message);
277 if (tries > 50) throw err;
278 newWorker.send(message, fakeParam2, fakeParam3, fakeParam4, tries + 1);
282 newWorker.on("exit", function () {
283 delete cluster.workers[newWorker.id];
287 cluster.workers[newWorker.id] = newWorker;
288 cluster._workersCounter++;
293 if (process.isBun && (cluster.isMaster === undefined || (cluster.isMaster && process.env.NODE_UNIQUE_ID))) {
297 // Shim cluster.isPrimary field
298 if (cluster.isPrimary === undefined && cluster.isMaster !== undefined) cluster.isPrimary = cluster.isMaster;
304 function generateETag(filePath, stat) {
305 if (!ETagDB[filePath + "-" + stat.size + "-" + stat.mtime]) ETagDB[filePath + "-" + stat.size + "-" + stat.mtime] = sha256(filePath + "-" + stat.size + "-" + stat.mtime);
306 return ETagDB[filePath + "-" + stat.size + "-" + stat.mtime];
309 // Brute force protection-related
310 var bruteForceDb = {};
312 // PBKDF2/scrypt cache
313 var pbkdf2Cache = [];
314 var scryptCache = [];
315 var passwordHashCacheIntervalId = -1;
317 // SVR.JS worker spawn-related
318 var SVRJSInitialized = false;
320 var reallyExiting = false;
322 var threadLimitWarned = false;
324 // SVR.JS worker forking function
325 function SVRJSFork() {
327 if (SVRJSInitialized) serverconsole.locmessage("Starting next thread, because previous one hung up/crashed...");
331 if (!threadLimitWarned && cluster.__shimmed__ && process.isBun && process.versions.bun && process.versions.bun[0] != "0") {
332 threadLimitWarned = true;
333 serverconsole.locwarnmessage("SVR.JS limited the number of workers to one, because of startup problems in Bun 1.0 and newer with shimmed (not native) clustering module. Reliability may suffer.");
335 if (!(cluster.__shimmed__ && process.isBun && process.versions.bun && process.versions.bun[0] != "0" && Object.keys(cluster.workers) > 0)) {
336 newWorker = cluster.fork();
338 if (SVRJSInitialized) serverconsole.locwarnmessage("SVR.JS limited the number of workers to one, because of startup problems in Bun 1.0 and newer with shimmed (not native) clustering module. Reliability may suffer.");
341 if (err.name == "NotImplementedError") {
342 // If cluster.fork throws a NotImplementedError, shim cluster module
344 if (!threadLimitWarned && cluster.__shimmed__ && process.isBun && process.versions.bun && process.versions.bun[0] != "0") {
345 threadLimitWarned = true;
346 serverconsole.locwarnmessage("SVR.JS limited the number of workers to one, because of startup problems in Bun 1.0 and newer with shimmed (not native) clustering module. Reliability may suffer.");
348 if (!(cluster.__shimmed__ && process.isBun && process.versions.bun && process.versions.bun[0] != "0" && Object.keys(cluster.workers) > 0)) {
349 newWorker = cluster.fork();
351 if (SVRJSInitialized) serverconsole.locwarnmessage("SVR.JS limited the number of workers to one, because of startup problems in Bun 1.0 and newer with shimmed (not native) clustering module. Reliability may suffer.");
358 // Add event listeners
360 newWorker.on("error", function (err) {
361 if (!reallyExiting) serverconsole.locwarnmessage("There was a problem when handling SVR.JS worker! (from master process side) Reason: " + err.message);
363 newWorker.on("exit", function () {
364 if (!exiting && Object.keys(cluster.workers).length == 0) {
369 newWorker.on("message", bruteForceListenerWrapper(newWorker));
370 newWorker.on("message", listenConnListener);
374 var http = require("http");
375 http.STATUS_CODES[497] = "HTTP Request Sent to HTTPS Port";
376 http.STATUS_CODES[598] = "Network Read Timeout Error";
377 http.STATUS_CODES[599] = "Network Connect Timeout Error";
378 var url = require("url");
379 var dns = require("dns");
381 var gracefulFs = require("graceful-fs");
382 if (!process.isBun) gracefulFs.gracefulify(fs); // Bun + graceful-fs + SVR.JS + requests about static content = 500 Internal Server Error!
384 // Don't use graceful-fs
386 var path = require("path");
387 var hexstrbase64 = undefined;
389 hexstrbase64 = require("./hexstrbase64/index.js");
391 // Don't use hexstrbase64
393 var inspector = undefined;
395 inspector = require("inspector");
397 // Don't use inspector
399 var zlib = require("zlib");
402 tar = require("tar");
408 var formidable = undefined;
410 formidable = require("formidable");
416 var ocsp = undefined;
417 var ocspCache = undefined;
419 ocsp = require("ocsp");
420 ocspCache = new ocsp.Cache();
428 http2 = require("http2");
431 http2.Http2ServerRequest();
433 if (err.name == "NotImplementedError" || err.code == "ERR_NOT_IMPLEMENTED") throw err;
437 http2.__disabled__ = null;
438 http2.createServer = function () {
439 throw new Error("HTTP/2 support is not present");
441 http2.createSecureServer = function () {
442 throw new Error("HTTP/2 support is not present");
444 http2.connect = function () {
445 throw new Error("HTTP/2 support is not present");
447 http2.get = function () {
448 throw new Error("HTTP/2 support is not present");
454 crypto = require("crypto");
455 https = require("https");
461 createServer: function () {
462 throw new Error("Crypto support is not present");
464 connect: function () {
465 throw new Error("Crypto support is not present");
468 throw new Error("Crypto support is not present");
471 http2.createSecureServer = function () {
472 throw new Error("Crypto support is not present");
475 var mime = require("mime-types");
477 var listenAddress = undefined;
478 var sListenAddress = undefined;
486 if (!fs.existsSync(__dirname + "/log")) fs.mkdirSync(__dirname + "/log");
487 if (!fs.existsSync(__dirname + "/mods")) fs.mkdirSync(__dirname + "/mods");
488 if (!fs.existsSync(__dirname + "/temp")) fs.mkdirSync(__dirname + "/temp");
490 var modFiles = fs.readdirSync(__dirname + "/mods").sort();
493 function sizify(bytes, addI) {
494 if (bytes == 0) return "0";
495 if (bytes < 0) bytes = -bytes;
496 var prefixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"];
497 var prefixIndex = Math.floor(Math.log2 ? Math.log2(bytes) / 10 : (Math.log(bytes) / (Math.log(2) * 10)));
498 if (prefixIndex >= prefixes.length - 1) prefixIndex = prefixes.length - 1;
499 var prefixIndexTranslated = Math.pow(2, 10 * prefixIndex);
500 var decimalPoints = 2 - Math.floor(Math.log10 ? Math.log10(bytes / prefixIndexTranslated) : (Math.log(bytes / prefixIndexTranslated) / Math.log(10)));
501 if (decimalPoints < 0) decimalPoints = 0;
502 return (Math.ceil((bytes / prefixIndexTranslated) * Math.pow(10, decimalPoints)) / Math.pow(10, decimalPoints)) + prefixes[prefixIndex] + ((prefixIndex > 0 && addI) ? "i" : "");
506 var osType = os.type();
507 var platform = os.platform();
508 if (platform == "android") {
510 } else if (osType == "Windows_NT" || osType == "WindowsNT") {
511 var arch = os.arch();
512 if (arch == "ia32") {
514 } else if (arch == "x64") {
517 return "Win" + arch.toUpperCase();
519 } else if (osType.indexOf("CYGWIN") == 0) {
521 } else if (osType.indexOf("MINGW") == 0) {
523 } else if (osType.indexOf("MSYS") == 0) {
525 } else if (osType.indexOf("UWIN") == 0) {
527 } else if (osType == "GNU") {
534 function createRegex(regex, isPath) {
535 var regexStrMatch = regex.match(/^\/((?:\\.|[^\/\\])*)\/([a-zA-Z0-9]*)$/);
536 if (!regexStrMatch) throw new Error("Invalid regular expression: " + regex);
537 var searchString = regexStrMatch[1];
538 var modifiers = regexStrMatch[2];
539 if (isPath && !modifiers.match(/i/i) && os.platform() == "win32") modifiers += "i";
540 return new RegExp(searchString, modifiers);
543 // Function to check if IPs are equal
544 function ipMatch(IP1, IP2) {
545 if (!IP1) return true;
546 if (!IP2) return false;
548 // Function to normalize IPv4 address (remove leading zeros)
549 function normalizeIPv4Address(address) {
550 return address.replace(/(^|\.)(?:0(?!\.|$))+/g, "");
553 // Function to expand IPv6 address to full format
554 function expandIPv6Address(address) {
555 var fullAddress = "";
556 var expandedAddress = "";
557 var validGroupCount = 8;
558 var validGroupSize = 4;
561 var extractIpv4 = /([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/;
562 var validateIpv4 = /((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})/;
564 if (validateIpv4.test(address)) {
565 var oldGroups = address.match(extractIpv4);
566 for (var i = 1; i < oldGroups.length; i++) {
567 ipv4 += ("00" + (parseInt(oldGroups[i], 10).toString(16))).slice(-2) + (i == 2 ? ":" : "");
569 address = address.replace(extractIpv4, ipv4);
572 if (address.indexOf("::") == -1) {
573 fullAddress = address;
575 var sides = address.split("::");
576 var groupsPresent = 0;
577 sides.forEach(function (side) {
578 groupsPresent += side.split(":").length;
580 fullAddress += sides[0] + ":";
581 if (validGroupCount - groupsPresent > 1) {
582 fullAddress += "0000:".repeat(validGroupCount - groupsPresent);
584 fullAddress += sides[1];
586 var groups = fullAddress.split(":");
587 for (var i = 0; i < validGroupCount; i++) {
588 if (groups[i].length < validGroupSize) {
589 groups[i] = "0".repeat(validGroupSize - groups[i].length) + groups[i];
591 expandedAddress += (i != validGroupCount - 1) ? groups[i] + ":" : groups[i];
593 return expandedAddress;
596 // Normalize or expand IP addresses
597 IP1 = IP1.toLowerCase();
598 if (IP1 == "localhost") IP1 = "::1";
599 if (IP1.indexOf("::ffff:") == 0) IP1 = IP1.substring(7);
600 if (IP1.indexOf(":") > -1) {
601 IP1 = expandIPv6Address(IP1);
603 IP1 = normalizeIPv4Address(IP1);
606 IP2 = IP2.toLowerCase();
607 if (IP2 == "localhost") IP2 = "::1";
608 if (IP2.indexOf("::ffff:") == 0) IP2 = IP2.substring(7);
609 if (IP2.indexOf(":") > -1) {
610 IP2 = expandIPv6Address(IP2);
612 IP2 = normalizeIPv4Address(IP2);
615 // Check if processed IPs are equal
616 if (IP1 == IP2) return true;
620 function checkForEnabledDirectoryListing(hostname, localAddress) {
621 function matchHostname(hostnameM) {
622 if (typeof hostnameM == "undefined" || hostnameM == "*") {
624 } else if (hostname && hostnameM.indexOf("*.") == 0 && hostnameM != "*.") {
625 var hostnamesRoot = hostnameM.substring(2);
626 if (hostname == hostnamesRoot || (hostname.length > hostnamesRoot.length && hostname.indexOf("." + hostnamesRoot) == hostname.length - hostnamesRoot.length - 1)) {
629 } else if (hostname && hostname == hostnameM) {
635 var main = (configJSON.enableDirectoryListing || configJSON.enableDirectoryListing === undefined);
636 if (!configJSON.enableDirectoryListingVHost) return main;
638 configJSON.enableDirectoryListingVHost.every(function (vhost) {
639 if (matchHostname(vhost.host) && ipMatch(vhost.ip, localAddress)) {
646 if (!vhostP || vhostP.enabled === undefined) return main;
647 else return vhostP.enabled;
650 // IP Block list object
651 function ipBlockList(rawBlockList) {
653 // Initialize the instance with empty arrays
654 if (rawBlockList === undefined) rawBlockList = [];
657 rawtoPreparedMap: [],
662 // Function to normalize IPv4 address (remove leading zeros)
663 function normalizeIPv4Address(address) {
664 return address.replace(/(^|\.)(?:0(?!\.|$))+/g, "");
667 // Function to expand IPv6 address to full format
668 function expandIPv6Address(address) {
669 var fullAddress = "";
670 var expandedAddress = "";
671 var validGroupCount = 8;
672 var validGroupSize = 4;
675 var extractIpv4 = /([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/;
676 var validateIpv4 = /((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})/;
678 if (validateIpv4.test(address)) {
679 var oldGroups = address.match(extractIpv4);
680 for (var i = 1; i < oldGroups.length; i++) {
681 ipv4 += ("00" + (parseInt(oldGroups[i], 10).toString(16))).slice(-2) + (i == 2 ? ":" : "");
683 address = address.replace(extractIpv4, ipv4);
686 if (address.indexOf("::") == -1) {
687 fullAddress = address;
689 var sides = address.split("::");
690 var groupsPresent = 0;
691 sides.forEach(function (side) {
692 groupsPresent += side.split(":").length;
694 fullAddress += sides[0] + ":";
695 if (validGroupCount - groupsPresent > 1) {
696 fullAddress += "0000:".repeat(validGroupCount - groupsPresent);
698 fullAddress += sides[1];
700 var groups = fullAddress.split(":");
701 for (var i = 0; i < validGroupCount; i++) {
702 if (groups[i].length < validGroupSize) {
703 groups[i] = "0".repeat(validGroupSize - groups[i].length) + groups[i];
705 expandedAddress += (i != validGroupCount - 1) ? groups[i] + ":" : groups[i];
707 return expandedAddress;
710 // Convert IPv4 address to an integer representation
711 function ipv4ToInt(ip) {
712 var ips = ip.split(".");
713 return parseInt(ips[0]) * 16777216 + parseInt(ips[1]) * 65536 + parseInt(ips[2]) * 256 + parseInt(ips[3]);
716 // Get IPv4 CIDR block limits (min and max)
717 function getIPv4CIDRLimits(ip, cidrMask) {
718 var ipInt = ipv4ToInt(ip);
719 var exp = Math.pow(2, 32 - cidrMask);
720 var ipMin = Math.floor(ipInt / exp) * exp;
721 var ipMax = ipMin + exp - 1;
728 // Convert IPv6 address to an array of blocks
729 function ipv6ToBlocks(ip) {
730 var ips = ip.split(":");
732 ips.forEach(function (ipe) {
733 ip2s.push(parseInt(ipe));
738 // Get IPv6 CIDR block limits (min and max)
739 function getIPv6CIDRLimits(ip, cidrMask) {
740 var ipBlocks = ipv6ToBlocks(ip);
741 var fieldsToDelete = Math.floor((128 - cidrMask) / 16);
742 var fieldMaskModify = (128 - cidrMask) % 16;
745 for (var i = 0; i < 8; i++) {
746 ipBlockMin.push((i < 8 - fieldsToDelete) ? ((i < 7 - fieldsToDelete) ? ipBlocks[i] : (ipBlocks[i] >> fieldMaskModify << fieldMaskModify)) : 0);
748 for (var i = 0; i < 8; i++) {
749 ipBlockMax.push((i < 8 - fieldsToDelete) ? ((i < 7 - fieldsToDelete) ? ipBlocks[i] : ((ipBlocks[i] >> fieldMaskModify << fieldMaskModify) + Math.pow(2, fieldMaskModify) - 1)) : 65535);
757 // Check if the IPv4 address matches the given CIDR block
758 function checkIfIPv4CIDRMatches(ipInt, cidrObject) {
759 if (cidrObject.v6) return false;
760 return ipInt >= cidrObject.min && ipInt <= cidrObject.max;
763 // Check if the IPv6 address matches the given CIDR block
764 function checkIfIPv6CIDRMatches(ipBlock, cidrObject) {
765 if (!cidrObject.v6) return false;
766 for (var i = 0; i < 8; i++) {
767 if (ipBlock[i] < cidrObject.min[i] || ipBlock[i] > cidrObject.max[i]) return true;
772 // Function to add an IP or CIDR block to the block list
773 instance.add = function (rawValue) {
774 // Add to raw block list
775 instance.raw.push(rawValue);
777 // Initialize variables
778 var beginIndex = instance.prepared.length;
779 var cidrIndex = instance.cidrs.length;
783 // Check if the input contains CIDR notation
784 if (rawValue.indexOf("/") > -1) {
785 var rwArray = rawValue.split("/");
786 cidrMask = rwArray.pop();
787 rawValue = rwArray.join("/");
790 // Normalize the IP address or expand the IPv6 address
791 rawValue = rawValue.toLowerCase();
792 if (rawValue.indexOf("::ffff:") == 0) rawValue = rawValue.substring(7);
793 if (rawValue.indexOf(":") > -1) {
795 rawValue = expandIPv6Address(rawValue);
797 rawValue = normalizeIPv4Address(rawValue);
800 // Add the IP or CIDR block to the appropriate list
804 cidrLimits = getIPv6CIDRLimits(rawValue, cidrMask);
805 cidrLimits.v6 = true;
807 cidrLimits = getIPv4CIDRLimits(rawValue, cidrMask);
808 cidrLimits.v6 = false;
810 instance.cidrs.push(cidrLimits);
811 instance.rawtoPreparedMap.push({
816 instance.prepared.push(rawValue);
817 instance.rawtoPreparedMap.push({
824 // Function to remove an IP or CIDR block from the block list
825 instance.remove = function (ip) {
826 var index = instance.raw.indexOf(ip);
827 if (index == -1) return false;
828 var map = instance.rawtoPreparedMap[index];
829 instance.raw.splice(index, 1);
830 instance.rawtoPreparedMap.splice(index, 1);
832 instance.cidrs.splice(map.index, 1);
834 instance.prepared.splice(map.index, 1);
839 // Function to check if an IP is blocked by the block list
840 instance.check = function (rawValue) {
841 if (instance.raw.length == 0) return false;
844 // Normalize or expand the IP address
845 rawValue = rawValue.toLowerCase();
846 if (rawValue == "localhost") rawValue = "::1";
847 if (rawValue.indexOf("::ffff:") == 0) rawValue = rawValue.substring(7);
848 if (rawValue.indexOf(":") > -1) {
850 rawValue = expandIPv6Address(rawValue);
852 rawValue = normalizeIPv4Address(rawValue);
855 // Check if the IP is in the prepared list
856 if (instance.prepared.indexOf(rawValue) > -1) return true;
858 // Check if the IP is within any CIDR block in the block list
859 if (instance.cidrs.length == 0) return false;
860 var ipParsedObject = (!isIPv6 ? ipv4ToInt : ipv6ToBlocks)(rawValue);
861 var checkMethod = (!isIPv6 ? checkIfIPv4CIDRMatches : checkIfIPv6CIDRMatches);
863 return instance.cidrs.some(function (iCidr) {
864 return checkMethod(ipParsedObject, iCidr);
868 // Add initial raw block list values to the instance
869 rawBlockList.forEach(function (rbe) {
876 // Generate V8-style error stack from Error object.
877 function generateErrorStack(errorObject) {
878 // Split the error stack by newlines.
879 var errorStack = errorObject.stack ? errorObject.stack.split("\n") : [];
881 // If the error stack starts with the error name, return the original stack (it is V8-style then).
882 if (errorStack.some(function (errorStackLine) {
883 return (errorStackLine.indexOf(errorObject.name) == 0);
885 return errorObject.stack;
888 // Create a new error stack with the error name and code (if available).
889 var newErrorStack = [errorObject.name + (errorObject.code ? ": " + errorObject.code : "") + (errorObject.message == "" ? "" : ": " + errorObject.message)];
891 // Process each line of the original error stack.
892 errorStack.forEach(function (errorStackLine) {
893 if (errorStackLine != "") {
894 // Split the line into function and location parts (if available).
895 var errorFrame = errorStackLine.split("@");
897 if (errorFrame.length > 1) location = errorFrame.pop();
898 var func = errorFrame.join("@");
900 // Build the new error stack entry with function and location information.
901 newErrorStack.push(" at " + (func == "" ? (!location || location == "" ? "<anonymous>" : location) : (func + (!location || location == "" ? "" : " (" + location + ")"))));
905 // Join the new error stack entries with newlines and return the final stack.
906 return newErrorStack.join("\n");
909 function calculateBroadcastIPv4FromCidr(ipWithCidr) {
910 // Check if CIDR notation is valid, if it's not, return null
911 if (!ipWithCidr) return null;
912 var ipCA = ipWithCidr.split("/");
913 if (ipCA.length != 2) return null;
915 // Extract IP and mask (numberic format)
917 var mask = parseInt(ipCA[1]);
919 return ip.split(".").map(function (num, index) {
920 // Calculate resulting 8-bit
921 var power = Math.max(Math.min(mask - (index * 8), 8), 0);
922 return ((parseInt(num) & ((Math.pow(2, power) - 1) << (8 - power))) | Math.pow(2, 8 - power) - 1).toString();
926 function calculateNetworkIPv4FromCidr(ipWithCidr) {
927 // Check if CIDR notation is valid, if it's not, return null
928 if (!ipWithCidr) return null;
929 var ipCA = ipWithCidr.split("/");
930 if (ipCA.length != 2) return null;
932 // Extract IP and mask (numberic format)
934 var mask = parseInt(ipCA[1]);
936 return ip.split(".").map(function (num, index) {
937 // Calculate resulting 8-bit
938 var power = Math.max(Math.min(mask - (index * 8), 8), 0);
939 return ((parseInt(num) & ((Math.pow(2, power) - 1) << (8 - power)))).toString();
943 var inspectorURL = undefined;
946 inspectorURL = inspector.url();
949 // Failed to get inspector URL
952 if (!process.stdout.isTTY && !inspectorURL) {
953 // When stdout is not a terminal and not attached to an Node.JS inspector, disable it to improve performance of SVR.JS
954 console.log = function () {};
955 process.stdout.write = function () {};
956 process.stdout._write = function () {};
957 process.stdout._writev = function () {};
960 // IP and network inteface-related
964 ifaces = os.networkInterfaces();
969 var brdIPs = ["255.255.255.255", "127.255.255.255", "0.255.255.255"];
970 var netIPs = ["127.0.0.0"];
972 Object.keys(ifaces).forEach(function (ifname) {
974 ifaces[ifname].forEach(function (iface) {
975 if (iface.family !== "IPv4" || iface.internal !== false) {
979 ips.push(ifname + ":" + alias, iface.address);
981 ips.push(ifname, iface.address);
983 brdIPs.push(calculateBroadcastIPv4FromCidr(iface.cidr));
984 netIPs.push(calculateNetworkIPv4FromCidr(iface.cidr));
989 if (ips.length == 0) {
990 Object.keys(ifaces).forEach(function (ifname) {
992 ifaces[ifname].forEach(function (iface) {
993 if (iface.family !== "IPv6" || iface.internal !== false) {
997 ips.push(ifname + ":" + alias, iface.address);
999 ips.push(ifname, iface.address);
1006 // Server startup attempt counter
1008 var attmtsRedir = 5;
1010 // Some variables...
1011 var errors = os.constants.errno;
1012 var timestamp = new Date().getTime();
1014 // Server IP address
1015 var host = ips[(ips.length) - 1];
1016 if (!host) host = "[offline]";
1018 // Public IP address-related
1019 var ipRequestCompleted = false;
1020 var ipRequestGotError = false;
1021 if (host != "[offline]" || ifaceEx) {
1022 var ipRequest = (crypto.__disabled__ !== undefined ? http : https).get({
1023 host: "api64.ipify.org",
1024 port: (crypto.__disabled__ !== undefined ? 80 : 443),
1027 "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")
1031 ipRequest.removeAllListeners("timeout");
1032 res.on("data", function (d) {
1033 if (res.statusCode != 200) {
1034 ipRequestCompleted = true;
1035 process.emit("ipRequestCompleted");
1038 pubip = d.toString();
1040 ipRequestCompleted = true;
1041 process.emit("ipRequestCompleted");
1043 var callbackDone = false;
1045 var dnsTimeout = setTimeout(function () {
1046 callbackDone = true;
1047 ipRequestCompleted = true;
1048 process.emit("ipRequestCompleted");
1052 dns.reverse(pubip, function (err, hostnames) {
1053 if (callbackDone) return;
1054 clearTimeout(dnsTimeout);
1055 if (!err && hostnames.length > 0) domain = hostnames[0];
1056 ipRequestCompleted = true;
1057 process.emit("ipRequestCompleted");
1060 clearTimeout(dnsTimeout);
1061 callbackDone = true;
1062 ipRequestCompleted = true;
1063 process.emit("ipRequestCompleted");
1068 ipRequest.on("error", function () {
1069 if (crypto.__disabled__ || ipRequestGotError) {
1070 ipRequestCompleted = true;
1071 process.emit("ipRequestCompleted");
1073 ipRequestGotError = true;
1076 ipRequest.on("timeout", function () {
1077 if (crypto.__disabled__ || ipRequestGotError) {
1078 ipRequestCompleted = true;
1079 process.emit("ipRequestCompleted");
1081 ipRequestGotError = true;
1085 if (!crypto.__disabled) {
1086 var ipRequest2 = https.get({
1087 host: "api.seeip.org",
1091 "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")
1095 ipRequest2.removeAllListeners("timeout");
1096 res.on("data", function (d) {
1097 if (res.statusCode != 200) {
1098 ipRequestCompleted = true;
1099 process.emit("ipRequestCompleted");
1102 pubip = d.toString();
1104 ipRequestCompleted = true;
1105 process.emit("ipRequestCompleted");
1107 var callbackDone = false;
1109 var dnsTimeout = setTimeout(function () {
1110 callbackDone = true;
1111 ipRequestCompleted = true;
1112 process.emit("ipRequestCompleted");
1116 dns.reverse(pubip, function (err, hostnames) {
1117 if (callbackDone) return;
1118 clearTimeout(dnsTimeout);
1119 if (!err && hostnames.length > 0) domain = hostnames[0];
1120 ipRequestCompleted = true;
1121 process.emit("ipRequestCompleted");
1124 clearTimeout(dnsTimeout);
1125 callbackDone = true;
1126 ipRequestCompleted = true;
1127 process.emit("ipRequestCompleted");
1132 ipRequest2.on("error", function () {
1133 if (crypto.__disabled__ || ipRequestGotError) {
1134 ipRequestCompleted = true;
1135 process.emit("ipRequestCompleted");
1137 ipRequestGotError = true;
1140 ipRequest2.on("timeout", function () {
1141 if (crypto.__disabled__ || ipRequestGotError) {
1142 ipRequestCompleted = true;
1143 process.emit("ipRequestCompleted");
1145 ipRequestGotError = true;
1150 ipRequestCompleted = true;
1153 function ipStatusCallback(callback) {
1154 if (ipRequestCompleted) {
1157 process.once("ipRequestCompleted", callback);
1161 var configJSON = {};
1162 var configJSONRErr = undefined;
1163 var configJSONPErr = undefined;
1164 if (fs.existsSync(__dirname + "/config.json")) {
1165 var configJSONf = "";
1167 configJSONf = fs.readFileSync(__dirname + "/config.json"); // Read JSON File
1169 configJSON = JSON.parse(configJSONf); // Parse JSON
1171 configJSONPErr = err2;
1174 configJSONRErr = err2;
1178 // Default server configuration properties
1179 var wwwredirect = false;
1180 var rawBlockList = [];
1182 var page404 = "404.html";
1183 var serverAdmin = "[no contact information]";
1184 var stackHidden = false;
1185 var exposeServerVersion = true;
1186 var rewriteMap = [];
1187 var allowStatus = true;
1188 var 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])$/"];
1189 var enableIPSpoofing = false;
1191 var disableNonEncryptedServer = false;
1192 var disableToHTTPSRedirect = false;
1193 var nonStandardCodesRaw = [];
1194 var disableUnusedWorkerTermination = false;
1195 var rewriteDirtyURLs = false;
1196 var errorPages = [];
1197 var useWebRootServerSideScript = true;
1198 var exposeModsInErrorPages = true;
1199 var disableTrailingSlashRedirects = false;
1200 var environmentVariables = {};
1201 var wwwrootPostfixesVHost = [];
1202 var wwwrootPostfixPrefixesVHost = [];
1203 var allowDoubleSlashes = false;
1204 var allowPostfixDoubleSlashes = false;
1206 // Get properties from config.json
1207 if (configJSON.blacklist != undefined) rawBlockList = configJSON.blacklist;
1208 if (configJSON.wwwredirect != undefined) wwwredirect = configJSON.wwwredirect;
1209 if (configJSON.port != undefined) port = configJSON.port;
1210 if (configJSON.pubport != undefined) pubport = configJSON.pubport;
1211 if (typeof port === "string") {
1212 if (port.match(/^[0-9]+$/)) {
1213 port = parseInt(port);
1215 var portLMatch = port.match(/^(\[[^ \]@\/\\]+\]|[^ \]\[:@\/\\]+):([0-9]+)$/);
1217 listenAddress = portLMatch[1].replace(/^\[|\]$/g, "").replace(/^::ffff:/i, "");
1218 port = parseInt(portLMatch[2]);
1222 if (configJSON.domian != undefined) domain = configJSON.domian;
1223 if (configJSON.domain != undefined) domain = configJSON.domain;
1224 if (configJSON.sport != undefined) sport = configJSON.sport;
1225 if (typeof sport === "string") {
1226 if (sport.match(/^[0-9]+$/)) {
1227 sport = parseInt(sport);
1229 var sportLMatch = sport.match(/^(\[[^ \]@\/\\]+\]|[^ \]\[:@\/\\]+):([0-9]+)$/);
1231 sListenAddress = sportLMatch[1].replace(/^\[|\]$/g, "").replace(/^::ffff:/i, "");
1232 sport = parseInt(sportLMatch[2]);
1236 if (configJSON.spubport != undefined) spubport = configJSON.spubport;
1237 if (configJSON.page404 != undefined) page404 = configJSON.page404;
1238 if (configJSON.serverAdministratorEmail != undefined) serverAdmin = configJSON.serverAdministratorEmail;
1239 if (configJSON.nonStandardCodes != undefined) nonStandardCodesRaw = configJSON.nonStandardCodes;
1240 if (configJSON.stackHidden != undefined) stackHidden = configJSON.stackHidden;
1241 if (configJSON.users != undefined) users = configJSON.users;
1242 if (configJSON.exposeServerVersion != undefined) exposeServerVersion = configJSON.exposeServerVersion;
1243 if (configJSON.rewriteMap != undefined) rewriteMap = configJSON.rewriteMap;
1244 if (configJSON.allowStatus != undefined) allowStatus = configJSON.allowStatus;
1245 if (configJSON.dontCompress != undefined) dontCompress = configJSON.dontCompress;
1246 if (configJSON.enableIPSpoofing != undefined) enableIPSpoofing = configJSON.enableIPSpoofing;
1247 if (configJSON.secure != undefined) secure = secure || configJSON.secure;
1248 if (configJSON.sni != undefined) sni = configJSON.sni;
1249 if (configJSON.disableNonEncryptedServer != undefined) disableNonEncryptedServer = configJSON.disableNonEncryptedServer;
1250 if (configJSON.disableToHTTPSRedirect != undefined) disableToHTTPSRedirect = configJSON.disableToHTTPSRedirect;
1251 if (configJSON.disableUnusedWorkerTermination != undefined) disableUnusedWorkerTermination = configJSON.disableUnusedWorkerTermination;
1252 if (configJSON.rewriteDirtyURLs != undefined) rewriteDirtyURLs = configJSON.rewriteDirtyURLs;
1253 if (configJSON.errorPages != undefined) errorPages = configJSON.errorPages;
1254 if (configJSON.useWebRootServerSideScript != undefined) useWebRootServerSideScript = configJSON.useWebRootServerSideScript;
1255 if (configJSON.exposeModsInErrorPages != undefined) exposeModsInErrorPages = configJSON.exposeModsInErrorPages;
1256 if (configJSON.disableTrailingSlashRedirects != undefined) disableTrailingSlashRedirects = configJSON.disableTrailingSlashRedirects;
1257 if (configJSON.environmentVariables != undefined) environmentVariables = configJSON.environmentVariables;
1258 if (configJSON.wwwrootPostfixesVHost != undefined) wwwrootPostfixesVHost = configJSON.wwwrootPostfixesVHost;
1259 if (configJSON.wwwrootPostfixPrefixesVHost != undefined) wwwrootPostfixPrefixesVHost = configJSON.wwwrootPostfixPrefixesVHost;
1260 if (configJSON.allowDoubleSlashes != undefined) allowDoubleSlashes = configJSON.allowDoubleSlashes;
1261 if (configJSON.allowPostfixDoubleSlashes != undefined) allowPostfixDoubleSlashes = configJSON.allowPostfixDoubleSlashes;
1263 var wwwrootError = null;
1265 if (cluster.isPrimary || cluster.isPrimary === undefined) process.chdir(configJSON.wwwroot != undefined ? configJSON.wwwroot : __dirname);
1271 Object.keys(environmentVariables).forEach(function (key) {
1272 process.env[key] = environmentVariables[key];
1275 // Failed to set environment variables.
1278 // Compability for older mods
1279 configJSON.version = version;
1280 configJSON.productName = "SVR.JS";
1282 var blocklist = ipBlockList(rawBlockList);
1284 var nonStandardCodes = [];
1285 nonStandardCodesRaw.forEach(function (nonStandardCodeRaw) {
1287 Object.keys(nonStandardCodeRaw).forEach(function (nsKey) {
1288 if (nsKey != "users") {
1289 newObject[nsKey] = nonStandardCodeRaw[nsKey];
1291 newObject["users"] = ipBlockList(nonStandardCodeRaw.users);
1294 nonStandardCodes.push(newObject);
1297 var customHeaders = (configJSON.customHeaders == undefined ? {} : JSON.parse(JSON.stringify(configJSON.customHeaders)));
1298 if (exposeServerVersion) {
1299 customHeaders["Server"] = "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")";
1301 customHeaders["Server"] = "SVR.JS";
1304 function getCustomHeaders() {
1305 return JSON.parse(JSON.stringify(customHeaders));
1310 vnum = process.config.variables.node_module_version;
1312 // Version number not retrieved
1315 if (vnum === undefined) vnum = 0;
1316 if (process.isBun) vnum = 64;
1318 // SVR.JS path sanitizer function
1319 function sanitizeURL(resource, allowDoubleSlashes) {
1320 if (resource == "*" || resource == "") return resource;
1321 // Remove null characters
1322 resource = resource.replace(/%00|\0/g, "");
1323 // Check if URL is malformed (e.g. %c0%af or %u002f or simply %as)
1324 if (resource.match(/%(?:c[01]|f[ef]|(?![0-9a-f]{2}).{2}|.{0,1}$)/i)) throw new URIError("URI malformed");
1325 // Decode URL-encoded characters while preserving certain characters
1326 resource = resource.replace(/%([0-9a-f]{2})/gi, function (match, hex) {
1327 var decodedChar = String.fromCharCode(parseInt(hex, 16));
1328 return /(?!["<>^`{|}?#%])[!-~]/.test(decodedChar) ? decodedChar : "%" + hex;
1330 // Encode certain characters
1331 resource = resource.replace(/[<>^`{|}]]/g, function (character) {
1332 var charCode = character.charCodeAt(0);
1333 return "%" + (charCode < 16 ? "0" : "") + charCode.toString(16).toUpperCase();
1335 var sanitizedResource = resource;
1336 // Ensure the resource starts with a slash
1337 if (resource[0] != "/") sanitizedResource = "/" + sanitizedResource;
1338 // Convert backslashes to slashes and handle duplicate slashes
1339 sanitizedResource = sanitizedResource.replace(/\\/g, "/").replace(allowDoubleSlashes ? /\/{3,}/g : /\/+/g, "/");
1340 // Handle relative navigation (e.g., "/./", "/../", "../", "./"), also remove trailing dots in paths
1341 sanitizedResource = sanitizedResource.replace(/\/\.(?:\.{2,})?(?=\/|$)/g, "").replace(/([^.\/])\.+(?=\/|$)/g, "$1");
1342 while (sanitizedResource.match(/\/(?!\.\.\/)[^\/]+\/\.\.(?=\/|$)/)) {
1343 sanitizedResource = sanitizedResource.replace(/\/(?!\.\.\/)[^\/]+\/\.\.(?=\/|$)/g, "");
1345 sanitizedResource = sanitizedResource.replace(/\/\.\.(?=\/|$)/g, "");
1346 if (sanitizedResource.length == 0) return "/";
1347 else return sanitizedResource;
1350 // SVR.JS URL parser function
1351 function parseURL(uri, prepend) {
1352 // Replace newline characters with its respective URL encodings
1353 uri = uri.replace(/\r/g, "%0D").replace(/\n/g, "%0A");
1355 // If URL begins with a slash, prepend a string if available
1356 if (prepend && uri[0] == "/") uri = prepend.replace(/\/+$/,"") + uri;
1358 // Determine if URL has slashes
1359 var hasSlashes = (uri.indexOf("/") != -1);
1361 // Parse the URL using regular expression
1362 var parsedURI = uri.match(/^(?:([^:]+:)(\/\/)?)?(?:([^@]+)@)?([^:\/?#\*]+|\[[^\*]\/]\])?(?::([0-9]+))?(\*|\/[^?#]*)?(\?[^#]*)?(#[\S\s]*)?/);
1363 // Match 1: protocol
1364 // Match 2: slashes after protocol
1365 // Match 3: authentication credentials
1366 // Match 4: host name
1368 // Match 6: path name
1369 // Match 7: query string
1372 // If regular expression didn't match the entire URL, throw an error
1373 if (parsedURI[0].length != uri.length) throw new Error("Invalid URL: " + uri);
1375 // If match 1 is not empty, set the slash variable based on state of match 2
1376 if (parsedURI[1]) hasSlashes = (parsedURI[2] == "//");
1378 // If match 6 is empty and URL has slashes, set it to a slash.
1379 if (hasSlashes && !parsedURI[6]) parsedURI[6] = "/";
1381 // If match 4 contains Unicode characters, convert it to Punycode. If the result is an empty string, throw an error
1382 if (parsedURI[4] && !parsedURI[4].match(/^[a-zA-Z0-9\.\-]+$/)) {
1383 parsedURI[4] = url.domainToASCII(parsedURI[4]);
1384 if (!parsedURI[4]) throw new Error("Invalid URL: " + uri);
1387 // Create a new URL object
1388 var uobject = new url.Url();
1390 // Populate a URL object
1391 if (hasSlashes) uobject.slashes = true;
1392 if (parsedURI[1]) uobject.protocol = parsedURI[1];
1393 if (parsedURI[3]) uobject.auth = parsedURI[3];
1395 uobject.host = parsedURI[4] + (parsedURI[5] ? (":" + parsedURI[5]) : "");
1396 if (parsedURI[4][0] == "[") uobject.hostname = parsedURI[4].substring(1, parsedURI[4].length-1);
1397 else uobject.hostname = parsedURI[4];
1399 if (parsedURI[5]) uobject.port = parsedURI[5];
1400 if (parsedURI[6]) uobject.pathname = parsedURI[6];
1402 uobject.search = parsedURI[7];
1403 // Parse query strings
1404 var qobject = Object.create(null);
1405 var parsedQuery = parsedURI[7].substring(1).match(/([^&=]*)(?:=([^&]*))?/g);
1406 parsedQuery.forEach(function (qp) {
1407 if (qp.length > 0) {
1408 var parsedQP = qp.match(/([^&=]*)(?:=([^&]*))?/);
1410 qobject[parsedQP[1]] = parsedQP[2] ? parsedQP[2] : "";
1414 uobject.query = qobject;
1416 uobject.query = Object.create(null);
1418 if (parsedURI[8]) uobject.hash = parsedURI[8];
1419 if (uobject.pathname) uobject.path = uobject.pathname + (uobject.search ? uobject.search : "");
1420 uobject.href = (uobject.protocol ? (uobject.protocol + (uobject.slashes ? "//" : "")) : "") + (uobject.auth ? (uobject.auth + "@") : "") + (uobject.hostname ? uobject.hostname : "") + (uobject.path ? uobject.path : "") + (uobject.hash ? uobject.hash : "");
1425 // Node.JS mojibake URL fixing function
1426 function fixNodeMojibakeURL(string) {
1430 Buffer.from(string, "latin1").forEach(function (value) {
1432 encoded += "%" + (value < 16 ? "0" : "") + value.toString(16).toUpperCase();
1434 encoded += String.fromCodePoint(value);
1438 //Upper case the URL encodings
1439 return encoded.replace(/%[0-9a-f-A-F]{2}/g, function (match) {
1440 return match.toUpperCase();
1449 if (!configJSON.key) configJSON.key = "cert/key.key";
1450 if (!configJSON.cert) configJSON.cert = "cert/cert.crt";
1452 key = "SSL DISABLED";
1453 cert = "SSL DISABLED";
1454 configJSON.cert = "SSL DISABLED";
1455 configJSON.key = "SSL DISABLED";
1458 if (!fs.existsSync(__dirname + "/config.json")) {
1462 var certificateError = null;
1463 var sniReDos = false;
1468 key = fs.readFileSync((configJSON.key[0] != "/" && !configJSON.key.match(/^[A-Z0-9]:\\/)) ? __dirname + "/" + configJSON.key : configJSON.key).toString();
1469 cert = fs.readFileSync((configJSON.cert[0] != "/" && !configJSON.cert.match(/^[A-Z0-9]:\\/)) ? __dirname + "/" + configJSON.cert : configJSON.cert).toString();
1470 var sniNames = Object.keys(sni);
1471 var sniCredentials = [];
1472 sniNames.forEach(function (sniName) {
1473 if (typeof sniName === "string" && sniName.match(/\*[^*.:]*\*[^*.:]*(?:\.|:|$)/)) {
1476 sniCredentials.push({
1478 cert: fs.readFileSync((sni[sniName].cert[0] != "/" && !sni[sniName].cert.match(/^[A-Z0-9]:\\/)) ? __dirname + "/" + sni[sniName].cert : sni[sniName].cert).toString(),
1479 key: fs.readFileSync((sni[sniName].key[0] != "/" && !sni[sniName].key.match(/^[A-Z0-9]:\\/)) ? __dirname + "/" + sni[sniName].key : sni[sniName].key).toString()
1483 certificateError = err;
1487 var logFile = undefined;
1488 var logSync = false;
1493 if (configJSON.enableLogging || configJSON.enableLogging == undefined) {
1495 fs.appendFileSync(__dirname + "/log/" + (cluster.isPrimary ? "master" : (cluster.isPrimary === undefined ? "singlethread" : "worker")) + "-" + timestamp + ".log", "[" + new Date().toISOString() + "] " + s + "\r\n");
1498 logFile = fs.createWriteStream(__dirname + "/log/" + (cluster.isPrimary ? "master" : (cluster.isPrimary === undefined ? "singlethread" : "worker")) + "-" + timestamp + ".log", {
1502 logFile.on("error", function (err) {
1503 if (!s.match(/^SERVER WARNING MESSAGE(?: \[Request Id: [0-9a-f]{6}\])?: There was a problem while saving logs! Logs will not be kept in log file\. Reason: /) && !reallyExiting) serverconsole.locwarnmessage("There was a problem while saving logs! Logs will not be kept in log file. Reason: " + err.message);
1506 if (logFile.writable) {
1507 logFile.write("[" + new Date().toISOString() + "] " + s + "\r\n");
1509 throw new Error("Log file stream is closed.");
1514 if (!s.match(/^SERVER WARNING MESSAGE(?: \[Request Id: [0-9a-f]{6}\])?: There was a problem while saving logs! Logs will not be kept in log file\. Reason: /) && !reallyExiting) serverconsole.locwarnmessage("There was a problem while saving logs! Logs will not be kept in log file. Reason: " + err.message);
1518 // Server console function
1519 var serverconsole = {
1520 climessage: function (msg) {
1521 if (msg.indexOf("\n") != -1) {
1522 msg.split("\n").forEach(function (nmsg) {
1523 serverconsole.climessage(nmsg);
1527 console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m: " + msg);
1528 LOG("SERVER CLI MESSAGE: " + msg);
1531 reqmessage: function (msg) {
1532 if (msg.indexOf("\n") != -1) {
1533 msg.split("\n").forEach(function (nmsg) {
1534 serverconsole.reqmessage(nmsg);
1538 console.log("\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m: " + msg + "\x1b[37m\x1b[0m");
1539 LOG("SERVER REQUEST MESSAGE: " + msg);
1542 resmessage: function (msg) {
1543 if (msg.indexOf("\n") != -1) {
1544 msg.split("\n").forEach(function (nmsg) {
1545 serverconsole.resmessage(nmsg);
1549 console.log("\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m: " + msg + "\x1b[37m\x1b[0m");
1550 LOG("SERVER RESPONSE MESSAGE: " + msg);
1553 errmessage: function (msg) {
1554 if (msg.indexOf("\n") != -1) {
1555 msg.split("\n").forEach(function (nmsg) {
1556 serverconsole.errmessage(nmsg);
1560 console.log("\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m: " + msg + "\x1b[37m\x1b[0m");
1561 LOG("SERVER RESPONSE ERROR MESSAGE: " + msg);
1564 locerrmessage: function (msg) {
1565 if (msg.indexOf("\n") != -1) {
1566 msg.split("\n").forEach(function (nmsg) {
1567 serverconsole.locerrmessage(nmsg);
1571 console.log("\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m: " + msg + "\x1b[40m\x1b[0m");
1572 LOG("SERVER ERROR MESSAGE: " + msg);
1575 locwarnmessage: function (msg) {
1576 if (msg.indexOf("\n") != -1) {
1577 msg.split("\n").forEach(function (nmsg) {
1578 serverconsole.locwarnmessage(nmsg);
1582 console.log("\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m: " + msg + "\x1b[40m\x1b[0m");
1583 LOG("SERVER WARNING MESSAGE: " + msg);
1586 locmessage: function (msg) {
1587 if (msg.indexOf("\n") != -1) {
1588 msg.split("\n").forEach(function (nmsg) {
1589 serverconsole.locmessage(nmsg);
1593 console.log("\x1b[1mSERVER MESSAGE\x1b[22m: " + msg);
1594 LOG("SERVER MESSAGE: " + msg);
1599 // Wrap around process.exit, so that log contents can be flushed.
1600 process.unsafeExit = process.exit;
1601 process.exit = function (code) {
1602 if (logFile && logFile.writable && !logFile.pending) {
1604 logFile.close(function () {
1605 logFile = undefined;
1607 process.unsafeExit(code);
1609 if (process.isBun) {
1610 setInterval(function () {
1611 if (!logFile.writable) {
1612 logFile = undefined;
1614 process.unsafeExit(code);
1618 setTimeout(function () {
1619 logFile = undefined;
1621 process.unsafeExit(code);
1622 }, 10000); // timeout
1624 logFile = undefined;
1626 process.unsafeExit(code);
1630 process.unsafeExit(code);
1634 // SVR.JS mod loader
1635 var modLoadingErrors = [];
1636 var SSJSError = undefined;
1638 // Load mods if the `disableMods` flag is not set
1640 // Define the modloader folder name
1641 var modloaderFolderName = "modloader";
1642 if (cluster.isPrimary === false) {
1643 // If not the master process, create a unique modloader folder name for each worker
1644 modloaderFolderName = ".modloader_w" + Math.floor(Math.random() * 65536);
1647 // Define the temporary server-side JavaScript file name
1648 var tempServerSideScriptName = "serverSideScript.js";
1649 if (!(process.isBun && process.versions.bun && process.versions.bun[0] == "0") && cluster.isPrimary === false) {
1650 // If not the master process and it's not Bun, create a unique temporary server-side JavaScript file name for each worker
1651 tempServerSideScriptName = ".serverSideScript_w" + Math.floor(Math.random() * 65536) + ".js";
1654 // Iterate through the list of mod files
1655 modFiles.forEach(function (modFileRaw) {
1656 // Build the path to the current mod file
1657 var modFile = __dirname + "/mods/" + modFileRaw;
1660 // Try creating the modloader folder (if not already exists)
1662 fs.mkdirSync(__dirname + "/temp/" + modloaderFolderName);
1664 // If the folder already exists, continue to the next step
1665 if (err.code != "EEXIST") {
1666 // If there was another error, try creating the temp folder and then the modloader folder again
1667 fs.mkdirSync(__dirname + "/temp");
1669 fs.mkdirSync(__dirname + "/temp/" + modloaderFolderName);
1671 // If there was another error, throw it
1672 if (err.code != "EEXIST") throw err;
1677 // Create a subfolder for the current mod within the modloader folder
1678 fs.mkdirSync(__dirname + "/temp/" + modloaderFolderName + "/" + modFileRaw);
1680 // If there was an error creating the folder, ignore it if it's a known error
1681 if (err.code != "EEXIST" && err.code != "ENOENT") throw err;
1682 // Some other SVR.JS process may have created the files.
1685 // Check if the current mod file is a regular file
1686 if (fs.statSync(modFile).isFile()) {
1688 // Determine if the mod file is a ".tar.gz" file or not
1689 if (modFile.indexOf(".tar.gz") == modFile.length - 7) {
1690 // If it's a ".tar.gz" file, extract its contents using `tar`
1691 if (tar._errored) throw tar._errored;
1695 C: __dirname + "/temp/" + modloaderFolderName + "/" + modFileRaw
1698 // If it's not a ".tar.gz" file, throw an error about `svrmodpack` support being dropped
1699 throw new Error("This version of SVR.JS no longer supports \"svrmodpack\" library for SVR.JS mods. Please consider using newer mods with .tar.gz format.");
1702 // Initialize variables for mod loading
1703 var Mod = undefined;
1704 var mod = undefined;
1706 // Attempt to require the mod's index.js file, retrying up to 3 times in case of syntax errors
1707 for (var j = 0; j < 3; j++) {
1709 Mod = require("./temp/" + modloaderFolderName + "/" + modFileRaw + "/index.js");
1713 if (j >= 2 || err.name == "SyntaxError") throw err;
1714 // Wait for a short time before retrying
1715 var now = Date.now();
1716 while (Date.now() - now < 2);
1717 // Try reloading mod
1721 // Add the loaded mod to the mods list
1724 // Attempt to read the mod's info file, retrying up to 3 times
1725 for (var j = 0; j < 3; j++) {
1727 modInfos.push(JSON.parse(fs.readFileSync(__dirname + "/temp/" + modloaderFolderName + "/" + modFileRaw + "/mod.info")));
1731 // If failed to read info file, add a placeholder entry to modInfos with an error message
1733 name: "Unknown mod (" + modFileRaw + ";" + err.message + ")",
1737 // Wait for a short time before retrying
1738 var now = Date.now();
1739 while (Date.now() - now < 2);
1740 // Try reloading mod info
1744 modLoadingErrors.push({
1752 // Determine path of server-side script file
1753 var SSJSPath = "./serverSideScript.js";
1754 if (!useWebRootServerSideScript) SSJSPath = __dirname + "/serverSideScript.js";
1756 // Check if a custom server side script file exists
1757 if (fs.existsSync(SSJSPath) && fs.statSync(SSJSPath).isFile()) {
1759 // Prepend necessary modules and variables to the custom server side script
1760 var modhead = "var readline = require('readline');\r\nvar os = require('os');\r\nvar http = require('http');\r\nvar url = require('url');\r\nvar fs = require('fs');\r\nvar path = require('path');\r\n" + (hexstrbase64 === undefined ? "" : "var hexstrbase64 = require('../hexstrbase64/index.js');\r\n") + (crypto.__disabled__ === undefined ? "var crypto = require('crypto');\r\nvar https = require('https');\r\n" : "") + "var stream = require('stream');\r\nvar customvar1;\r\nvar customvar2;\r\nvar customvar3;\r\nvar customvar4;\r\n\r\nfunction Mod() {}\r\nMod.prototype.callback = function callback(req, res, serverconsole, responseEnd, href, ext, uobject, search, defaultpage, users, page404, head, foot, fd, elseCallback, configJSON, callServerError, getCustomHeaders, origHref, redirect, parsePostData, authUser) {\r\nreturn function () {\r\nvar disableEndElseCallbackExecute = false;\r\nfunction filterHeaders(e){var r={};return Object.keys(e).forEach((function(t){null!==e[t]&&void 0!==e[t]&&(\"object\"==typeof e[t]?r[t]=JSON.parse(JSON.stringify(e[t])):r[t]=e[t])})),r}\r\nfunction checkHostname(e){if(void 0===e||\"*\"==e)return!0;if(req.headers.host&&0==e.indexOf(\"*.\")&&\"*.\"!=e){var r=e.substring(2);if(req.headers.host==r||req.headers.host.indexOf(\".\"+r)==req.headers.host.length-r.length-1)return!0}else if(req.headers.host&&req.headers.host==e)return!0;return!1}\r\nfunction checkHref(e){return href==e||\"win32\"==os.platform()&&href.toLowerCase()==e.toLowerCase()}\r\n";
1761 var modfoot = "\r\nif(!disableEndElseCallbackExecute) {\r\ntry{\r\nelseCallback();\r\n} catch(err) {\r\n}\r\n}\r\n}\r\n}\r\nmodule.exports = Mod;";
1762 // Write the modified server side script to the temp folder
1763 fs.writeFileSync(__dirname + "/temp/" + tempServerSideScriptName, modhead + fs.readFileSync(SSJSPath) + modfoot);
1765 // Initialize variables for server side script loading
1766 var aMod = undefined;
1767 var amod = undefined;
1769 // Attempt to require the custom server side script, retrying up to 5 times
1770 for (var i = 0; i < 5; i++) {
1772 aMod = require("./temp/" + tempServerSideScriptName);
1776 if (i >= 4 || err.name == "SyntaxError") throw err;
1777 // Wait for a short time before retrying
1778 var now = Date.now();
1779 while (Date.now() - now < 2);
1780 // Try reloading mod
1784 // Add the loaded server side script to the mods list
1794 function sha256(s) {
1795 if (crypto.__disabled__ === undefined) {
1796 var hash = crypto.createHash("SHA256");
1798 return hash.digest("hex");
1803 function safeAdd(x, y) {
1804 var lsw = (x & 0xFFFF) + (y & 0xFFFF);
1805 var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
1806 return (msw << 16) | (lsw & 0xFFFF);
1810 return (X >>> n) | (X << (32 - n));
1817 function Ch(x, y, z) {
1818 return ((x & y) ^ ((~x) & z));
1821 function Maj(x, y, z) {
1822 return ((x & y) ^ (x & z) ^ (y & z));
1825 function Sigma0256(x) {
1826 return (S(x, 2) ^ S(x, 13) ^ S(x, 22));
1829 function Sigma1256(x) {
1830 return (S(x, 6) ^ S(x, 11) ^ S(x, 25));
1833 function Gamma0256(x) {
1834 return (S(x, 7) ^ S(x, 18) ^ R(x, 3));
1837 function Gamma1256(x) {
1838 return (S(x, 17) ^ S(x, 19) ^ R(x, 10));
1841 function coreSha256(m, l) {
1842 var K = new Array(0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2);
1843 var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
1844 var W = new Array(64);
1845 var a, b, c, d, e, f, g, h, i, j;
1848 m[l >> 5] |= 0x80 << (24 - l % 32);
1849 m[((l + 64 >> 9) << 4) + 15] = l;
1851 for (var i = 0; i < m.length; i += 16) {
1861 for (var j = 0; j < 64; j++) {
1862 if (j < 16) W[j] = m[j + i];
1863 else W[j] = safeAdd(safeAdd(safeAdd(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
1865 T1 = safeAdd(safeAdd(safeAdd(safeAdd(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
1866 T2 = safeAdd(Sigma0256(a), Maj(a, b, c));
1875 a = safeAdd(T1, T2);
1878 HASH[0] = safeAdd(a, HASH[0]);
1879 HASH[1] = safeAdd(b, HASH[1]);
1880 HASH[2] = safeAdd(c, HASH[2]);
1881 HASH[3] = safeAdd(d, HASH[3]);
1882 HASH[4] = safeAdd(e, HASH[4]);
1883 HASH[5] = safeAdd(f, HASH[5]);
1884 HASH[6] = safeAdd(g, HASH[6]);
1885 HASH[7] = safeAdd(h, HASH[7]);
1890 function str2binb(str) {
1892 var mask = (1 << chrsz) - 1;
1893 for (var i = 0; i < str.length * chrsz; i += chrsz) {
1894 bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i % 32);
1899 function Utf8Encode(string) {
1900 string = string.replace(/\r\n/g, "\n");
1903 for (var n = 0; n < string.length; n++) {
1905 var c = string.charCodeAt(n);
1908 utftext += String.fromCharCode(c);
1909 } else if ((c > 127) && (c < 2048)) {
1910 utftext += String.fromCharCode((c >> 6) | 192);
1911 utftext += String.fromCharCode((c & 63) | 128);
1913 utftext += String.fromCharCode((c >> 12) | 224);
1914 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
1915 utftext += String.fromCharCode((c & 63) | 128);
1923 function binb2hex(binarray) {
1924 var hexTab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
1926 for (var i = 0; i < binarray.length * 4; i++) {
1927 str += hexTab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) +
1928 hexTab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF);
1934 return binb2hex(coreSha256(str2binb(s), s.length * chrsz));
1938 // Function to get URL path for use in forbidden path adding.
1939 function getInitializePath(to) {
1940 var cwd = process.cwd();
1941 if (os.platform() == "win32") {
1942 to = to.replace(/\//g, "\\");
1943 if (to[0] == "\\") to = cwd.split("\\")[0] + to;
1945 var absoluteTo = path.isAbsolute(to) ? to : (__dirname + (os.platform() == "win32" ? "\\" : "/") + to);
1946 if (os.platform() == "win32" && cwd[0] != absoluteTo[0]) return "";
1947 var relative = path.relative(cwd, absoluteTo);
1948 if (os.platform() == "win32") {
1949 return "/" + relative.replace(/\\/g, "/");
1951 return "/" + relative;
1955 // Function to check if URL path name is a forbidden path.
1956 function isForbiddenPath(decodedHref, match) {
1957 var forbiddenPath = forbiddenPaths[match];
1958 if (!forbiddenPath) return false;
1959 if (typeof forbiddenPath === "string") {
1960 return decodedHref === forbiddenPath || (os.platform() === "win32" && decodedHref.toLowerCase() === forbiddenPath.toLowerCase());
1962 if (typeof forbiddenPath === "object") {
1963 return forbiddenPath.some(function (forbiddenPathSingle) {
1964 return (decodedHref === forbiddenPathSingle || (os.platform() === "win32" && decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase()));
1970 // Function to check if URL path name is index of one of defined forbidden paths.
1971 function isIndexOfForbiddenPath(decodedHref, match) {
1972 var forbiddenPath = forbiddenPaths[match];
1973 if (!forbiddenPath) return false;
1974 if (typeof forbiddenPath === "string") {
1975 return decodedHref === forbiddenPath || decodedHref.indexOf(forbiddenPath + "/") === 0 || (os.platform() === "win32" && (decodedHref.toLowerCase() === forbiddenPath.toLowerCase() || decodedHref.toLowerCase().indexOf(forbiddenPath.toLowerCase() + "/") === 0));
1977 if (typeof forbiddenPath === "object") {
1978 return forbiddenPath.some(function (forbiddenPathSingle) {
1979 return (decodedHref === forbiddenPathSingle || decodedHref.indexOf(forbiddenPathSingle + "/") === 0 || (os.platform() === "win32" && (decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase() || decodedHref.toLowerCase().indexOf(forbiddenPathSingle.toLowerCase() + "/") === 0)));
1985 // Set up forbidden paths
1986 var forbiddenPaths = {};
1988 forbiddenPaths.config = getInitializePath("./config.json");
1989 forbiddenPaths.certificates = [];
1991 forbiddenPaths.certificates.push(getInitializePath(configJSON.cert));
1992 forbiddenPaths.certificates.push(getInitializePath(configJSON.key));
1993 Object.keys(sni).forEach(function (sniHostName) {
1994 forbiddenPaths.certificates.push(getInitializePath(sni[sniHostName].cert));
1995 forbiddenPaths.certificates.push(getInitializePath(sni[sniHostName].key));
1998 forbiddenPaths.svrjs = getInitializePath("./" + ((__dirname[__dirname.length - 1] != "/") ? __filename.replace(__dirname + "/", "") : __filename.replace(__dirname, "")));
1999 forbiddenPaths.serverSideScripts = [];
2000 if (useWebRootServerSideScript) {
2001 forbiddenPaths.serverSideScripts.push("/serverSideScript.js");
2003 forbiddenPaths.serverSideScripts.push(getInitializePath("./serverSideScript.js"));
2005 forbiddenPaths.serverSideScriptDirectories = [];
2006 forbiddenPaths.serverSideScriptDirectories.push(getInitializePath("./node_modules"));
2007 forbiddenPaths.serverSideScriptDirectories.push(getInitializePath("./mods"));
2008 forbiddenPaths.temp = getInitializePath("./temp");
2009 forbiddenPaths.log = getInitializePath("./log");
2011 // HTTP error descriptions
2012 var serverHTTPErrorDescs = {
2013 200: "The request succeeded! :)",
2014 201: "A new resource has been created.",
2015 202: "The request has been accepted for processing, but the processing has not been completed.",
2016 400: "The request you made is invalid.",
2017 401: "You need to authenticate yourself in order to access the requested file.",
2018 402: "You need to pay in order to access the requested file.",
2019 403: "You don't have access to the requested file.",
2020 404: "The requested file doesn't exist. If you have typed the URL manually, then please check the spelling.",
2021 405: "Method used to access the requested file isn't allowed.",
2022 406: "The request is capable of generating only unacceptable content.",
2023 407: "You need to authenticate yourself in order to use the proxy.",
2024 408: "You have timed out.",
2025 409: "The request you sent conflicts with the current state of the server.",
2026 410: "The requested file is permanently deleted.",
2027 411: "Content-Length property is required.",
2028 412: "The server doesn't meet the preconditions you put in the request.",
2029 413: "The request you sent is too large.",
2030 414: "The URL you sent is too long.",
2031 415: "The media type of request you sent isn't supported by the server.",
2032 416: "The requested content range (Content-Range header) you sent is unsatisfiable.",
2033 417: "The expectation specified in the Expect property couldn't be satisfied.",
2034 418: "The server (teapot) can't brew any coffee! ;)",
2035 421: "The request you made isn't intended for this server.",
2036 422: "The server couldn't process content sent by you.",
2037 423: "The requested file is locked.",
2038 424: "The request depends on another failed request.",
2039 425: "The server is unwilling to risk processing a request that might be replayed.",
2040 426: "You need to upgrade the protocols you use to request a file.",
2041 428: "The request you sent needs to be conditional, but it isn't.",
2042 429: "You sent too many requests to the server.",
2043 431: "The request you sent contains headers that are too large.",
2044 451: "The requested file isn't accessible for legal reasons.",
2045 497: "You sent a non-TLS request to the HTTPS server.",
2046 500: "The server had an unexpected error. Below, the error stack is shown: </p><code>{stack}</code><p>You may need to contact the server administrator at <i>{contact}</i>.",
2047 501: "The request requires the use of a function, which isn't currently implemented by the server.",
2048 502: "The server had an error while it was acting as a gateway.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
2049 503: "The service provided by the server is currently unavailable, possibly due to maintenance downtime or capacity problems. Please try again later.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
2050 504: "The server couldn't get a response in time while it was acting as a gateway.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
2051 505: "The server doesn't support the HTTP version used in the request.",
2052 506: "The Variant header is configured to be engaged in content negotiation.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
2053 507: "The server ran out of disk space necessary to complete the request.",
2054 508: "The server detected an infinite loop while processing the request.",
2055 509: "The server has its bandwidth limit exceeded.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
2056 510: "The server requires an extended HTTP request. The request you made isn't an extended HTTP request.",
2057 511: "You need to authenticate yourself in order to get network access.",
2058 598: "The server couldn't get a response in time while it was acting as a proxy.",
2059 599: "The server couldn't connect in time while it was acting as a proxy."
2062 // Server error descriptions
2063 var serverErrorDescs = {
2064 "EADDRINUSE": "Address is already in use by another process.",
2065 "EADDRNOTAVAIL": "Address is not available on this machine.",
2066 "EACCES": "Permission denied. You may not have sufficient privileges to access the requested address.",
2067 "EAFNOSUPPORT": "Address family not supported. The address family (IPv4 or IPv6) of the requested address is not supported.",
2068 "EALREADY": "Operation already in progress. The server is already in the process of establishing a connection on the requested address.",
2069 "ECONNABORTED": "Connection aborted. The connection to the server was terminated abruptly.",
2070 "ECONNREFUSED": "Connection refused. The server refused the connection attempt.",
2071 "ECONNRESET": "Connection reset by peer. The connection to the server was reset by the remote host.",
2072 "EDESTADDRREQ": "Destination address required. The destination address must be specified.",
2073 "EINVAL": "Invalid argument (invalid IP address?).",
2074 "ENETDOWN": "Network is down. The network interface used for the connection is not available.",
2075 "ENETUNREACH": "Network is unreachable. The network destination is not reachable from this host.",
2076 "ENOBUFS": "No buffer space available. Insufficient buffer space is available for the server to process the request.",
2077 "ENOTFOUND": "Domain name doesn't exist (invalid IP address?).",
2078 "ENOTSOCK": "Not a socket. The file descriptor provided is not a valid socket.",
2079 "EPROTO": "Protocol error. An unspecified protocol error occurred.",
2080 "EPROTONOSUPPORT": "Protocol not supported. The requested network protocol is not supported.",
2081 "ETIMEDOUT": "Connection timed out. The server did not respond within the specified timeout period.",
2082 "UNKNOWN": "There was an unknown error with the server."
2085 // Create server instances
2086 if (!cluster.isPrimary) {
2088 var malformedcounter = 0;
2089 var err4xxcounter = 0;
2090 var err5xxcounter = 0;
2091 var reqcounterKillReq = 0;
2095 server2 = http.createServer({
2096 requireHostHeader: false
2099 server2 = http.createServer();
2101 server2.on("request", function (req, res) {
2102 reqhandler(req, res, false);
2104 server2.on("checkExpectation", reqhandler);
2105 server2.on("clientError", function (err, socket) {
2106 reqerrhandler(err, socket, false);
2108 if (!disableToHTTPSRedirect) {
2109 server2.on("connect", function (request, socket) {
2110 var reqIdInt = Math.floor(Math.random() * 16777216);
2111 if (reqIdInt == 16777216) reqIdInt = 0;
2112 var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
2113 var serverconsole = {
2114 climessage: function (msg) {
2115 if (msg.indexOf("\n") != -1) {
2116 msg.split("\n").forEach(function (nmsg) {
2117 serverconsole.climessage(nmsg);
2121 console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2122 LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
2125 reqmessage: function (msg) {
2126 if (msg.indexOf("\n") != -1) {
2127 msg.split("\n").forEach(function (nmsg) {
2128 serverconsole.reqmessage(nmsg);
2132 console.log("\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2133 LOG("SERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg);
2136 resmessage: function (msg) {
2137 if (msg.indexOf("\n") != -1) {
2138 msg.split("\n").forEach(function (nmsg) {
2139 serverconsole.resmessage(nmsg);
2143 console.log("\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2144 LOG("SERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg);
2147 errmessage: function (msg) {
2148 if (msg.indexOf("\n") != -1) {
2149 msg.split("\n").forEach(function (nmsg) {
2150 serverconsole.errmessage(nmsg);
2154 console.log("\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2155 LOG("SERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
2158 locerrmessage: function (msg) {
2159 if (msg.indexOf("\n") != -1) {
2160 msg.split("\n").forEach(function (nmsg) {
2161 serverconsole.locerrmessage(nmsg);
2165 console.log("\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
2166 LOG("SERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
2169 locwarnmessage: function (msg) {
2170 if (msg.indexOf("\n") != -1) {
2171 msg.split("\n").forEach(function (nmsg) {
2172 serverconsole.locwarnmessage(nmsg);
2176 console.log("\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
2177 LOG("SERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg);
2180 locmessage: function (msg) {
2181 if (msg.indexOf("\n") != -1) {
2182 msg.split("\n").forEach(function (nmsg) {
2183 serverconsole.locmessage(nmsg);
2187 console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2188 LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2192 socket.on("close", function (hasError) {
2193 if (!hasError) serverconsole.locmessage("Client disconnected.");
2194 else serverconsole.locmessage("Client disconnected due to error.");
2196 socket.on("error", function () {});
2197 var reqip = socket.remoteAddress;
2198 var reqport = socket.remotePort;
2199 serverconsole.locmessage("Somebody connected to " + (typeof port == "number" ? "port " : "socket ") + port + "...");
2201 serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " wants to proxy " + request.url + " through this server");
2202 if (request.headers["user-agent"] != undefined) serverconsole.reqmessage("Client uses " + request.headers["user-agent"]);
2203 serverconsole.errmessage("This server will never be a proxy.");
2204 if (!socket.destroyed) socket.end("HTTP/1.1 501 Not Implemented\n\n");
2207 server2.on("connect", connhandler);
2209 server2.on("error", function (err) {
2210 serverErrorHandler(err, true);
2212 server2.on("listening", function () {
2217 if (configJSON.enableHTTP2 == true) {
2219 server = http2.createSecureServer({
2221 requireHostHeader: false,
2224 requestCert: configJSON.useClientCertificate,
2225 rejectUnauthorized: configJSON.rejectUnauthorizedClientCertificates,
2226 ciphers: configJSON.cipherSuite,
2227 ecdhCurve: configJSON.ecdhCurve,
2228 minVersion: configJSON.tlsMinVersion,
2229 maxVersion: configJSON.tlsMaxVersion,
2230 sigalgs: configJSON.signatureAlgorithms,
2231 settings: configJSON.http2Settings
2234 server = http2.createServer({
2236 requireHostHeader: false,
2237 settings: configJSON.http2Settings
2242 server = https.createServer({
2245 requireHostHeader: false,
2246 requestCert: configJSON.useClientCertificate,
2247 rejectUnauthorized: configJSON.rejectUnauthorizedClientCertificates,
2248 ciphers: configJSON.cipherSuite,
2249 ecdhCurve: configJSON.ecdhCurve,
2250 minVersion: configJSON.tlsMinVersion,
2251 maxVersion: configJSON.tlsMaxVersion,
2252 sigalgs: configJSON.signatureAlgorithms
2256 server = http.createServer({
2257 requireHostHeader: false
2260 server = http.createServer();
2266 sniCredentials.forEach(function (sniCredentialsSingle) {
2267 server.addContext(sniCredentialsSingle.name, {
2268 cert: sniCredentialsSingle.cert,
2269 key: sniCredentialsSingle.key
2272 var snMatches = sniCredentialsSingle.name.match(/^([^:[]*|\[[^]]*\]?)((?::.*)?)$/);
2273 if (!snMatches[1][0].match(/^\.+$/)) snMatches[1][0] = snMatches[1][0].replace(/\.+$/, "");
2274 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");
2276 // Can't replace regex, ignoring...
2283 server.on("request", reqhandler);
2284 server.on("checkExpectation", reqhandler);
2285 server.on("connect", connhandler);
2286 server.on("clientError", reqerrhandler);
2289 server.prependListener("connection", function (sock) {
2290 sock.reallyDestroy = sock.destroy;
2291 sock.destroy = function () {
2292 sock.toDestroy = true;
2296 server.prependListener("tlsClientError", function (err, sock) {
2297 if (err.code == "ERR_SSL_HTTP_REQUEST" || err.message.indexOf("http request") != -1) {
2298 sock._parent.destroy = sock._parent.reallyDestroy;
2299 sock._readableState = sock._parent._readableState;
2300 sock._writableState = sock._parent._writableState;
2301 sock._parent.toDestroy = false;
2302 sock.pipe = function (a, b, c) {
2303 sock._parent.pipe(a, b, c);
2305 sock.write = function (a, b, c) {
2306 sock._parent.write(a, b, c);
2308 sock.end = function (a, b, c) {
2309 sock._parent.end(a, b, c);
2311 sock.destroyed = sock._parent.destroyed;
2312 sock.readable = sock._parent.readable;
2313 sock.writable = sock._parent.writable;
2314 sock.remoteAddress = sock._parent.remoteAddress;
2315 sock.remotePort = sock._parent.remoteAddress;
2316 sock.destroy = function (a, b, c) {
2318 sock._parent.destroy(a, b, c);
2319 sock.destroyed = sock._parent.destroyed;
2321 // Socket is probably already destroyed.
2325 sock._parent.destroy = sock._parent.reallyDestroy;
2327 if (sock._parent.toDestroy) sock._parent.destroy();
2329 // Socket is probably already destroyed.
2334 server.prependListener("secureConnection", function (sock) {
2335 sock._parent.destroy = sock._parent.reallyDestroy;
2336 delete sock._parent.reallyDestroy;
2339 if (configJSON.enableOCSPStapling && !ocsp._errored) {
2340 server.on("OCSPRequest", function (cert, issuer, callback) {
2341 ocsp.getOCSPURI(cert, function (err, uri) {
2342 if (err) return callback(err);
2344 var req = ocsp.request.generate(cert, issuer);
2350 ocspCache.request(req.id, options, callback);
2356 // Patches from Node.JS v18.0.0
2357 if (server.requestTimeout !== undefined && server.requestTimeout === 0) server.requestTimeout = 300000;
2358 if (server2.requestTimeout !== undefined && server2.requestTimeout === 0) server2.requestTimeout = 300000;
2360 function reqerrhandler(err, socket, fromMain) {
2361 if (fromMain === undefined) fromMain = true;
2362 // Define response object similar to Node.JS native one
2364 res.socket = socket;
2365 res.write = function (x) {
2366 if (err.code === "ECONNRESET" || !socket.writable) {
2371 res.end = function (x) {
2372 if (err.code === "ECONNRESET" || !socket.writable) {
2375 socket.end(x, function () {
2379 // Socket is probably already destroyed
2383 res.writeHead = function (code, name, headers) {
2384 if (code >= 400 && code <= 499) err4xxcounter++;
2385 if (code >= 500 && code <= 599) err5xxcounter++;
2386 var head = ("HTTP/1.1 " + code.toString() + " " + name + "\r\n");
2387 var headers = JSON.parse(JSON.stringify(headers));
2388 headers["Date"] = (new Date()).toGMTString();
2389 headers["Connection"] = "close";
2390 Object.keys(headers).forEach(function (headername) {
2391 if (headername.toLowerCase() == "set-cookie") {
2392 headers[headername].forEach(function (headerValueS) {
2393 if (headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) || headerValueS.match(/[^\x09\x20-\x7e\x80-\xff]/)) throw new Error("Invalid header!!! (" + headername + ")");
2394 head += (headername + ": " + headerValueS);
2397 if (headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) || headers[headername].match(/[^\x09\x20-\x7e\x80-\xff]/)) throw new Error("Invalid header!!! (" + headername + ")");
2398 head += (headername + ": " + headers[headername]);
2406 var reqIdInt = Math.floor(Math.random() * 16777216);
2407 if (reqIdInt == 16777216) reqIdInt = 0;
2408 var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
2409 var serverconsole = {
2410 climessage: function (msg) {
2411 if (msg.indexOf("\n") != -1) {
2412 msg.split("\n").forEach(function (nmsg) {
2413 serverconsole.climessage(nmsg);
2417 console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2418 LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
2421 reqmessage: function (msg) {
2422 if (msg.indexOf("\n") != -1) {
2423 msg.split("\n").forEach(function (nmsg) {
2424 serverconsole.reqmessage(nmsg);
2428 console.log("\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2429 LOG("SERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg);
2432 resmessage: function (msg) {
2433 if (msg.indexOf("\n") != -1) {
2434 msg.split("\n").forEach(function (nmsg) {
2435 serverconsole.resmessage(nmsg);
2439 console.log("\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2440 LOG("SERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg);
2443 errmessage: function (msg) {
2444 if (msg.indexOf("\n") != -1) {
2445 msg.split("\n").forEach(function (nmsg) {
2446 serverconsole.errmessage(nmsg);
2450 console.log("\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2451 LOG("SERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
2454 locerrmessage: function (msg) {
2455 if (msg.indexOf("\n") != -1) {
2456 msg.split("\n").forEach(function (nmsg) {
2457 serverconsole.locerrmessage(nmsg);
2461 console.log("\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
2462 LOG("SERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
2465 locwarnmessage: function (msg) {
2466 if (msg.indexOf("\n") != -1) {
2467 msg.split("\n").forEach(function (nmsg) {
2468 serverconsole.locwarnmessage(nmsg);
2472 console.log("\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
2473 LOG("SERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg);
2476 locmessage: function (msg) {
2477 if (msg.indexOf("\n") != -1) {
2478 msg.split("\n").forEach(function (nmsg) {
2479 serverconsole.locmessage(nmsg);
2483 console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2484 LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2488 socket.on("close", function (hasError) {
2489 if (!hasError || err.code == "ERR_SSL_HTTP_REQUEST" || err.message.indexOf("http request") != -1) serverconsole.locmessage("Client disconnected.");
2490 else serverconsole.locmessage("Client disconnected due to error.");
2492 socket.on("error", function () {});
2494 // Header and footer placeholders
2498 function responseEnd(body) {
2499 // If body is Buffer, then it is converted to String anyway.
2500 res.write(head + body + foot);
2504 // Server error calling method
2505 function callServerError(errorCode, extName, stack, ch) {
2506 if (typeof errorCode !== "number") {
2507 throw new TypeError("HTTP error code parameter needs to be an integer.");
2510 // Handle optional parameters
2511 if (extName && typeof extName === "object") {
2514 extName = undefined;
2515 } else if (typeof extName !== "string" && extName !== null && extName !== undefined) {
2516 throw new TypeError("Extension name parameter needs to be a string.");
2519 if (stack && typeof stack === "object" && Object.prototype.toString.call(stack) !== "[object Error]") {
2522 } else if (typeof stack !== "object" && typeof stack !== "string" && stack) {
2523 throw new TypeError("Error stack parameter needs to be either a string or an instance of Error object.");
2526 // Determine error file
2527 function getErrorFileName(list, callback, _i) {
2528 if (err.code == "ERR_SSL_HTTP_REQUEST" && process.version && parseInt(process.version.split(".")[0].substring(1)) >= 16) {
2529 // Disable custom error page for HTTP SSL error
2530 callback(errorCode.toString() + ".html");
2534 function medCallback(p) {
2537 if (errorCode == 404) {
2538 fs.access(page404, fs.constants.F_OK, function (err) {
2540 fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
2543 callback(errorCode.toString() + ".html");
2545 callback("." + errorCode.toString());
2548 callServerError(500, err2);
2555 callServerError(500, err2);
2560 fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
2563 callback(errorCode.toString() + ".html");
2565 callback("." + errorCode.toString());
2568 callServerError(500, err2);
2576 if (_i >= list.length) {
2581 if (list[_i].scode != errorCode) {
2582 getErrorFileName(list, callback, _i + 1);
2585 fs.access(list[_i].path, fs.constants.F_OK, function (err) {
2587 getErrorFileName(list, callback, _i + 1);
2589 medCallback(list[_i].path);
2595 getErrorFileName(errorPages, function (errorFile) {
2596 if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
2597 if (stack === undefined) stack = generateErrorStack(new Error("Unknown error"));
2598 if (errorCode == 500 || errorCode == 502) {
2599 serverconsole.errmessage("There was an error while processing the request!");
2600 serverconsole.errmessage("Stack:");
2601 serverconsole.errmessage(stack);
2603 if (stackHidden) stack = "[error stack hidden]";
2604 if (serverHTTPErrorDescs[errorCode] === undefined) {
2605 callServerError(501, extName, stack);
2607 var cheaders = getCustomHeaders();
2609 var chon = Object.keys(cheaders);
2610 Object.keys(ch).forEach(function (chnS) {
2612 for (var j = 0; j < chon.length; j++) {
2613 if (chon[j].toLowerCase() == chnS.toLowerCase()) {
2618 if (ch[chnS]) cheaders[nhn] = ch[chnS];
2621 cheaders["Content-Type"] = "text/html; charset=utf-8";
2622 if (errorCode == 405 && !cheaders["Allow"]) cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
2623 if (err.code == "ERR_SSL_HTTP_REQUEST" && process.version && parseInt(process.version.split(".")[0].substring(1)) >= 16) {
2624 // Disable custom error page for HTTP SSL error
2625 res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
2626 res.write(("<!DOCTYPE html><html><head><title>{errorMessage}</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /><style>html{background-color:#dfffdf;color:#000000;font-family:FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;margin:0.75em}body{background-color:#ffffff;padding:0.5em 0.5em 0.1em;margin:0.5em auto;width:90%;max-width:800px;-webkit-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15)}h1{text-align:center;font-size:2.25em;margin:0.3em 0 0.5em}code{background-color:#dfffdf;-webkit-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);display:block;padding:0.2em;font-family:\"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", Hack, Menlo, Consolas, Monaco, monospace;font-size:0.85em;margin:auto;width:95%;max-width:600px}table{width:95%;border-collapse:collapse;margin:auto;overflow-wrap:break-word;word-wrap:break-word;word-break:break-all;word-break:break-word;position:relative;z-index:0}table tbody{background-color:#ffffff;color:#000000}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);content:' ';position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}table img{margin:0;display:inline}th,tr{padding:0.15em;text-align:center}th{background-color:#007000;color:#ffffff}th a{color:#ffffff}td,th{padding:0.225em}td{text-align:left}tr:nth-child(odd){background-color:#dfffdf}hr{color:#ffffff}@media screen and (prefers-color-scheme: dark){html{background-color:#002000;color:#ffffff}body{background-color:#000f00;-webkit-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15)}code{background-color:#002000;-webkit-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1)}a{color:#ffffff}a:hover{color:#00ff00}table tbody{background-color:#000f00;color:#ffffff}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175)}tr:nth-child(odd){background-color:#002000}}</style></head><body><h1>{errorMessage}</h1><p>{errorDesc}</p><p><i>{server}</i></p></body></html>").replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").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(/>/g, ">")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")));
2629 fs.readFile(errorFile, function (err, data) {
2632 res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
2633 responseEnd(data.toString().replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").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(/>/g, ">")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")));
2635 var additionalError = 500;
2636 if (err.code == "ENOENT") {
2637 additionalError = 404;
2638 } else if (err.code == "ENOTDIR") {
2639 additionalError = 404; // Assume that file doesn't exist
2640 } else if (err.code == "EACCES") {
2641 additionalError = 403;
2642 } else if (err.code == "ENAMETOOLONG") {
2643 additionalError = 414;
2644 } else if (err.code == "EMFILE") {
2645 additionalError = 503;
2646 } else if (err.code == "ELOOP") {
2647 additionalError = 508;
2649 res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
2650 res.write(("<!DOCTYPE html><html><head><title>{errorMessage}</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /><style>html{background-color:#dfffdf;color:#000000;font-family:FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;margin:0.75em}body{background-color:#ffffff;padding:0.5em 0.5em 0.1em;margin:0.5em auto;width:90%;max-width:800px;-webkit-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15)}h1{text-align:center;font-size:2.25em;margin:0.3em 0 0.5em}code{background-color:#dfffdf;-webkit-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);display:block;padding:0.2em;font-family:\"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", Hack, Menlo, Consolas, Monaco, monospace;font-size:0.85em;margin:auto;width:95%;max-width:600px}table{width:95%;border-collapse:collapse;margin:auto;overflow-wrap:break-word;word-wrap:break-word;word-break:break-all;word-break:break-word;position:relative;z-index:0}table tbody{background-color:#ffffff;color:#000000}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);content:' ';position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}table img{margin:0;display:inline}th,tr{padding:0.15em;text-align:center}th{background-color:#007000;color:#ffffff}th a{color:#ffffff}td,th{padding:0.225em}td{text-align:left}tr:nth-child(odd){background-color:#dfffdf}hr{color:#ffffff}@media screen and (prefers-color-scheme: dark){html{background-color:#002000;color:#ffffff}body{background-color:#000f00;-webkit-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15)}code{background-color:#002000;-webkit-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1)}a{color:#ffffff}a:hover{color:#00ff00}table tbody{background-color:#000f00;color:#ffffff}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175)}tr:nth-child(odd){background-color:#002000}}</style></head><body><h1>{errorMessage}</h1><p>{errorDesc}</p>" + ((additionalError == 404) ? "" : "<p>Additionally, a {additionalError} error occurred while loading an error page.</p>") + "<p><i>{server}</i></p></body></html>").replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").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(/>/g, ">")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")).replace(/{additionalError}/g, additionalError.toString()));
2658 var reqip = socket.remoteAddress;
2659 var reqport = socket.remotePort;
2662 serverconsole.locmessage("Somebody connected to " + (secure && fromMain ? ((typeof sport == "number" ? "port " : "socket ") + sport) : ((typeof port == "number" ? "port " : "socket ") + port)) + "...");
2663 serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " sent invalid request.");
2665 head = fs.existsSync("./.head") ? fs.readFileSync("./.head").toString() : (fs.existsSync("./head.html") ? fs.readFileSync("./head.html").toString() : ""); // header
2666 foot = fs.existsSync("./.foot") ? fs.readFileSync("./.foot").toString() : (fs.existsSync("./foot.html") ? fs.readFileSync("./foot.html").toString() : ""); // footer
2668 if ((err.code && (err.code.indexOf("ERR_SSL_") == 0 || err.code.indexOf("ERR_TLS_") == 0)) || (!err.code && err.message.indexOf("SSL routines") != -1)) {
2669 if (err.code == "ERR_SSL_HTTP_REQUEST" || err.message.indexOf("http request") != -1) {
2670 serverconsole.errmessage("Client sent HTTP request to HTTPS port.");
2671 callServerError(497);
2674 serverconsole.errmessage("An SSL error occured: " + (err.code ? err.code : err.message));
2675 callServerError(400);
2680 if (err.code && err.code.indexOf("ERR_HTTP2_") == 0) {
2681 serverconsole.errmessage("An HTTP/2 error occured: " + err.code);
2682 callServerError(400);
2686 if (err.code && err.code == "ERR_HTTP_REQUEST_TIMEOUT") {
2687 serverconsole.errmessage("Client timed out.");
2688 callServerError(408);
2692 if (!err.rawPacket) {
2693 serverconsole.errmessage("Connection ended prematurely.");
2694 callServerError(400);
2698 var packetLines = err.rawPacket.toString().split("\r\n");
2699 if (packetLines.length == 0) {
2700 serverconsole.errmessage("Invalid request.");
2701 callServerError(400);
2705 function checkHeaders(beginsFromFirst) {
2706 for (var i = (beginsFromFirst ? 0 : 1); i < packetLines.length; i++) {
2707 var header = packetLines[i];
2708 if (header == "") return false; // Beginning of body
2709 else if (header.indexOf(":") < 1) {
2710 serverconsole.errmessage("Invalid header.");
2711 callServerError(400);
2713 } else if (header.length > 8192) {
2714 serverconsole.errmessage("Header too large.");
2715 callServerError(431); // Headers too large
2721 var packetLine1 = packetLines[0].split(" ");
2723 var httpVersion = "HTTP/1.1";
2724 if (String(packetLine1[0]).indexOf(":") > 0) {
2725 if (!checkHeaders(true)) {
2726 serverconsole.errmessage("The request is invalid (it may be a part of larger invalid request).");
2727 callServerError(400); // Also malformed Packet
2731 if (String(packetLine1[0]).length < 50) method = packetLine1.shift();
2732 if (String(packetLine1[packetLine1.length - 1]).length < 50) httpVersion = packetLine1.pop();
2733 if (packetLine1.length != 1) {
2734 serverconsole.errmessage("The head of request is invalid.");
2735 callServerError(400); // Malformed Packet
2736 } else if (!httpVersion.toString().match(/^HTTP[\/]/i)) {
2737 serverconsole.errmessage("Invalid protocol.");
2738 callServerError(400); // bad protocol version
2739 } else if (http.METHODS.indexOf(method) == -1) {
2740 serverconsole.errmessage("Invalid method.");
2741 callServerError(405); // Also malformed Packet
2743 if (checkHeaders(false)) return;
2744 if (packetLine1[0].length > 255) {
2745 serverconsole.errmessage("URI too long.");
2746 callServerError(414); // Also malformed Packet
2748 serverconsole.errmessage("The request is invalid.");
2749 callServerError(400); // Also malformed Packet
2753 serverconsole.errmessage("There was an error while determining type of malformed request.");
2754 callServerError(400);
2758 function connhandler(request, socket, head) {
2759 var reqIdInt = Math.floor(Math.random() * 16777216);
2760 if (reqIdInt == 16777216) reqIdInt = 0;
2761 var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
2762 var serverconsole = {
2763 climessage: function (msg) {
2764 if (msg.indexOf("\n") != -1) {
2765 msg.split("\n").forEach(function (nmsg) {
2766 serverconsole.climessage(nmsg);
2770 console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2771 LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
2774 reqmessage: function (msg) {
2775 if (msg.indexOf("\n") != -1) {
2776 msg.split("\n").forEach(function (nmsg) {
2777 serverconsole.reqmessage(nmsg);
2781 console.log("\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2782 LOG("SERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg);
2785 resmessage: function (msg) {
2786 if (msg.indexOf("\n") != -1) {
2787 msg.split("\n").forEach(function (nmsg) {
2788 serverconsole.resmessage(nmsg);
2792 console.log("\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2793 LOG("SERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg);
2796 errmessage: function (msg) {
2797 if (msg.indexOf("\n") != -1) {
2798 msg.split("\n").forEach(function (nmsg) {
2799 serverconsole.errmessage(nmsg);
2803 console.log("\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2804 LOG("SERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
2807 locerrmessage: function (msg) {
2808 if (msg.indexOf("\n") != -1) {
2809 msg.split("\n").forEach(function (nmsg) {
2810 serverconsole.locerrmessage(nmsg);
2814 console.log("\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
2815 LOG("SERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
2818 locwarnmessage: function (msg) {
2819 if (msg.indexOf("\n") != -1) {
2820 msg.split("\n").forEach(function (nmsg) {
2821 serverconsole.locwarnmessage(nmsg);
2825 console.log("\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
2826 LOG("SERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg);
2829 locmessage: function (msg) {
2830 if (msg.indexOf("\n") != -1) {
2831 msg.split("\n").forEach(function (nmsg) {
2832 serverconsole.locmessage(nmsg);
2836 console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2837 LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2842 socket.on("close", function (hasError) {
2843 if (!hasError) serverconsole.locmessage("Client disconnected.");
2844 else serverconsole.locmessage("Client disconnected due to error.");
2846 socket.on("error", function () {});
2848 var reqip = socket.remoteAddress;
2849 var reqport = socket.remotePort;
2851 serverconsole.locmessage("Somebody connected to " + (secure ? ((typeof sport == "number" ? "port " : "socket ") + sport) : ((typeof port == "number" ? "port " : "socket ") + port)) + "...");
2852 serverconsole.reqmessage("Client " + ((!reqip || reqip == "") ? "[unknown client]" : (reqip + ((reqport && reqport !== 0) && reqport != "" ? ":" + reqport : ""))) + " wants to proxy " + request.url + " through this server");
2853 if (request.headers["user-agent"] != undefined) serverconsole.reqmessage("Client uses " + request.headers["user-agent"]);
2855 function modExecute(mods, ffinals) {
2857 mods.forEach(function (mod) {
2858 if (mod.proxyCallback !== undefined) proxyMods.push(mod);
2861 var modFunction = ffinals;
2862 proxyMods.reverse().forEach(function (proxyMod) {
2863 modFunction = proxyMod.proxyCallback(req, socket, head, configJSON, serverconsole, modFunction);
2869 serverconsole.errmessage("SVR.JS doesn't support proxy without proxy mod.");
2870 if (!socket.destroyed) socket.end("HTTP/1.1 501 Not Implemented\n\n");
2872 modExecute(mods, vres);
2875 function reqhandler(req, res, fromMain) {
2876 if (fromMain === undefined) fromMain = true;
2877 var reqIdInt = Math.floor(Math.random() * 16777216);
2878 if (reqIdInt == 16777216) reqIdInt = 0;
2879 var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
2880 var serverconsole = {
2881 climessage: function (msg) {
2882 if (msg.indexOf("\n") != -1) {
2883 msg.split("\n").forEach(function (nmsg) {
2884 serverconsole.climessage(nmsg);
2888 console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2889 LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
2892 reqmessage: function (msg) {
2893 if (msg.indexOf("\n") != -1) {
2894 msg.split("\n").forEach(function (nmsg) {
2895 serverconsole.reqmessage(nmsg);
2899 console.log("\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2900 LOG("SERVER REQUEST MESSAGE [Request Id: " + reqId + "]: " + msg);
2903 resmessage: function (msg) {
2904 if (msg.indexOf("\n") != -1) {
2905 msg.split("\n").forEach(function (nmsg) {
2906 serverconsole.resmessage(nmsg);
2910 console.log("\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2911 LOG("SERVER RESPONSE MESSAGE [Request Id: " + reqId + "]: " + msg);
2914 errmessage: function (msg) {
2915 if (msg.indexOf("\n") != -1) {
2916 msg.split("\n").forEach(function (nmsg) {
2917 serverconsole.errmessage(nmsg);
2921 console.log("\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[37m\x1b[0m");
2922 LOG("SERVER RESPONSE ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
2925 locerrmessage: function (msg) {
2926 if (msg.indexOf("\n") != -1) {
2927 msg.split("\n").forEach(function (nmsg) {
2928 serverconsole.locerrmessage(nmsg);
2932 console.log("\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
2933 LOG("SERVER ERROR MESSAGE [Request Id: " + reqId + "]: " + msg);
2936 locwarnmessage: function (msg) {
2937 if (msg.indexOf("\n") != -1) {
2938 msg.split("\n").forEach(function (nmsg) {
2939 serverconsole.locwarnmessage(nmsg);
2943 console.log("\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg + "\x1b[40m\x1b[0m");
2944 LOG("SERVER WARNING MESSAGE [Request Id: " + reqId + "]: " + msg);
2947 locmessage: function (msg) {
2948 if (msg.indexOf("\n") != -1) {
2949 msg.split("\n").forEach(function (nmsg) {
2950 serverconsole.locmessage(nmsg);
2954 console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2955 LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2960 function matchHostname(hostname) {
2961 if (typeof hostname == "undefined" || hostname == "*") {
2963 } else if (req.headers.host && hostname.indexOf("*.") == 0 && hostname != "*.") {
2964 var hostnamesRoot = hostname.substring(2);
2965 if (req.headers.host == hostnamesRoot || (req.headers.host.length > hostnamesRoot.length && req.headers.host.indexOf("." + hostnamesRoot) == req.headers.host.length - hostnamesRoot.length - 1)) {
2968 } else if (req.headers.host && req.headers.host == hostname) {
2974 function getCustomHeaders() {
2975 var ph = JSON.parse(JSON.stringify(customHeaders));
2976 if (configJSON.customHeadersVHost) {
2978 configJSON.customHeadersVHost.every(function (vhost) {
2979 if (matchHostname(vhost.host) && ipMatch(vhost.ip, req.socket ? req.socket.localAddress : undefined)) {
2986 if (vhostP && vhostP.headers) {
2987 var phNu = JSON.parse(JSON.stringify(vhostP.headers));
2988 Object.keys(phNu).forEach(function (phNuK) {
2989 ph[phNuK] = phNu[phNuK];
2993 Object.keys(ph).forEach(function (phk) {
2994 if (typeof ph[phk] == "string") ph[phk] = ph[phk].replace(/\{path\}/g, req.url);
2999 // Make HTTP/1.x API-based scripts compatible with HTTP/2.0 API
3000 if (configJSON.enableHTTP2 == true && req.httpVersion == "2.0") {
3001 // Set HTTP/1.x methods (to prevent process warnings)
3002 res.writeHeadNodeApi = res.writeHead;
3003 res.setHeaderNodeApi = res.setHeader;
3005 res.writeHead = function (a, b, c) {
3007 if (typeof (b) == "object") table = b;
3008 if (table == undefined) table = this.tHeaders;
3009 if (table == undefined) table = {};
3010 table = JSON.parse(JSON.stringify(table));
3011 Object.keys(table).forEach(function (key) {
3012 var al = key.toLowerCase();
3013 if (al == "transfer-encoding" || al == "connection" || al == "keep-alive" || al == "upgrade") delete table[key];
3015 if (res.stream && res.stream.destroyed) {
3018 return res.writeHeadNodeApi(a, table);
3021 res.setHeader = function (headerName, headerValue) {
3022 var al = headerName.toLowerCase();
3023 if (al != "transfer-encoding" && al != "connection" && al != "keep-alive" && al != "upgrade") return res.setHeaderNodeApi(headerName, headerValue);
3027 // Set HTTP/1.x headers
3028 if (!req.headers.host) req.headers.host = req.headers[":authority"];
3029 if (!req.url) req.url = req.headers[":path"];
3030 if (!req.protocol) req.protocol = req.headers[":scheme"];
3031 if (!req.method) req.method = req.headers[":method"];
3032 if (req.headers[":path"] == undefined || req.headers[":method"] == undefined) {
3033 var err = new Error("Either \":path\" or \":method\" pseudoheader is missing.");
3034 if(Buffer.alloc) err.rawPacket = Buffer.alloc(0);
3035 reqerrhandler(err, req.socket, fromMain);
3039 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)) {
3040 var headers = getCustomHeaders();
3041 res.writeHead(204, http.STATUS_CODES[204], headers);
3046 req.url = fixNodeMojibakeURL(req.url);
3048 var headWritten = false;
3049 var lastStatusCode = null;
3050 res.writeHeadNative = res.writeHead;
3051 res.writeHead = function (code, codeDescription, headers) {
3052 if (!(headWritten && process.isBun && code === lastStatusCode && codeDescription === undefined && codeDescription === undefined)) {
3054 process.emitWarning("res.writeHead called multiple times.", {
3055 code: "WARN_SVRJS_MULTIPLE_WRITEHEAD"
3061 if (code >= 400 && code <= 599) {
3062 if (code >= 400 && code <= 499) err4xxcounter++;
3063 else if (code >= 500 && code <= 599) err5xxcounter++;
3064 serverconsole.errmessage("Server responded with " + code.toString() + " code.");
3066 serverconsole.resmessage("Server responded with " + code.toString() + " code.");
3068 if (typeof codeDescription != "string" && http.STATUS_CODES[code]) {
3069 if (!headers) headers = codeDescription;
3070 codeDescription = http.STATUS_CODES[code];
3072 lastStatusCode = code;
3074 res.writeHeadNative(code, codeDescription, headers);
3077 var finished = false;
3078 res.on("finish", function () {
3081 serverconsole.locmessage("Client disconnected.");
3084 res.on("close", function () {
3087 serverconsole.locmessage("Client disconnected.");
3090 var isProxy = false;
3091 if (req.url[0] != "/" && req.url != "*") isProxy = true;
3092 serverconsole.locmessage("Somebody connected to " + (secure && fromMain ? ((typeof sport == "number" ? "port " : "socket ") + sport) : ((typeof port == "number" ? "port " : "socket ") + port)) + "...");
3094 if (req.socket == null) {
3095 serverconsole.errmessage("Client socket is null!!!");
3099 // Set up X-Forwarded-For
3100 var reqip = req.socket.remoteAddress;
3101 var reqport = req.socket.remotePort;
3104 var isForwardedValid = true;
3105 if (enableIPSpoofing) {
3106 if (req.headers["x-forwarded-for"] != undefined) {
3107 var preparedReqIP = req.headers["x-forwarded-for"].split(",")[0].replace(/ /g, "");
3108 var preparedReqIPvalid = net.isIP(preparedReqIP);
3109 if (preparedReqIPvalid) {
3110 if (preparedReqIPvalid == 4 && req.socket.remoteAddress && req.socket.remoteAddress.indexOf(":") > -1) preparedReqIP = "::ffff:" + preparedReqIP;
3111 reqip = preparedReqIP;
3114 oldport = req.socket.remotePort;
3115 oldip = req.socket.remoteAddress;
3116 req.socket.realRemotePort = reqport;
3117 req.socket.realRemoteAddress = reqip;
3118 req.socket.originalRemotePort = oldport;
3119 req.socket.originalRemoteAddress = oldip;
3120 res.socket.realRemotePort = reqport;
3121 res.socket.realRemoteAddress = reqip;
3122 res.socket.originalRemotePort = oldport;
3123 res.socket.originalRemoteAddress = oldip;
3125 // Address setting failed
3128 isForwardedValid = false;
3135 // Process the Host header
3136 var oldHostHeader = req.headers.host;
3137 if (typeof req.headers.host == "string") {
3138 req.headers.host = req.headers.host.toLowerCase();
3139 if (!req.headers.host.match(/^\.+$/)) req.headers.host = req.headers.host.replace(/\.$/g, "");
3142 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);
3143 if (req.headers["user-agent"] != undefined) serverconsole.reqmessage("Client uses " + req.headers["user-agent"]);
3144 if (oldHostHeader && oldHostHeader != req.headers.host) serverconsole.resmessage("Host name rewritten: " + oldHostHeader + " => " + req.headers.host);
3146 var acceptEncoding = req.headers["accept-encoding"];
3147 if (!acceptEncoding) acceptEncoding = "";
3149 // Header and footer placeholders
3153 function responseEnd(body) {
3154 // If body is Buffer, then it is converted to String anyway.
3155 res.write(head + body + foot);
3159 // Server error calling method
3160 function callServerError(errorCode, extName, stack, ch) {
3161 if (typeof errorCode !== "number") {
3162 throw new TypeError("HTTP error code parameter needs to be an integer.");
3165 // Handle optional parameters
3166 if (extName && typeof extName === "object") {
3169 extName = undefined;
3170 } else if (typeof extName !== "string" && extName !== null && extName !== undefined) {
3171 throw new TypeError("Extension name parameter needs to be a string.");
3174 if (stack && typeof stack === "object" && Object.prototype.toString.call(stack) !== "[object Error]") {
3177 } else if (typeof stack !== "object" && typeof stack !== "string" && stack) {
3178 throw new TypeError("Error stack parameter needs to be either a string or an instance of Error object.");
3181 // Determine error file
3182 function getErrorFileName(list, callback, _i) {
3183 function medCallback(p) {
3186 if (errorCode == 404) {
3187 fs.access(page404, fs.constants.F_OK, function (err) {
3189 fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
3192 callback(errorCode.toString() + ".html");
3194 callback("." + errorCode.toString());
3197 callServerError(500, err2);
3204 callServerError(500, err2);
3209 fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
3212 callback(errorCode.toString() + ".html");
3214 callback("." + errorCode.toString());
3217 callServerError(500, err2);
3225 if (_i >= list.length) {
3230 if (list[_i].scode != errorCode || !(matchHostname(list[_i].host) && ipMatch(list[_i].ip, req.socket ? req.socket.localAddress : undefined))) {
3231 getErrorFileName(list, callback, _i + 1);
3234 fs.access(list[_i].path, fs.constants.F_OK, function (err) {
3236 getErrorFileName(list, callback, _i + 1);
3238 medCallback(list[_i].path);
3244 getErrorFileName(errorPages, function (errorFile) {
3245 // Generate error stack if not provided
3246 if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
3247 if (stack === undefined) stack = generateErrorStack(new Error("Unknown error"));
3249 if (errorCode == 500 || errorCode == 502) {
3250 serverconsole.errmessage("There was an error while processing the request!");
3251 serverconsole.errmessage("Stack:");
3252 serverconsole.errmessage(stack);
3255 // Hide the error stack if specified
3256 if (stackHidden) stack = "[error stack hidden]";
3258 // Validate the error code and handle unknown codes
3259 if (serverHTTPErrorDescs[errorCode] === undefined) {
3260 callServerError(501, extName, stack);
3262 var cheaders = getCustomHeaders();
3264 // Process custom headers if provided
3266 var chon = Object.keys(cheaders);
3267 Object.keys(ch).forEach(function (chnS) {
3269 for (var j = 0; j < chon.length; j++) {
3270 if (chon[j].toLowerCase() == chnS.toLowerCase()) {
3275 if (ch[chnS]) cheaders[nhn] = ch[chnS];
3279 cheaders["Content-Type"] = "text/html; charset=utf-8";
3281 // Set default Allow header for 405 error if not provided
3282 if (errorCode == 405 && !cheaders["Allow"]) cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
3284 // Read the error file and replace placeholders with error information
3285 fs.readFile(errorFile, function (err, data) {
3288 res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
3289 responseEnd(data.toString().replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/ {2}/g, " ")).replace(/{path}/g, req.url.replace(/&/g, "&").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, "<").replace(/>/g, ">") + ((req.headers.host == undefined || isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"))).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]"))); // Replace placeholders in error response
3291 var additionalError = 500;
3292 // Handle additional error cases
3293 if (err.code == "ENOENT") {
3294 additionalError = 404;
3295 } else if (err.code == "ENOTDIR") {
3296 additionalError = 404; // Assume that file doesn't exist
3297 } else if (err.code == "EACCES") {
3298 additionalError = 403;
3299 } else if (err.code == "ENAMETOOLONG") {
3300 additionalError = 414;
3301 } else if (err.code == "EMFILE") {
3302 additionalError = 503;
3303 } else if (err.code == "ELOOP") {
3304 additionalError = 508;
3307 res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
3308 res.write(("<!DOCTYPE html><html><head><title>{errorMessage}</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /><style>html{background-color:#dfffdf;color:#000000;font-family:FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;margin:0.75em}body{background-color:#ffffff;padding:0.5em 0.5em 0.1em;margin:0.5em auto;width:90%;max-width:800px;-webkit-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15)}h1{text-align:center;font-size:2.25em;margin:0.3em 0 0.5em}code{background-color:#dfffdf;-webkit-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);display:block;padding:0.2em;font-family:\"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", Hack, Menlo, Consolas, Monaco, monospace;font-size:0.85em;margin:auto;width:95%;max-width:600px}table{width:95%;border-collapse:collapse;margin:auto;overflow-wrap:break-word;word-wrap:break-word;word-break:break-all;word-break:break-word;position:relative;z-index:0}table tbody{background-color:#ffffff;color:#000000}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);content:' ';position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}table img{margin:0;display:inline}th,tr{padding:0.15em;text-align:center}th{background-color:#007000;color:#ffffff}th a{color:#ffffff}td,th{padding:0.225em}td{text-align:left}tr:nth-child(odd){background-color:#dfffdf}hr{color:#ffffff}@media screen and (prefers-color-scheme: dark){html{background-color:#002000;color:#ffffff}body{background-color:#000f00;-webkit-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15)}code{background-color:#002000;-webkit-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1)}a{color:#ffffff}a:hover{color:#00ff00}table tbody{background-color:#000f00;color:#ffffff}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175)}tr:nth-child(odd){background-color:#002000}}</style></head><body><h1>{errorMessage}</h1><p>{errorDesc}</p>" + ((additionalError == 404) ? "" : "<p>Additionally, a {additionalError} error occurred while loading an error page.</p>") + "<p><i>{server}</i></p></body></html>").replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/ {2}/g, " ")).replace(/{path}/g, req.url.replace(/&/g, "&").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, "<").replace(/>/g, ">") + ((req.headers.host == undefined || isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"))).replace(/{contact}/g, serverAdmin.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")).replace(/{additionalError}/g, additionalError.toString())); // Replace placeholders in error response
3317 head = fs.existsSync("./.head") ? fs.readFileSync("./.head").toString() : (fs.existsSync("./head.html") ? fs.readFileSync("./head.html").toString() : ""); // header
3318 foot = fs.existsSync("./.foot") ? fs.readFileSync("./.foot").toString() : (fs.existsSync("./foot.html") ? fs.readFileSync("./foot.html").toString() : ""); // footer
3320 callServerError(500, err);
3324 // Function to perform HTTP redirection to a specified destination URL
3325 function redirect(destination, isTemporary, keepMethod, customHeaders) {
3326 // If keepMethod is a object, then save it to customHeaders
3327 if (typeof keepMethod == "object") customHeaders = keepMethod;
3329 // If isTemporary is a object, then save it to customHeaders
3330 if (typeof isTemporary == "object") customHeaders = isTemporary;
3332 // If customHeaders are not provided, get the default custom headers
3333 if (customHeaders === undefined) customHeaders = getCustomHeaders();
3335 // Set the "Location" header to the destination URL
3336 customHeaders["Location"] = destination;
3338 // Determine the status code for redirection based on the isTemporary and keepMethod flags
3339 var statusCode = keepMethod ? (isTemporary ? 307 : 308) : (isTemporary ? 302 : 301);
3341 // Write the response header with the appropriate status code and message
3342 res.writeHead(statusCode, http.STATUS_CODES[statusCode], customHeaders);
3344 // Log the redirection message
3345 serverconsole.resmessage("Client redirected to " + destination);
3350 // Return from the function
3354 // Function to parse incoming POST data from the request
3355 function parsePostData(options, callback) {
3356 // If the request method is not POST, return a 405 Method Not Allowed error
3357 if (req.method != "POST") {
3358 // Get the default custom headers and add "Allow" header with value "POST"
3359 var customHeaders = getCustomHeaders();
3360 customHeaders["Allow"] = "POST";
3362 // Call the server error function with 405 status code and custom headers
3363 callServerError(405, customHeaders);
3367 // Set formidableOptions to options, if provided; otherwise, set it to an empty object
3368 var formidableOptions = options ? options : {};
3370 // If no callback is provided, set the callback to options and reset formidableOptions
3373 formidableOptions = {};
3376 // If the formidable module had an error, call the server error function with 500 status code and error stack
3377 if (formidable._errored) callServerError(500, formidable._errored);
3379 // Create a new formidable form
3380 var form = formidable(formidableOptions);
3382 // Parse the request and process the fields and files
3383 form.parse(req, function (err, fields, files) {
3384 // If there was an error, call the server error function with status code determined by error
3386 if (err.httpCode) callServerError(err.httpCode);
3387 else callServerError(400);
3390 // Otherwise, call the provided callback function with the parsed fields and files
3391 callback(fields, files);
3395 // Authenticated user variable
3396 var authUser = null;
3398 // URL-related objects.
3401 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
3403 // Return an 400 error
3404 callServerError(400);
3405 serverconsole.errmessage("Bad request!");
3408 var search = uobject.search;
3409 var href = uobject.pathname;
3410 var ext = href.match(/[^\/]\.([^.]+)$/);
3412 else ext = ext[1].toLowerCase();
3413 var decodedHref = "";
3415 decodedHref = decodeURIComponent(href);
3417 // Return an 400 error
3418 callServerError(400);
3419 serverconsole.errmessage("Bad request!");
3422 var origHref = href; // Placeholder origHref
3424 if (req.headers["expect"] && req.headers["expect"] != "100-continue") {
3425 // Expectations not met.
3426 callServerError(417);
3430 // Mod execution function
3431 function modExecute(mods, ffinals) {
3432 // Prepare modFunction
3433 var modFunction = ffinals;
3434 var useMods = mods.slice();
3437 // Get list of forward proxy mods
3439 mods.forEach(function (mod) {
3440 if (mod.proxyCallback !== undefined) useMods.push(mod);
3444 useMods.reverse().forEach(function (modO) {
3445 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);
3448 // Execute modFunction
3452 var vresCalled = false;
3456 process.emitWarning("elseCallback() invoked multiple times.", {
3457 code: "WARN_SVRJS_MULTIPLE_ELSECALLBACK"
3464 // Function to check the level of a path relative to the web root
3465 function checkPathLevel(path) {
3466 // Split the path into an array of components based on "/"
3467 var pathComponents = path.split("/");
3469 // Initialize counters for level up (..) and level down (.)
3470 var levelUpCount = 0;
3471 var levelDownCount = 0;
3473 // Loop through the path components
3474 for (var i = 0; i < pathComponents.length; i++) {
3475 // If the component is "..", decrement the levelUpCount
3476 if (".." === pathComponents[i]) {
3479 // If the component is not "." or an empty string, increment the levelDownCount
3480 else if ("." !== pathComponents[i] && "" !== pathComponents[i]) {
3485 // Calculate the overall level by subtracting levelUpCount from levelDownCount
3486 var overallLevel = levelDownCount - levelUpCount;
3488 // Return the overall level
3489 return overallLevel;
3494 var eheaders = getCustomHeaders();
3495 eheaders["Content-Type"] = "text/html; charset=utf-8";
3496 res.writeHead(501, http.STATUS_CODES[501], eheaders);
3497 res.write("<!DOCTYPE html><html><head><title>Proxy not implemented</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /><style>html{background-color:#dfffdf;color:#000000;font-family:FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;margin:0.75em}body{background-color:#ffffff;padding:0.5em 0.5em 0.1em;margin:0.5em auto;width:90%;max-width:800px;-webkit-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15)}h1{text-align:center;font-size:2.25em;margin:0.3em 0 0.5em}code{background-color:#dfffdf;-webkit-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);display:block;padding:0.2em;font-family:\"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", Hack, Menlo, Consolas, Monaco, monospace;font-size:0.85em;margin:auto;width:95%;max-width:600px}table{width:95%;border-collapse:collapse;margin:auto;overflow-wrap:break-word;word-wrap:break-word;word-break:break-all;word-break:break-word;position:relative;z-index:0}table tbody{background-color:#ffffff;color:#000000}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);content:' ';position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}table img{margin:0;display:inline}th,tr{padding:0.15em;text-align:center}th{background-color:#007000;color:#ffffff}th a{color:#ffffff}td,th{padding:0.225em}td{text-align:left}tr:nth-child(odd){background-color:#dfffdf}hr{color:#ffffff}@media screen and (prefers-color-scheme: dark){html{background-color:#002000;color:#ffffff}body{background-color:#000f00;-webkit-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15)}code{background-color:#002000;-webkit-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1)}a{color:#ffffff}a:hover{color:#00ff00}table tbody{background-color:#000f00;color:#ffffff}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175)}tr:nth-child(odd){background-color:#002000}}</style></head><body><h1>Proxy not implemented</h1><p>SVR.JS doesn't support proxy without proxy mod. If you're administator of this server, then install this mod in order to use SVR.JS as a proxy.</p><p><i>" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "</i></p></body></html>");
3499 serverconsole.errmessage("SVR.JS doesn't support proxy without proxy mod.");
3503 if (req.method == "OPTIONS") {
3504 var hdss = getCustomHeaders();
3505 hdss["Allow"] = "GET, POST, HEAD, OPTIONS";
3506 res.writeHead(204, http.STATUS_CODES[204], hdss);
3509 } else if (req.method != "GET" && req.method != "POST" && req.method != "HEAD") {
3510 callServerError(405);
3511 serverconsole.errmessage("Invaild method: " + req.method);
3515 if (allowStatus && (href == "/svrjsstatus.svr" || (os.platform() == "win32" && href.toLowerCase() == "/svrjsstatus.svr"))) {
3516 function formatRelativeTime(relativeTime) {
3517 var days = Math.floor(relativeTime / 60 / (60 * 24));
3518 var dateDiff = new Date(relativeTime * 1000);
3519 return days + " days, " + dateDiff.getUTCHours() + " hours, " + dateDiff.getUTCMinutes() + " minutes, " + dateDiff.getUTCSeconds() + " seconds";
3521 var statusBody = "";
3522 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, "<").replace(/>/g, ">") + "<br/><hr/>";
3524 //Those entries are just dates and numbers converted/formatted to strings, so no escaping is needed.
3525 statusBody += "Current time: " + new Date().toString() + "<br/>Thread start time: " + new Date(new Date() - (process.uptime() * 1000)).toString() + "<br/>Thread uptime: " + formatRelativeTime(Math.floor(process.uptime())) + "<br/>";
3526 statusBody += "OS uptime: " + formatRelativeTime(os.uptime()) + "<br/>";
3527 statusBody += "Total request count: " + reqcounter + "<br/>";
3528 statusBody += "Average request rate: " + (Math.round((reqcounter / process.uptime()) * 100) / 100) + " requests/s<br/>";
3529 statusBody += "Client errors (4xx): " + err4xxcounter + "<br/>";
3530 statusBody += "Server errors (5xx): " + err5xxcounter + "<br/>";
3531 statusBody += "Average error rate: " + (Math.round(((err4xxcounter + err5xxcounter) / reqcounter) * 10000) / 100) + "%<br/>";
3532 statusBody += "Malformed HTTP requests: " + malformedcounter;
3533 if (process.memoryUsage) statusBody += "<br/>Memory usage of thread: " + sizify(process.memoryUsage().rss, true) + "B";
3534 if (process.cpuUsage) statusBody += "<br/>Total CPU usage by thread: u" + (process.cpuUsage().user / 1000) + "ms s" + (process.cpuUsage().system / 1000) + "ms - " + (Math.round((((process.cpuUsage().user + process.cpuUsage().system) / 1000000) / process.uptime()) * 1000) / 1000) + "%";
3535 statusBody += "<br/>Thread PID: " + process.pid + "<br/>";
3537 res.writeHead(200, http.STATUS_CODES[200], {
3538 "Content-Type": "text/html; charset=utf-8"
3540 res.end((head == "" ? "<!DOCTYPE html><html><head><title>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")) + "</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /><style>html{background-color:#dfffdf;color:#000000;font-family:FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;margin:0.75em}body{background-color:#ffffff;padding:0.5em 0.5em 0.1em;margin:0.5em auto;width:90%;max-width:800px;-webkit-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15)}h1{text-align:center;font-size:2.25em;margin:0.3em 0 0.5em}code{background-color:#dfffdf;-webkit-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);display:block;padding:0.2em;font-family:\"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", Hack, Menlo, Consolas, Monaco, monospace;font-size:0.85em;margin:auto;width:95%;max-width:600px}table{width:95%;border-collapse:collapse;margin:auto;overflow-wrap:break-word;word-wrap:break-word;word-break:break-all;word-break:break-word;position:relative;z-index:0}table tbody{background-color:#ffffff;color:#000000}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);content:' ';position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}table img{margin:0;display:inline}th,tr{padding:0.15em;text-align:center}th{background-color:#007000;color:#ffffff}th a{color:#ffffff}td,th{padding:0.225em}td{text-align:left}tr:nth-child(odd){background-color:#dfffdf}hr{color:#ffffff}@media screen and (prefers-color-scheme: dark){html{background-color:#002000;color:#ffffff}body{background-color:#000f00;-webkit-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15)}code{background-color:#002000;-webkit-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1)}a{color:#ffffff}a:hover{color:#00ff00}table tbody{background-color:#000f00;color:#ffffff}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175)}tr:nth-child(odd){background-color:#002000}}</style></head><body>" : head.replace(/<head>/i, "<head><title>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")) + "</title>")) + "<h1>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")) + "</h1>" + statusBody + (foot == "" ? "</body></html>" : foot));
3544 var dHref = decodeURIComponent(href);
3545 var readFrom = "." + dHref;
3546 var dirImagesMissing = false;
3547 fs.stat(readFrom, function (err, stats) {
3549 if (err.code == "ENOENT") {
3550 if (__dirname != process.cwd() && dHref.match(/^\/\.dirimages\/(?:(?!\.png$).)+\.png$/)) {
3551 dirImagesMissing = true;
3552 readFrom = __dirname + dHref;
3554 callServerError(404);
3555 serverconsole.errmessage("Resource not found.");
3558 } else if (err.code == "ENOTDIR") {
3559 callServerError(404); // Assume that file doesn't exist.
3560 serverconsole.errmessage("Resource not found.");
3562 } else if (err.code == "EACCES") {
3563 callServerError(403);
3564 serverconsole.errmessage("Access denied.");
3566 } else if (err.code == "ENAMETOOLONG") {
3567 callServerError(414);
3569 } else if (err.code == "EMFILE") {
3570 callServerError(503);
3572 } else if (err.code == "ELOOP") {
3573 callServerError(508); // The symbolic link loop is detected during file system operations.
3574 serverconsole.errmessage("Symbolic link loop detected.");
3577 callServerError(500, err);
3582 // Check if index file exists
3583 if (!dirImagesMissing && (req.url == "/" || stats.isDirectory())) {
3584 fs.stat((readFrom + "/index.html").replace(/\/+/g, "/"), function (e, s) {
3585 if (e || !s.isFile()) {
3586 fs.stat((readFrom + "/index.htm").replace(/\/+/g, "/"), function (e, s) {
3587 if (e || !s.isFile()) {
3588 fs.stat((readFrom + "/index.xhtml").replace(/\/+/g, "/"), function (e, s) {
3589 if (e || !s.isFile()) {
3590 properDirectoryListingAndStaticFileServe();
3594 readFrom = (readFrom + "/index.xhtml").replace(/\/+/g, "/");
3595 properDirectoryListingAndStaticFileServe();
3601 readFrom = (readFrom + "/index.htm").replace(/\/+/g, "/");
3602 properDirectoryListingAndStaticFileServe();
3608 readFrom = (readFrom + "/index.html").replace(/\/+/g, "/");
3609 properDirectoryListingAndStaticFileServe();
3612 } else if (dirImagesMissing) {
3613 fs.stat(readFrom, function (e, s) {
3614 if (e || !s.isFile()) {
3615 properDirectoryListingAndStaticFileServe();
3618 properDirectoryListingAndStaticFileServe();
3622 properDirectoryListingAndStaticFileServe();
3625 function properDirectoryListingAndStaticFileServe() {
3626 if (stats.isFile()) {
3627 var acceptEncoding = req.headers["accept-encoding"];
3628 if (!acceptEncoding) acceptEncoding = "";
3630 var filelen = stats.size;
3633 var fileETag = undefined;
3634 if (configJSON.enableETag == undefined || configJSON.enableETag) {
3635 fileETag = generateETag(href, stats);
3636 // Check if the client's request matches the ETag value (If-None-Match)
3637 var clientETag = req.headers["if-none-match"];
3638 if (clientETag === fileETag) {
3639 res.writeHead(304, http.STATUS_CODES[304], {
3646 // Check if the client's request doesn't match the ETag value (If-Match)
3647 var ifMatchETag = req.headers["if-match"];
3648 if (ifMatchETag && ifMatchETag !== "*" && ifMatchETag !== fileETag) {
3649 callServerError(412, {
3656 // Handle partial content request
3657 if (ext != "html" && req.headers["range"]) {
3659 var rhd = getCustomHeaders();
3660 rhd["Accept-Ranges"] = "bytes";
3661 rhd["Content-Range"] = "bytes */" + filelen;
3662 var regexmatch = req.headers["range"].match(/bytes=([0-9]*)-([0-9]*)/);
3664 callServerError(416, rhd);
3666 // Process the partial content request
3667 var beginOrig = regexmatch[1];
3668 var endOrig = regexmatch[2];
3670 var end = filelen - 1;
3671 if (beginOrig == "" && endOrig == "") {
3672 callServerError(416, rhd);
3674 } else if (beginOrig == "") {
3675 begin = end - parseInt(endOrig) + 1;
3677 begin = parseInt(beginOrig);
3678 if (endOrig != "") end = parseInt(endOrig);
3680 if (begin > end || begin < 0 || begin > filelen - 1) {
3681 callServerError(416, rhd);
3684 if (end > filelen - 1) end = filelen - 1;
3685 rhd["Content-Range"] = "bytes " + begin + "-" + end + "/" + filelen;
3686 rhd["Content-Length"] = end - begin + 1;
3687 if (!(mime.contentType(ext) == false) && ext != "") rhd["Content-Type"] = mime.contentType(ext);
3688 if (fileETag) rhd["ETag"] = fileETag;
3690 if (req.method != "HEAD") {
3691 var readStream = fs.createReadStream(readFrom, {
3695 readStream.on("error", function (err) {
3696 if (err.code == "ENOENT") {
3697 callServerError(404);
3698 serverconsole.errmessage("Resource not found.");
3699 } else if (err.code == "ENOTDIR") {
3700 callServerError(404); // Assume that file doesn't exist.
3701 serverconsole.errmessage("Resource not found.");
3702 } else if (err.code == "EACCES") {
3703 callServerError(403);
3704 serverconsole.errmessage("Access denied.");
3705 } else if (err.code == "ENAMETOOLONG") {
3706 callServerError(414);
3707 } else if (err.code == "EMFILE") {
3708 callServerError(503);
3709 } else if (err.code == "ELOOP") {
3710 callServerError(508); // The symbolic link loop is detected during file system operations.
3711 serverconsole.errmessage("Symbolic link loop detected.");
3713 callServerError(500, err);
3715 }).on("open", function () {
3717 res.writeHead(206, http.STATUS_CODES[206], rhd);
3718 readStream.pipe(res);
3719 serverconsole.resmessage("Client successfully received content.");
3721 callServerError(500, err);
3725 res.writeHead(206, http.STATUS_CODES[206], rhd);
3730 callServerError(500, err);
3733 // Helper function to check if compression is allowed for the file
3734 function canCompress(path, list) {
3735 var canCompress = true;
3736 for (var i = 0; i < list.length; i++) {
3737 if (createRegex(list[i], true).test(path)) {
3738 canCompress = false;
3745 var useBrotli = (ext != "br" && filelen > 256 && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/));
3746 var useDeflate = (ext != "zip" && filelen > 256 && acceptEncoding.match(/\bdeflate\b/));
3747 var useGzip = (ext != "gz" && filelen > 256 && acceptEncoding.match(/\bgzip\b/));
3749 var isCompressable = true;
3751 // Check for files not to compressed and compression enabling setting. Also check for browser quirks and adjust compression accordingly
3752 if((!useBrotli && !useDeflate && !useGzip) || configJSON.enableCompression !== true || !canCompress(href, dontCompress)) {
3753 isCompressable = false; // Compression is disabled
3754 } else if (ext != "html" && ext != "htm" && ext != "xhtml" && ext != "xht" && ext != "shtml") {
3755 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"]))) {
3756 isCompressable = false; // Netscape 4.x doesn't handle compressed data properly outside of HTML documents.
3757 } else if (/^w3m\/[^ ]*$/.test(req.headers["user-agent"])) {
3758 isCompressable = false; // w3m doesn't handle compressed data properly outside of HTML documents.
3761 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"]))) {
3762 isCompressable = false; // Netscape 4.06-4.08 doesn't handle compressed data properly.
3766 callServerError(500, err);
3770 // Bun 1.1 has definition for zlib.createBrotliCompress, but throws an error while invoking the function.
3771 if (process.isBun && useBrotli && isCompressable) {
3773 zlib.createBrotliCompress();
3781 if (useBrotli && isCompressable) {
3782 hdhds["Content-Encoding"] = "br";
3783 } else if (useDeflate && isCompressable) {
3784 hdhds["Content-Encoding"] = "deflate";
3785 } else if (useGzip && isCompressable) {
3786 hdhds["Content-Encoding"] = "gzip";
3788 if (ext == "html") {
3789 hdhds["Content-Length"] = head.length + filelen + foot.length;
3791 hdhds["Content-Length"] = filelen;
3794 if (ext != "html") hdhds["Accept-Ranges"] = "bytes";
3795 delete hdhds["Content-Type"];
3796 if (!(mime.contentType(ext) == false) && ext != "") hdhds["Content-Type"] = mime.contentType(ext);
3797 if (fileETag) hdhds["ETag"] = fileETag;
3799 if (req.method != "HEAD") {
3800 var readStream = fs.createReadStream(readFrom);
3801 readStream.on("error", function (err) {
3802 if (err.code == "ENOENT") {
3803 callServerError(404);
3804 serverconsole.errmessage("Resource not found.");
3805 } else if (err.code == "ENOTDIR") {
3806 callServerError(404); // Assume that file doesn't exist.
3807 serverconsole.errmessage("Resource not found.");
3808 } else if (err.code == "EACCES") {
3809 callServerError(403);
3810 serverconsole.errmessage("Access denied.");
3811 } else if (err.code == "ENAMETOOLONG") {
3812 callServerError(414);
3813 } else if (err.code == "EMFILE") {
3814 callServerError(503);
3815 } else if (err.code == "ELOOP") {
3816 callServerError(508); // The symbolic link loop is detected during file system operations.
3817 serverconsole.errmessage("Symbolic link loop detected.");
3819 callServerError(500, err);
3821 }).on("open", function () {
3824 if (useBrotli && isCompressable) {
3825 resStream = zlib.createBrotliCompress();
3826 resStream.pipe(res);
3827 } else if (useDeflate && isCompressable) {
3828 resStream = zlib.createDeflateRaw();
3829 resStream.pipe(res);
3830 } else if (useGzip && isCompressable) {
3831 resStream = zlib.createGzip();
3832 resStream.pipe(res);
3836 if (ext == "html") {
3837 function afterWriteCallback() {
3838 readStream.on("end", function () {
3839 resStream.end(foot);
3841 readStream.pipe(resStream, {
3845 res.writeHead(200, http.STATUS_CODES[200], hdhds);
3846 if (!resStream.write(head)) {
3847 resStream.on("drain", afterWriteCallback);
3849 process.nextTick(afterWriteCallback);
3852 res.writeHead(200, http.STATUS_CODES[200], hdhds);
3853 readStream.pipe(resStream);
3855 serverconsole.resmessage("Client successfully received content.");
3857 callServerError(500, err);
3861 res.writeHead(200, http.STATUS_CODES[200], hdhds);
3863 serverconsole.resmessage("Client successfully received content.");
3866 callServerError(500, err);
3869 } else if (stats.isDirectory()) {
3870 // Check if directory listing is enabled in the configuration
3871 if (checkForEnabledDirectoryListing(req.headers.host, req.socket ? req.socket.localAddress : undefined)) {
3872 var customDirListingHeader = "";
3873 var customDirListingFooter = "";
3875 function getCustomDirListingHeader(callback) {
3876 fs.readFile(("." + dHref + "/.dirhead").replace(/\/+/g, "/"), function (err, data) {
3878 if (err.code == "ENOENT" || err.code == "EISDIR") {
3879 if (os.platform != "win32" || href != "/") {
3880 fs.readFile(("." + dHref + "/HEAD.html").replace(/\/+/g, "/"), function (err, data) {
3882 if (err.code == "ENOENT" || err.code == "EISDIR") {
3885 callServerError(500, err);
3888 customDirListingHeader = data.toString();
3896 callServerError(500, err);
3899 customDirListingHeader = data.toString();
3905 function getCustomDirListingFooter(callback) {
3906 fs.readFile(("." + dHref + "/.dirfoot").replace(/\/+/g, "/"), function (err, data) {
3908 if (err.code == "ENOENT" || err.code == "EISDIR") {
3909 if (os.platform != "win32" || href != "/") {
3910 fs.readFile(("." + dHref + "/FOOT.html").replace(/\/+/g, "/"), function (err, data) {
3912 if (err.code == "ENOENT" || err.code == "EISDIR") {
3915 callServerError(500, err);
3918 customDirListingFooter = data.toString();
3926 callServerError(500, err);
3929 customDirListingFooter = data.toString();
3935 // Read custom header and footer content (if available)
3936 getCustomDirListingHeader(function () {
3937 getCustomDirListingFooter(function () {
3938 // Check if custom header has HTML tag
3939 var headerHasHTMLTag = customDirListingHeader.replace(/<!--(?:(?:(?!--\>)[\s\S])*|)(?:-->|$)/g, "").match(/<html(?![a-zA-Z0-9])(?:"(?:\\(?:[\s\S]|$)|[^\\"])*(?:"|$)|'(?:\\(?:[\s\S]|$)|[^\\'])*(?:'|$)|[^'">])*(?:>|$)/i);
3941 // Generate HTML head and footer based on configuration and custom content
3942 var htmlHead = (!configJSON.enableDirectoryListingWithDefaultHead || head == "" ?
3943 (!headerHasHTMLTag ?
3944 "<!DOCTYPE html><html><head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "</title><meta charset=\"UTF-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /><style>html{background-color:#dfffdf;color:#000000;font-family:FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;margin:0.75em}body{background-color:#ffffff;padding:0.5em 0.5em 0.1em;margin:0.5em auto;width:90%;max-width:800px;-webkit-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15);box-shadow:0 5px 10px 0 rgba(0, 0, 0, 0.15)}h1{text-align:center;font-size:2.25em;margin:0.3em 0 0.5em}code{background-color:#dfffdf;-webkit-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.1);display:block;padding:0.2em;font-family:\"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", Hack, Menlo, Consolas, Monaco, monospace;font-size:0.85em;margin:auto;width:95%;max-width:600px}table{width:95%;border-collapse:collapse;margin:auto;overflow-wrap:break-word;word-wrap:break-word;word-break:break-all;word-break:break-word;position:relative;z-index:0}table tbody{background-color:#ffffff;color:#000000}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);box-shadow:0 4px 8px 0 rgba(0, 0, 0, 0.175);content:' ';position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}table img{margin:0;display:inline}th,tr{padding:0.15em;text-align:center}th{background-color:#007000;color:#ffffff}th a{color:#ffffff}td,th{padding:0.225em}td{text-align:left}tr:nth-child(odd){background-color:#dfffdf}hr{color:#ffffff}@media screen and (prefers-color-scheme: dark){html{background-color:#002000;color:#ffffff}body{background-color:#000f00;-webkit-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);-moz-box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15);box-shadow:0 5px 10px 0 rgba(127, 127, 127, 0.15)}code{background-color:#002000;-webkit-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);-moz-box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1);box-shadow:0 2px 4px 0 rgba(127, 127, 127, 0.1)}a{color:#ffffff}a:hover{color:#00ff00}table tbody{background-color:#000f00;color:#ffffff}table tbody:after{-webkit-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);-moz-box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175);box-shadow:0 4px 8px 0 rgba(127, 127, 127, 0.175)}tr:nth-child(odd){background-color:#002000}}</style></head><body>" :
3945 customDirListingHeader.replace(/<head>/i, "<head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "</title>")) :
3946 head.replace(/<head>/i, "<head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "</title>")) +
3947 (!headerHasHTMLTag ? customDirListingHeader : "") +
3948 "<h1>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "</h1><table id=\"directoryListing\"> <tr> <th></th> <th>Filename</th> <th>Size</th> <th>Date</th> </tr>" + (checkPathLevel(decodeURIComponent(origHref)) < 1 ? "" : "<tr><td style=\"width: 24px;\"><img src=\"/.dirimages/return.png\" width=\"24px\" height=\"24px\" alt=\"[RET]\" /></td><td style=\"word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;\"><a href=\"" + (origHref).replace(/\/+/g, "/").replace(/\/[^\/]*\/?$/, "/") + "\">Return</a></td><td></td><td></td></tr>");
3950 var htmlFoot = "</table><p><i>" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + (req.headers.host == undefined ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")) + "</i></p>" + customDirListingFooter + (!configJSON.enableDirectoryListingWithDefaultHead || foot == "" ? "</body></html>" : foot);
3952 if (fs.existsSync("." + decodeURIComponent(href) + "/.maindesc".replace(/\/+/g, "/"))) {
3953 htmlFoot = "</table><hr/>" + fs.readFileSync("." + decodeURIComponent(href) + "/.maindesc".replace(/\/+/g, "/")) + htmlFoot;
3956 fs.readdir(readFrom, function (err, list) {
3961 // Function to get stats for all files in the directory
3962 function getStatsForAllFilesI(fileList, callback, prefix, pushArray, index) {
3963 if (fileList.length == 0) {
3964 callback(pushArray);
3968 fs.stat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) {
3970 fs.lstat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) {
3972 name: fileList[index],
3973 stats: err ? null : stats,
3976 if (index < fileList.length - 1) {
3977 getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
3979 callback(pushArray);
3984 name: fileList[index],
3988 if (index < fileList.length - 1) {
3989 getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
3991 callback(pushArray);
3997 // Wrapper function to get stats for all files
3998 function getStatsForAllFiles(fileList, prefix, callback) {
3999 if (!prefix) prefix = "";
4000 getStatsForAllFilesI(fileList, callback, prefix, [], 0);
4003 // Get stats for all files in the directory and generate the listing
4004 getStatsForAllFiles(list, readFrom, function (filelist) {
4005 var directoryListingRows = [];
4006 for (var i = 0; i < filelist.length; i++) {
4007 if (filelist[i].name[0] !== ".") {
4008 var estats = filelist[i].stats;
4009 var ename = filelist[i].name;
4010 var eext = ename.match(/\.([^.]+)$/);
4011 eext = eext ? eext[1] : "";
4012 var emime = eext ? mime.contentType(eext) : false;
4013 if (filelist[i].errored) {
4014 directoryListingRows.push(
4015 "<tr><td style=\"width: 24px;\"><img src=\"/.dirimages/bad.png\" alt=\"[BAD]\" width=\"24px\" height=\"24px\" /></td><td style=\"word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;\"><a href=\"" +
4016 (href + "/" + encodeURI(ename)).replace(/\/+/g, "/") +
4018 ename.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") +
4019 "</a></td><td>-</td><td>" +
4020 (estats ? estats.mtime.toDateString() : "-") +
4024 var entry = "<tr><td style=\"width: 24px;\"><img src=\"[img]\" alt=\"[alt]\" width=\"24px\" height=\"24px\" /></td><td style=\"word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;\"><a href=\"" +
4025 (origHref + "/" + encodeURIComponent(ename)).replace(/\/+/g, "/") +
4026 (estats.isDirectory() ? "/" : "") +
4028 ename.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") +
4030 (estats.isDirectory() ? "-" : sizify(estats.size.toString())) +
4032 estats.mtime.toDateString() +
4035 // Determine the file type and set the appropriate image and alt text
4036 if (estats.isDirectory()) {
4037 entry = entry.replace("[img]", "/.dirimages/directory.png").replace("[alt]", "[DIR]");
4038 } else if (!estats.isFile()) {
4039 entry = "<tr><td style=\"width: 24px;\"><img src=\"[img]\" alt=\"[alt]\" width=\"24px\" height=\"24px\" /></td><td style=\"word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;\"><a href=\"" +
4040 (origHref + "/" + encodeURIComponent(ename)).replace(/\/+/g, "/") +
4042 ename.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") +
4043 "</a></td><td>-</td><td>" +
4044 estats.mtime.toDateString() +
4047 // Determine the special file types (block device, character device, etc.)
4048 if (estats.isBlockDevice()) {
4049 entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[BLK]");
4050 } else if (estats.isCharacterDevice()) {
4051 entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[CHR]");
4052 } else if (estats.isFIFO()) {
4053 entry = entry.replace("[img]", "/.dirimages/fifo.png").replace("[alt]", "[FIF]");
4054 } else if (estats.isSocket()) {
4055 entry = entry.replace("[img]", "/.dirimages/socket.png").replace("[alt]", "[SCK]");
4057 } else if (ename.match(/README|LICEN[SC]E/i)) {
4058 entry = entry.replace("[img]", "/.dirimages/important.png").replace("[alt]", "[IMP]");
4059 } else if (eext.match(/^(?:[xs]?html?|xml)$/i)) {
4060 entry = entry.replace("[img]", "/.dirimages/html.png").replace("[alt]", (eext == "xml" ? "[XML]" : "[HTM]"));
4061 } else if (eext == "js") {
4062 entry = entry.replace("[img]", "/.dirimages/javascript.png").replace("[alt]", "[JS ]");
4063 } else if (eext == "php") {
4064 entry = entry.replace("[img]", "/.dirimages/php.png").replace("[alt]", "[PHP]");
4065 } else if (eext == "css") {
4066 entry = entry.replace("[img]", "/.dirimages/css.png").replace("[alt]", "[CSS]");
4067 } else if (emime && emime.split("/")[0] == "image") {
4068 entry = entry.replace("[img]", "/.dirimages/image.png").replace("[alt]", (eext == "ico" ? "[ICO]" : "[IMG]"));
4069 } else if (emime && emime.split("/")[0] == "font") {
4070 entry = entry.replace("[img]", "/.dirimages/font.png").replace("[alt]", "[FON]");
4071 } else if (emime && emime.split("/")[0] == "audio") {
4072 entry = entry.replace("[img]", "/.dirimages/audio.png").replace("[alt]", "[AUD]");
4073 } else if ((emime && emime.split("/")[0] == "text") || eext == "json") {
4074 entry = entry.replace("[img]", "/.dirimages/text.png").replace("[alt]", (eext == "json" ? "[JSO]" : "[TXT]"));
4075 } else if (emime && emime.split("/")[0] == "video") {
4076 entry = entry.replace("[img]", "/.dirimages/video.png").replace("[alt]", "[VID]");
4077 } else if (eext.match(/^(?:zip|rar|bz2|[gb7x]z|lzma|tar)$/i)) {
4078 entry = entry.replace("[img]", "/.dirimages/archive.png").replace("[alt]", "[ARC]");
4079 } else if (eext.match(/^(?:[id]mg|iso|flp)$/i)) {
4080 entry = entry.replace("[img]", "/.dirimages/diskimage.png").replace("[alt]", "[DSK]");
4082 entry = entry.replace("[img]", "/.dirimages/other.png").replace("[alt]", "[OTH]");
4084 directoryListingRows.push(entry);
4089 // Push the information about empty directory
4090 if (directoryListingRows.length == 0) {
4091 directoryListingRows.push("<tr><td></td><td>No files found</td><td></td><td></td></tr>");
4094 // Send the directory listing response
4095 res.writeHead(200, http.STATUS_CODES[200], {
4096 "Content-Type": "text/html; charset=utf-8"
4098 res.end(htmlHead + directoryListingRows.join("") + htmlFoot);
4099 serverconsole.resmessage("Client successfully received content.");
4103 if (err.code == "ENOENT") {
4104 callServerError(404);
4105 serverconsole.errmessage("Resource not found.");
4106 } else if (err.code == "ENOTDIR") {
4107 callServerError(404); // Assume that file doesn't exist.
4108 serverconsole.errmessage("Resource not found.");
4109 } else if (err.code == "EACCES") {
4110 callServerError(403);
4111 serverconsole.errmessage("Access denied.");
4112 } else if (err.code == "ENAMETOOLONG") {
4113 callServerError(414);
4114 } else if (err.code == "EMFILE") {
4115 callServerError(503);
4116 } else if (err.code == "ELOOP") {
4117 callServerError(508); // The symbolic link loop is detected during file system operations.
4118 serverconsole.errmessage("Symbolic link loop detected.");
4120 callServerError(500, err);
4127 // Directory listing is disabled, call 403 Forbidden error
4128 callServerError(403);
4129 serverconsole.errmessage("Directory listing is disabled.");
4132 callServerError(501);
4133 serverconsole.errmessage("SVR.JS doesn't support block devices, character devices, FIFOs nor sockets.");
4141 // Scan the block list
4142 if (blocklist.check(reqip)) {
4143 // Invoke 403 Forbidden error
4144 callServerError(403);
4145 serverconsole.errmessage("Client is in the block list.");
4149 if (req.url == "*") {
4151 if (req.method == "OPTIONS") {
4152 // Respond with list of methods
4153 var hdss = getCustomHeaders();
4154 hdss["Allow"] = "GET, POST, HEAD, OPTIONS";
4155 res.writeHead(204, http.STATUS_CODES[204], hdss);
4159 // SVR.JS doesn't understand that request, so throw an 400 error
4160 callServerError(400);
4165 if (req.method == "CONNECT") {
4166 // CONNECT requests should be handled in "connect" event.
4167 callServerError(501);
4168 serverconsole.errmessage("CONNECT requests aren't supported. Your JS runtime probably doesn't support 'connect' handler for HTTP library.");
4172 // Check for invalid X-Forwarded-For header
4173 if (!isForwardedValid) {
4174 serverconsole.errmessage("X-Forwarded-For header is invalid.");
4175 callServerError(400);
4180 var sanitizedHref = sanitizeURL(href, allowDoubleSlashes);
4181 var preparedReqUrl = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
4183 // Check if URL is "dirty"
4184 if (href != sanitizedHref && !isProxy) {
4185 var sanitizedURL = uobject;
4186 sanitizedURL.path = null;
4187 sanitizedURL.href = null;
4188 sanitizedURL.pathname = sanitizedHref;
4189 sanitizedURL.hostname = null;
4190 sanitizedURL.host = null;
4191 sanitizedURL.port = null;
4192 sanitizedURL.protocol = null;
4193 sanitizedURL.slashes = null;
4194 sanitizedURL = url.format(sanitizedURL);
4195 serverconsole.resmessage("URL sanitized: " + req.url + " => " + sanitizedURL);
4196 if (rewriteDirtyURLs) {
4197 req.url = sanitizedURL;
4199 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4201 // Return an 400 error
4202 callServerError(400);
4203 serverconsole.errmessage("Bad request!");
4206 search = uobject.search;
4207 href = uobject.pathname;
4208 ext = href.match(/[^\/]\.([^.]+)$/);
4210 else ext = ext[1].toLowerCase();
4212 decodedHref = decodeURIComponent(href);
4215 callServerError(400);
4216 serverconsole.errmessage("Bad request!");
4220 redirect(sanitizedURL, false);
4223 } else if (req.url != preparedReqUrl && !isProxy) {
4224 serverconsole.resmessage("URL sanitized: " + req.url + " => " + preparedReqUrl);
4225 if (rewriteDirtyURLs) {
4226 req.url = preparedReqUrl;
4228 redirect(preparedReqUrl, false);
4233 // Handle redirects to HTTPS
4234 if (secure && !fromMain && !disableNonEncryptedServer && !disableToHTTPSRedirect) {
4235 var hostx = req.headers.host;
4236 if (hostx === undefined) {
4237 serverconsole.errmessage("Host header is missing.");
4238 callServerError(400);
4243 callServerError(501);
4244 serverconsole.errmessage("This server will never be a proxy.");
4248 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);
4250 var destinationPort = 0;
4252 var parsedHostx = hostx.match(/(\[[^\]]*\]|[^:]*)(?::([0-9]+))?/);
4253 var hostname = parsedHostx[1];
4254 var hostPort = parsedHostx[2] ? parseInt(parsedHostx[2]) : 80;
4255 if (isNaN(hostPort)) hostPort = 80;
4257 if (hostPort == port || (port == pubport && !isPublicServer)) {
4258 destinationPort = sport;
4260 destinationPort = spubport;
4263 redirect("https://" + hostname + (destinationPort == 443 ? "" : (":" + destinationPort)) + req.url);
4267 // Handle redirects to addresses with "www." prefix
4269 var hostname = req.headers.host.split(":");
4270 var hostport = null;
4271 if (hostname.length > 1 && (hostname[0] != "[" || hostname[hostname.length - 1] != "]")) hostport = hostname.pop();
4272 hostname = hostname.join(":");
4273 if (hostname == domain && hostname.indexOf("www.") != 0) {
4274 redirect((req.socket.encrypted ? "https" : "http") + "://www." + hostname + (hostport ? ":" + hostport : "") + req.url.replace(/\/+/g, "/"));
4279 // Handle URL rewriting
4280 function rewriteURL(address, map, callback, _fileState, _mapBegIndex) {
4281 var rewrittenURL = address;
4282 var doCallback = true;
4284 for (var i = (_mapBegIndex ? _mapBegIndex : 0); i < map.length; i++) {
4285 var mapEntry = map[i];
4286 if (href != "/" && (mapEntry.isNotDirectory || mapEntry.isNotFile) && !_fileState) {
4287 fs.stat("." + decodeURIComponent(href), function (err, stats) {
4291 } else if (stats.isDirectory()) {
4293 } else if (stats.isFile()) {
4298 rewriteURL(address, map, callback, _fileState, i);
4303 var tempRewrittenURL = rewrittenURL;
4304 if (!mapEntry.allowDoubleSlashes) {
4305 address = address.replace(/\/+/g,"/");
4306 tempRewrittenURL = address;
4308 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)) {
4309 rewrittenURL = tempRewrittenURL;
4311 mapEntry.replacements.forEach(function (replacement) {
4312 rewrittenURL = rewrittenURL.replace(createRegex(replacement.regex), replacement.replacement);
4314 if (mapEntry.append) rewrittenURL += mapEntry.append;
4317 callback(err, null);
4323 if (doCallback) callback(null, rewrittenURL);
4326 // Trailing slash redirection
4327 function redirectTrailingSlashes(callback) {
4328 if (!isProxy && !disableTrailingSlashRedirects && href[href.length - 1] != "/" && origHref[origHref.length - 1] != "/") {
4329 fs.stat("." + decodeURIComponent(href), function (err, stats) {
4330 if (err || !stats.isDirectory()) {
4334 callServerError(500, err);
4337 var destinationURL = uobject;
4338 destinationURL.path = null;
4339 destinationURL.href = null;
4340 destinationURL.pathname = origHref + "/";
4341 destinationURL.hostname = null;
4342 destinationURL.host = null;
4343 destinationURL.port = null;
4344 destinationURL.protocol = null;
4345 destinationURL.slashes = null;
4346 destinationURL = url.format(destinationURL);
4347 redirect(destinationURL);
4357 // Add web root postfixes
4359 var preparedReqUrl3 = (allowPostfixDoubleSlashes ? (href.replace(/\/+/,"/") + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "")) : req.url);
4360 var urlWithPostfix = preparedReqUrl3;
4361 var postfixPrefix = "";
4362 wwwrootPostfixPrefixesVHost.every(function (currentPostfixPrefix) {
4363 if (preparedReqUrl3.indexOf(currentPostfixPrefix) == 0) {
4364 if (currentPostfixPrefix.match(/\/+$/)) postfixPrefix = currentPostfixPrefix.replace(/\/+$/, "");
4365 else if (urlWithPostfix.length == currentPostfixPrefix.length || urlWithPostfix[currentPostfixPrefix.length] == "?" || urlWithPostfix[currentPostfixPrefix.length] == "/" || urlWithPostfix[currentPostfixPrefix.length] == "#") postfixPrefix = currentPostfixPrefix;
4367 urlWithPostfix = urlWithPostfix.substring(postfixPrefix.length);
4373 wwwrootPostfixesVHost.every(function (postfixEntry) {
4374 if (matchHostname(postfixEntry.host) && ipMatch(postfixEntry.ip, req.socket ? req.socket.localAddress : undefined) && !(postfixEntry.skipRegex && preparedReqUrl3.match(createRegex(postfixEntry.skipRegex)))) {
4375 urlWithPostfix = postfixPrefix + "/" + postfixEntry.postfix + urlWithPostfix;
4381 if (urlWithPostfix != preparedReqUrl3) {
4382 serverconsole.resmessage("Added web root postfix: " + req.url + " => " + urlWithPostfix);
4383 req.url = urlWithPostfix;
4385 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4387 // Return an 400 error
4388 callServerError(400);
4389 serverconsole.errmessage("Bad request!");
4392 search = uobject.search;
4393 href = uobject.pathname;
4394 ext = href.match(/[^\/]\.([^.]+)$/);
4396 else ext = ext[1].toLowerCase();
4399 decodedHref = decodeURIComponent(href);
4402 callServerError(400);
4403 serverconsole.errmessage("Bad request!");
4407 var sHref = sanitizeURL(href, allowDoubleSlashes);
4408 var preparedReqUrl2 = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
4410 if (req.url != preparedReqUrl2 || sHref != href.replace(/\/\.(?=\/|$)/g, "/").replace(/\/+/g, "/")) {
4411 callServerError(403);
4412 serverconsole.errmessage("Content blocked.");
4414 } else if (sHref != href) {
4415 var rewrittenAgainURL = uobject;
4416 rewrittenAgainURL.path = null;
4417 rewrittenAgainURL.href = null;
4418 rewrittenAgainURL.pathname = sHref;
4419 rewrittenAgainURL.hostname = null;
4420 rewrittenAgainURL.host = null;
4421 rewrittenAgainURL.port = null;
4422 rewrittenAgainURL.protocol = null;
4423 rewrittenAgainURL.slashes = null;
4424 rewrittenAgainURL = url.format(rewrittenAgainURL);
4425 serverconsole.resmessage("URL sanitized: " + req.url + " => " + rewrittenAgainURL);
4426 req.url = rewrittenAgainURL;
4428 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4430 // Return an 400 error
4431 callServerError(400);
4432 serverconsole.errmessage("Bad request!");
4435 search = uobject.search;
4436 href = uobject.pathname;
4437 ext = href.match(/[^\/]\.([^.]+)$/);
4439 else ext = ext[1].toLowerCase();
4441 decodedHref = decodeURIComponent(href);
4444 callServerError(400);
4445 serverconsole.errmessage("Bad request!");
4453 rewriteURL(req.url, rewriteMap, function (err, rewrittenURL) {
4455 callServerError(500, err);
4458 if (rewrittenURL != req.url) {
4459 serverconsole.resmessage("URL rewritten: " + req.url + " => " + rewrittenURL);
4460 req.url = rewrittenURL;
4462 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4464 // Return an 400 error
4465 callServerError(400);
4466 serverconsole.errmessage("Bad request!");
4469 search = uobject.search;
4470 href = uobject.pathname;
4471 ext = href.match(/[^\/]\.([^.]+)$/);
4473 else ext = ext[1].toLowerCase();
4475 decodedHref = decodeURIComponent(href);
4478 callServerError(400);
4479 serverconsole.errmessage("Bad request!");
4483 var sHref = sanitizeURL(href, allowDoubleSlashes);
4484 var preparedReqUrl2 = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
4486 if (req.url != preparedReqUrl2 || sHref != href.replace(/\/\.(?=\/|$)/g, "/").replace(/\/+/g, "/")) {
4487 callServerError(403);
4488 serverconsole.errmessage("Content blocked.");
4490 } else if (sHref != href) {
4491 var rewrittenAgainURL = uobject;
4492 rewrittenAgainURL.path = null;
4493 rewrittenAgainURL.href = null;
4494 rewrittenAgainURL.pathname = sHref;
4495 rewrittenAgainURL.hostname = null;
4496 rewrittenAgainURL.host = null;
4497 rewrittenAgainURL.port = null;
4498 rewrittenAgainURL.protocol = null;
4499 rewrittenAgainURL.slashes = null;
4500 rewrittenAgainURL = url.format(rewrittenAgainURL);
4501 serverconsole.resmessage("URL sanitized: " + req.url + " => " + rewrittenAgainURL);
4502 req.url = rewrittenAgainURL;
4504 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4506 // Return an 400 error
4507 callServerError(400);
4508 serverconsole.errmessage("Bad request!");
4511 search = uobject.search;
4512 href = uobject.pathname;
4513 ext = href.match(/[^\/]\.([^.]+)$/);
4515 else ext = ext[1].toLowerCase();
4517 decodedHref = decodeURIComponent(href);
4520 callServerError(400);
4521 serverconsole.errmessage("Bad request!");
4526 // Set response headers
4528 var hkh = getCustomHeaders();
4529 Object.keys(hkh).forEach(function (hkS) {
4531 res.setHeader(hkS, hkh[hkS]);
4533 // Headers will not be set.
4538 // Prepare the path (remove multiple slashes)
4539 var decodedHrefWithoutDuplicateSlashes = decodedHref.replace(/\/+/g,"/");
4541 // Check if path is forbidden
4542 if ((isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "config") || isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "certificates")) && !isProxy) {
4543 callServerError(403);
4544 serverconsole.errmessage("Access to configuration file/certificates is denied.");
4546 } else if (isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "temp") && !isProxy) {
4547 callServerError(403);
4548 serverconsole.errmessage("Access to temporary folder is denied.");
4550 } else if (isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "log") && !isProxy && (configJSON.enableLogging || configJSON.enableLogging == undefined) && !configJSON.enableRemoteLogBrowsing) {
4551 callServerError(403);
4552 serverconsole.errmessage("Access to log files is denied.");
4554 } else if (isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") && !isProxy && !exposeServerVersion) {
4555 callServerError(403);
4556 serverconsole.errmessage("Access to SVR.JS script is denied.");
4558 } else if ((isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") || isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "serverSideScripts") || isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "serverSideScriptDirectories")) && !isProxy && (configJSON.disableServerSideScriptExpose || configJSON.disableServerSideScriptExpose === undefined)) {
4559 callServerError(403);
4560 serverconsole.errmessage("Access to sources is denied.");
4563 var nonscodeIndex = -1;
4567 // Scan for non-standard codes
4568 if (!isProxy && nonStandardCodes != undefined) {
4569 for (var i = 0; i < nonStandardCodes.length; i++) {
4570 if (matchHostname(nonStandardCodes[i].host) && ipMatch(nonStandardCodes[i].ip, req.socket ? req.socket.localAddress : undefined)) {
4571 var isMatch = false;
4572 var hrefWithoutDuplicateSlashes = href.replace(/\/+/g,"/");
4573 if (nonStandardCodes[i].regex) {
4575 var createdRegex = createRegex(nonStandardCodes[i].regex, true);
4576 isMatch = req.url.match(createdRegex) || hrefWithoutDuplicateSlashes.match(createdRegex);
4577 regexI[i] = createdRegex;
4580 isMatch = nonStandardCodes[i].url == hrefWithoutDuplicateSlashes || (os.platform() == "win32" && nonStandardCodes[i].url.toLowerCase() == hrefWithoutDuplicateSlashes.toLowerCase());
4583 if (nonStandardCodes[i].scode == 401) {
4584 // HTTP authentication
4585 if (authIndex == -1) {
4589 if (nonscodeIndex == -1) {
4590 if ((nonStandardCodes[i].scode == 403 || nonStandardCodes[i].scode == 451) && nonStandardCodes[i].users !== undefined) {
4591 if (nonStandardCodes[i].users.check(reqip)) nonscodeIndex = i;
4602 // Handle non-standard codes
4603 if (nonscodeIndex > -1) {
4604 var nonscode = nonStandardCodes[nonscodeIndex];
4605 if (nonscode.scode == 301 || nonscode.scode == 302 || nonscode.scode == 307 || nonscode.scode == 308) {
4607 if (regexI[nonscodeIndex]) {
4608 location = req.url.replace(regexI[nonscodeIndex], nonscode.location);
4609 if(location == req.url) {
4610 // Fallback replacement
4611 location = hrefWithoutDuplicateSlashes.replace(regexI[nonscodeIndex], nonscode.location);
4613 } else if (req.url.split("?")[1] == undefined || req.url.split("?")[1] == null || req.url.split("?")[1] == "" || req.url.split("?")[1] == " ") {
4614 location = nonscode.location;
4616 location = nonscode.location + "?" + req.url.split("?")[1];
4618 redirect(location, nonscode.scode == 302 || nonscode.scode == 307, nonscode.scode == 307 || nonscode.scode == 308);
4621 callServerError(nonscode.scode);
4622 if (nonscode.scode == 403) {
4623 serverconsole.errmessage("Content blocked.");
4624 } else if (nonscode.scode == 410) {
4625 serverconsole.errmessage("Content is gone.");
4626 } else if (nonscode.scode == 418) {
4627 serverconsole.errmessage("SVR.JS is always a teapot ;)");
4629 serverconsole.errmessage("Client fails receiving content.");
4635 // Handle HTTP authentication
4636 if (authIndex > -1) {
4637 var authcode = nonStandardCodes[authIndex];
4639 // Function to check if passwords match
4640 function checkIfPasswordMatches(list, password, callback, _i) {
4642 var cb = function (hash) {
4643 if (hash == list[_i].pass) {
4645 } else if (_i >= list.length - 1) {
4648 checkIfPasswordMatches(list, password, callback, _i + 1);
4651 var hashedPassword = sha256(password + list[_i].salt);
4652 var cacheEntry = null;
4653 if (list[_i].scrypt) {
4654 if (!crypto.scrypt) {
4655 callServerError(500, new Error("SVR.JS doesn't support scrypt-hashed passwords on Node.JS versions without scrypt hash support."));
4658 cacheEntry = scryptCache.find(function (entry) {
4659 return (entry.password == hashedPassword && entry.salt == list[_i].salt);
4662 cb(cacheEntry.hash);
4664 crypto.scrypt(password, list[_i].salt, 64, function (err, derivedKey) {
4666 callServerError(500, err);
4668 var key = derivedKey.toString("hex");
4671 password: hashedPassword,
4672 salt: list[_i].salt,
4680 } else if (list[_i].pbkdf2) {
4681 if (crypto.__disabled__ !== undefined) {
4682 callServerError(500, new Error("SVR.JS doesn't support PBKDF2-hashed passwords on Node.JS versions without crypto support."));
4685 cacheEntry = pbkdf2Cache.find(function (entry) {
4686 return (entry.password == hashedPassword && entry.salt == list[_i].salt);
4689 cb(cacheEntry.hash);
4691 crypto.pbkdf2(password, list[_i].salt, 36250, 64, "sha512", function (err, derivedKey) {
4693 callServerError(500, err);
4695 var key = derivedKey.toString("hex");
4698 password: hashedPassword,
4699 salt: list[_i].salt,
4712 function authorizedCallback(bruteProtection) {
4714 var ha = getCustomHeaders();
4715 ha["WWW-Authenticate"] = "Basic realm=\"" + (authcode.realm ? authcode.realm.replace(/(\\|")/g, "\\$1") : "SVR.JS HTTP Basic Authorization") + "\", charset=\"UTF-8\"";
4716 var credentials = req.headers["authorization"];
4718 callServerError(401, ha);
4719 serverconsole.errmessage("Content needs authorization.");
4722 var credentialsMatch = credentials.match(/^Basic (.+)$/);
4723 if (!credentialsMatch) {
4724 callServerError(401, ha);
4725 serverconsole.errmessage("Malformed credentials.");
4728 var decodedCredentials = Buffer.from(credentialsMatch[1], "base64").toString("utf8");
4729 var decodedCredentialsMatch = decodedCredentials.match(/^([^:]*):(.*)$/);
4730 if (!decodedCredentialsMatch) {
4731 callServerError(401, ha);
4732 serverconsole.errmessage("Malformed credentials.");
4735 var username = decodedCredentialsMatch[1];
4736 var password = decodedCredentialsMatch[2];
4737 var usernameMatch = [];
4738 var sha256Count = 0;
4739 var pbkdf2Count = 0;
4740 var scryptCount = 0;
4741 if (!authcode.userList || authcode.userList.indexOf(username) > -1) {
4742 usernameMatch = users.filter(function (entry) {
4745 } else if(entry.pbkdf2) {
4750 return entry.name == username;
4753 if (usernameMatch.length == 0) {
4754 // Pushing false user match to prevent time-based user enumeration
4755 var fakeCredentials = {
4757 pass: "SVRJSAWebServerRunningOnNodeJS",
4758 salt: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0"
4760 if (!process.isBun) {
4761 if (scryptCount > sha256Count && scryptCount > pbkdf2Count) {
4762 fakeCredentials.scrypt = true;
4763 } else if (pbkdf2Count > sha256Count) {
4764 fakeCredentials.pbkdf2 = true;
4767 usernameMatch.push(fakeCredentials);
4769 checkIfPasswordMatches(usernameMatch, password, function (authorized) {
4772 if (bruteProtection) {
4774 process.send("\x12AUTHW" + reqip);
4776 if (!bruteForceDb[reqip]) bruteForceDb[reqip] = {
4779 bruteForceDb[reqip].invalidAttempts++;
4780 if (bruteForceDb[reqip].invalidAttempts >= 10) {
4781 bruteForceDb[reqip].lastAttemptDate = new Date();
4785 callServerError(401, ha);
4786 serverconsole.errmessage("User \"" + String(username).replace(/[\r\n]/g, "") + "\" failed to log in.");
4788 if (bruteProtection) {
4790 process.send("\x12AUTHR" + reqip);
4792 if (bruteForceDb[reqip]) bruteForceDb[reqip] = {
4797 serverconsole.reqmessage("Client is logged in as \"" + String(username).replace(/[\r\n]/g, "") + "\".");
4798 authUser = username;
4799 redirectTrailingSlashes(function () {
4800 modExecute(mods, vres);
4804 callServerError(500, err);
4809 callServerError(500, err);
4813 if (authcode.disableBruteProtection) {
4814 // Don't brute-force protect it, just do HTTP authentication
4815 authorizedCallback(false);
4816 } else if (!process.send) {
4817 // Query data from JS object database
4818 if (!bruteForceDb[reqip] || !bruteForceDb[reqip].lastAttemptDate || (new Date() - 300000 >= bruteForceDb[reqip].lastAttemptDate)) {
4819 if (bruteForceDb[reqip] && bruteForceDb[reqip].invalidAttempts >= 10) bruteForceDb[reqip] = {
4822 authorizedCallback(true);
4824 callServerError(429);
4825 serverconsole.errmessage("Brute force limit reached!");
4828 var listenerEmitted = false;
4830 // Listen for brute-force protection response
4831 function authMessageListener(message) {
4832 if (listenerEmitted) return;
4833 if (message == "\x14AUTHA" + reqip || message == "\x14AUTHD" + reqip) {
4834 process.removeListener("message", authMessageListener);
4835 listenerEmitted = true;
4837 if (message == "\x14AUTHD" + reqip) {
4838 callServerError(429);
4839 serverconsole.errmessage("Brute force limit reached!");
4840 } else if (message == "\x14AUTHA" + reqip) {
4841 authorizedCallback(true);
4844 process.on("message", authMessageListener);
4845 process.send("\x12AUTHQ" + reqip);
4848 redirectTrailingSlashes(function () {
4849 modExecute(mods, vres);
4856 callServerError(500, err);
4860 function serverErrorHandler(err, isRedirect) {
4861 if(isRedirect) attmtsRedir--;
4863 if (cluster.isPrimary === undefined && (isRedirect ? attmtsRedir : attmts)) {
4864 serverconsole.locerrmessage(serverErrorDescs[err.code] ? serverErrorDescs[err.code] : serverErrorDescs["UNKNOWN"]);
4865 serverconsole.locmessage((isRedirect ? attmtsRedir : attmts) + " attempts left.");
4868 process.send("\x12ERRLIST" + (isRedirect ? attmtsRedir : attmts) + err.code);
4870 // Probably main process exited
4873 if ((isRedirect ? attmtsRedir : attmts) > 0) {
4874 (isRedirect ? server2 : server).close();
4875 setTimeout(start, 900);
4878 if (cluster.isPrimary !== undefined) process.send("\x12ERRCRASH" + err.code);
4880 // Probably main process exited
4882 setTimeout(function () {
4883 var errno = errors[err.code];
4884 process.exit(errno ? errno : 1);
4889 server.on("error", function (err) {
4890 serverErrorHandler(err, false);
4892 server.on("listening", function () {
4898 var closedMaster = true;
4900 // IPC listener for server listening signalization
4901 function listenConnListener(msg) {
4902 if (msg == "\x12LISTEN") {
4907 // IPC listener for brue force protection
4908 function bruteForceListenerWrapper(worker) {
4909 return function bruteForceListener(message) {
4911 if (message.substring(0, 6) == "\x12AUTHQ") {
4912 ip = message.substring(6);
4913 if (!bruteForceDb[ip] || !bruteForceDb[ip].lastAttemptDate || (new Date() - 300000 >= bruteForceDb[ip].lastAttemptDate)) {
4914 if (bruteForceDb[ip] && bruteForceDb[ip].invalidAttempts >= 10) bruteForceDb[ip] = {
4917 worker.send("\x14AUTHA" + ip);
4919 worker.send("\x14AUTHD" + ip);
4921 } else if (message.substring(0, 6) == "\x12AUTHR") {
4922 ip = message.substring(6);
4923 if (bruteForceDb[ip]) bruteForceDb[ip] = {
4926 } else if (message.substring(0, 6) == "\x12AUTHW") {
4927 ip = message.substring(6);
4928 if (!bruteForceDb[ip]) bruteForceDb[ip] = {
4931 bruteForceDb[ip].invalidAttempts++;
4932 if (bruteForceDb[ip].invalidAttempts >= 10) {
4933 bruteForceDb[ip].lastAttemptDate = new Date();
4939 var isWorkerHungUpBuff = true;
4940 var isWorkerHungUpBuff2 = true;
4942 // General IPC message listener
4943 function msgListener(msg) {
4944 for (var i = 0; i < Object.keys(cluster.workers).length; i++) {
4945 if (msg == "\x12END") {
4946 cluster.workers[Object.keys(cluster.workers)[i]].removeAllListeners("message");
4947 cluster.workers[Object.keys(cluster.workers)[i]].on("message", bruteForceListenerWrapper(cluster.workers[Object.keys(cluster.workers)[i]]));
4948 cluster.workers[Object.keys(cluster.workers)[i]].on("message", listenConnListener);
4951 if (msg == "\x12CLOSE") {
4952 closedMaster = true;
4953 } else if (msg == "\x12LISTEN" || msg.substring(0, 4) == "\x12AUTH") {
4955 } else if (msg == "\x12KILLOK") {
4956 if (typeof isWorkerHungUpBuff != "undefined") isWorkerHungUpBuff = false;
4957 } else if (msg == "\x12PINGOK") {
4958 if (typeof isWorkerHungUpBuff2 != "undefined") isWorkerHungUpBuff2 = false;
4959 } else if (msg == "\x12KILLTERMMSG") {
4960 serverconsole.locmessage("Terminating unused worker process...");
4961 } else if (msg == "\x12SAVEGOOD") {
4962 serverconsole.locmessage("Configuration saved.");
4963 } else if (msg.indexOf("\x12SAVEERR") == 0) {
4964 serverconsole.locwarnmessage("There was a problem while saving configuration file. Reason: " + msg.substring(8));
4965 } else if (msg == "\x12END") {
4966 cluster.workers[Object.keys(cluster.workers)[0]].on("message", function (msg) {
4967 if (msg.length >= 8 && msg.indexOf("\x12ERRLIST") == 0) {
4968 var tries = parseInt(msg.substring(8, 9));
4969 var errCode = msg.substring(9);
4970 serverconsole.locerrmessage(serverErrorDescs[errCode] ? serverErrorDescs[errCode] : serverErrorDescs["UNKNOWN"]);
4971 serverconsole.locmessage(tries + " attempts left.");
4973 if (msg.length >= 9 && msg.indexOf("\x12ERRCRASH") == 0) {
4974 var errno = errors[msg.substring(9)];
4975 process.exit(errno ? errno : 1);
4979 serverconsole.climessage(msg);
4983 var messageTransmitted = false;
4985 function listeningMessage() {
4986 if (typeof closedMaster !== "undefined") closedMaster = false;
4987 if (!cluster.isPrimary && cluster.isPrimary !== undefined) {
4988 process.send("\x12LISTEN");
4991 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$/)));
4992 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}$/));
4993 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$/)));
4994 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}$/));
4996 var sAccHost = host;
4997 if (!listenToAny) accHost = listenAddress;
4998 if (!sListenToAny) sAccHost = sListenAddress;
4999 if (messageTransmitted) return;
5000 messageTransmitted = true;
5001 serverconsole.locmessage("Started server at: ");
5002 if (secure && (sListenToLocalhost || sListenToAny)) {
5003 if (typeof sport === "number") {
5004 serverconsole.locmessage("* https://localhost" + (sport == 443 ? "" : (":" + sport)));
5006 serverconsole.locmessage("* " + sport); // Unix socket or Windows named pipe
5009 if (!(secure && disableNonEncryptedServer) && (listenToLocalhost || listenToAny)) {
5010 if (typeof port === "number") {
5011 serverconsole.locmessage("* http://localhost" + (port == 80 ? "" : (":" + port)));
5013 serverconsole.locmessage("* " + port); // Unix socket or Windows named pipe
5016 if (secure && typeof sport === "number" && !sListenToLocalhost && (!sListenToAny || (host != "" && host != "[offline]"))) serverconsole.locmessage("* https://" + (sAccHost.indexOf(":") > -1 ? "[" + sAccHost + "]" : sAccHost) + (sport == 443 ? "" : (":" + sport)));
5017 if (!(secure && disableNonEncryptedServer) && !listenToLocalhost && (!listenToAny || (host != "" && host != "[offline]")) && typeof port === "number") serverconsole.locmessage("* http://" + (accHost.indexOf(":") > -1 ? "[" + accHost + "]" : accHost) + (port == 80 ? "" : (":" + port)));
5018 ipStatusCallback(function () {
5020 if (secure && !sListenToLocalhost) serverconsole.locmessage("* https://" + (pubip.indexOf(":") > -1 ? "[" + pubip + "]" : pubip) + (spubport == 443 ? "" : (":" + spubport)));
5021 if (!(secure && disableNonEncryptedServer) && !listenToLocalhost) serverconsole.locmessage("* http://" + (pubip.indexOf(":") > -1 ? "[" + pubip + "]" : pubip) + (pubport == 80 ? "" : (":" + pubport)));
5024 if (secure && !sListenToLocalhost) serverconsole.locmessage("* https://" + domain + (spubport == 443 ? "" : (":" + spubport)));
5025 if (!(secure && disableNonEncryptedServer) && !listenToLocalhost) serverconsole.locmessage("* http://" + domain + (pubport == 80 ? "" : (":" + pubport)));
5027 serverconsole.locmessage("For CLI help, you can type \"help\"");
5031 function start(init) {
5032 init = Boolean(init);
5033 if (cluster.isPrimary || cluster.isPrimary === undefined) {
5035 for (i = 0; i < logo.length; i++) console.log(logo[i]); // Print logo
5037 console.log("Welcome to \x1b[1mSVR.JS - a web server running on Node.JS\x1b[0m");
5040 if (version.indexOf("Nightly-") === 0) serverconsole.locwarnmessage("This version is only for test purposes and may be unstable.");
5041 if (configJSON.enableHTTP2 && !secure) serverconsole.locwarnmessage("HTTP/2 without HTTPS may not work in web browsers. Web browsers only support HTTP/2 with HTTPS!");
5042 if (process.isBun) {
5043 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.");
5044 if (users.some(function (entry) {
5045 return entry.pbkdf2;
5046 })) serverconsole.locwarnmessage("PBKDF2 password hashing function in Bun blocks the event loop, which may result in denial of service.");
5048 if (cluster.isPrimary === undefined) serverconsole.locwarnmessage("You're running SVR.JS on single thread. Reliability may suffer, as the server is stopped after crash.");
5049 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.");
5050 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.");
5051 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).");
5052 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).");
5053 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.");
5054 if (!process.isBun && secure && process.versions && process.versions.openssl && process.versions.openssl.substring(0, 2) == "1.") {
5055 if (new Date() > new Date("11 September 2023")) {
5056 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.");
5058 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.");
5061 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.");
5062 if (disableMods) serverconsole.locwarnmessage("SVR.JS is running without mods and server-side JavaScript enabled. Web applications may not work as expected");
5065 // Display mod and server-side JavaScript errors
5066 if (process.isPrimary || process.isPrimary === undefined) {
5067 modLoadingErrors.forEach(function (modLoadingError) {
5068 serverconsole.locwarnmessage("There was a problem while loading a \"" + String(modLoadingError.modName).replace(/[\r\n]/g, "") + "\" mod.");
5069 serverconsole.locwarnmessage("Stack:");
5070 serverconsole.locwarnmessage(generateErrorStack(modLoadingError.error));
5073 serverconsole.locwarnmessage("There was a problem while loading server-side JavaScript.");
5074 serverconsole.locwarnmessage("Stack:");
5075 serverconsole.locwarnmessage(generateErrorStack(SSJSError));
5077 if (SSJSError || modLoadingErrors.length > 0) console.log();
5080 // Print server information
5081 serverconsole.locmessage("Server version: " + version);
5082 if (process.isBun) serverconsole.locmessage("Bun version: v" + process.versions.bun);
5083 else serverconsole.locmessage("Node.JS version: " + process.version);
5084 var CPUs = os.cpus();
5085 if (CPUs.length > 0) serverconsole.locmessage("CPU: " + (CPUs.length > 1 ? CPUs.length + "x " : "") + CPUs[0].model);
5088 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.");
5089 if (configJSONRErr) throw new Error("Can't read SVR.JS configuration file: " + configJSONRErr.message);
5090 if (configJSONPErr) throw new Error("SVR.JS configuration parse error: " + configJSONPErr.message);
5091 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.");
5092 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");
5093 if (listenAddress) {
5094 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).");
5095 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.");
5096 if (brdIPs.indexOf(listenAddress) > -1) throw new Error("SVR.JS can't listen on broadcast address.");
5097 if (netIPs.indexOf(listenAddress) > -1) throw new Error("SVR.JS can't listen on subnet address.");
5099 if (certificateError) throw new Error("There was a problem with SSL certificate/private key: " + certificateError.message);
5100 if (wwwrootError) throw new Error("There was a problem with your web root: " + wwwrootError.message);
5101 if (sniReDos) throw new Error("Refusing to start, because the current SNI configuration would make the server vulnerable to ReDoS.");
5104 // Print server startup information
5105 if (!(secure && disableNonEncryptedServer)) serverconsole.locmessage("Starting HTTP server at " + (typeof port == "number" ? (listenAddress ? ((listenAddress.indexOf(":") > -1 ? "[" + listenAddress + "]" : listenAddress)) + ":" : "port ") : "") + port.toString() + "...");
5106 if (secure) serverconsole.locmessage("Starting HTTPS server at " + (typeof sport == "number" ? (sListenAddress ? ((sListenAddress.indexOf(":") > -1 ? "[" + sListenAddress + "]" : sListenAddress)) + ":" : "port ") : "") + sport.toString() + "...");
5110 if (!cluster.isPrimary) {
5112 if (typeof (secure ? sport : port) == "number" && (secure ? sListenAddress : listenAddress)) {
5113 server.listen(secure ? sport : port, secure ? sListenAddress : listenAddress);
5115 server.listen(secure ? sport : port);
5118 if (err.code != "ERR_SERVER_ALREADY_LISTEN") throw err;
5120 if (secure && !disableNonEncryptedServer) {
5122 if (typeof port == "number" && listenAddress) {
5123 server2.listen(port, listenAddress);
5125 server2.listen(port);
5128 if (err.code != "ERR_SERVER_ALREADY_LISTEN") throw err;
5135 close: function () {
5138 if (secure && !disableNonEncryptedServer) {
5141 if (cluster.isPrimary === undefined) serverconsole.climessage("Server closed.");
5143 process.send("Server closed.");
5144 process.send("\x12CLOSE");
5147 if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot close server! Reason: " + err.message);
5148 else process.send("Cannot close server! Reason: " + err.message);
5153 if (typeof (secure ? sport : port) == "number" && (secure ? sListenAddress : listenAddress)) {
5154 server.listen(secure ? sport : port, secure ? sListenAddress : listenAddress);
5156 server.listen(secure ? sport : port);
5158 if (secure && !disableNonEncryptedServer) {
5159 if (typeof port == "number" && listenAddress) {
5160 server2.listen(port, listenAddress);
5162 server2.listen(port);
5165 if (cluster.isPrimary === undefined) serverconsole.climessage("Server opened.");
5167 process.send("Server opened.");
5170 if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot open server! Reason: " + err.message);
5171 else process.send("Cannot open server! Reason: " + err.message);
5175 if (cluster.isPrimary === undefined) serverconsole.climessage("Server commands:\n" + Object.keys(commands).join(" "));
5176 else process.send("Server commands:\n" + Object.keys(commands).join(" "));
5179 if (cluster.isPrimary === undefined) serverconsole.climessage("Mods:");
5180 else process.send("Mods:");
5181 for (var i = 0; i < modInfos.length; i++) {
5182 if (cluster.isPrimary === undefined) serverconsole.climessage((i + 1).toString() + ". " + modInfos[i].name + " " + modInfos[i].version);
5183 else process.send((i + 1).toString() + ". " + modInfos[i].name + " " + modInfos[i].version);
5185 if (modInfos.length == 0) {
5186 if (cluster.isPrimary === undefined) serverconsole.climessage("No mods installed.");
5187 else process.send("No mods installed.");
5190 stop: function (retcode) {
5191 reallyExiting = true;
5192 clearInterval(passwordHashCacheIntervalId);
5193 if ((!cluster.isPrimary && cluster.isPrimary !== undefined) && server.listening) {
5195 server.close(function () {
5196 if (server2.listening) {
5198 server2.close(function () {
5199 if (!process.removeFakeIPC) {
5200 if (typeof retcode == "number") {
5201 process.exit(retcode);
5208 if (!process.removeFakeIPC) {
5209 if (typeof retcode == "number") {
5210 process.exit(retcode);
5217 if (!process.removeFakeIPC) {
5218 if (typeof retcode == "number") {
5219 process.exit(retcode);
5227 if (typeof retcode == "number") {
5228 process.exit(retcode);
5233 if (process.removeFakeIPC) process.removeFakeIPC();
5235 if (typeof retcode == "number") {
5236 process.exit(retcode);
5242 clear: function () {
5245 block: function (ip) {
5246 if (ip == undefined || JSON.stringify(ip) == "[]") {
5247 if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot block non-existent IP.");
5248 else if (!cluster.isPrimary) process.send("Cannot block non-existent IP.");
5250 for (var i = 0; i < ip.length; i++) {
5251 if (ip[i] != "localhost" && ip[i].indexOf(":") == -1) {
5252 ip[i] = "::ffff:" + ip[i];
5254 if (!blocklist.check(ip[i])) {
5255 blocklist.add(ip[i]);
5258 if (cluster.isPrimary === undefined) serverconsole.climessage("IPs successfully blocked.");
5259 else if (!cluster.isPrimary) process.send("IPs successfully blocked.");
5262 unblock: function (ip) {
5263 if (ip == undefined || JSON.stringify(ip) == "[]") {
5264 if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot unblock non-existent IP.");
5265 else if (!cluster.isPrimary) process.send("Cannot unblock non-existent IP.");
5267 for (var i = 0; i < ip.length; i++) {
5268 if (ip[i].indexOf(":") == -1) {
5269 ip[i] = "::ffff:" + ip[i];
5271 blocklist.remove(ip[i]);
5273 if (cluster.isPrimary === undefined) serverconsole.climessage("IPs successfully unblocked.");
5274 else if (!cluster.isPrimary) process.send("IPs successfully unblocked.");
5277 restart: function () {
5278 if (cluster.isPrimary === undefined) serverconsole.climessage("This command is not supported on single-threaded SVR.JS.");
5279 else process.send("This command need to be run in SVR.JS master.");
5285 if (cluster.isPrimary === undefined) {
5286 setInterval(function () {
5289 serverconsole.locmessage("Configuration saved.");
5291 throw new Error(err);
5294 } else if (cluster.isPrimary) {
5295 setInterval(function () {
5296 var allClusters = Object.keys(cluster.workers);
5297 var goodWorkers = [];
5299 function checkWorker(callback, _id) {
5300 if (typeof _id === "undefined") _id = 0;
5301 if (_id >= allClusters.length) {
5306 if (cluster.workers[allClusters[_id]]) {
5307 isWorkerHungUpBuff2 = true;
5308 cluster.workers[allClusters[_id]].on("message", msgListener);
5309 cluster.workers[allClusters[_id]].send("\x14PINGPING");
5310 setTimeout(function () {
5311 if (isWorkerHungUpBuff2) {
5312 checkWorker(callback, _id + 1);
5314 goodWorkers.push(allClusters[_id]);
5315 checkWorker(callback, _id + 1);
5319 checkWorker(callback, _id + 1);
5322 if (cluster.workers[allClusters[_id]]) {
5323 cluster.workers[allClusters[_id]].removeAllListeners("message");
5324 cluster.workers[allClusters[_id]].on("message", bruteForceListenerWrapper(cluster.workers[allClusters[_id]]));
5325 cluster.workers[allClusters[_id]].on("message", listenConnListener);
5327 checkWorker(callback, _id + 1);
5330 checkWorker(function () {
5331 var wN = Math.floor(Math.random() * goodWorkers.length); //Send a configuration saving message to a random worker.
5333 if (cluster.workers[goodWorkers[wN]]) {
5334 isWorkerHungUpBuff2 = true;
5335 cluster.workers[goodWorkers[wN]].on("message", msgListener);
5336 cluster.workers[goodWorkers[wN]].send("\x14SAVECONF");
5339 if (cluster.workers[goodWorkers[wN]]) {
5340 cluster.workers[goodWorkers[wN]].removeAllListeners("message");
5341 cluster.workers[goodWorkers[wN]].on("message", bruteForceListenerWrapper(cluster.workers[goodWorkers[wN]]));
5342 cluster.workers[goodWorkers[wN]].on("message", listenConnListener);
5344 serverconsole.locwarnmessage("There was a problem while saving configuration file. Reason: " + err.message);
5349 if (!cluster.isPrimary) {
5350 passwordHashCacheIntervalId = setInterval(function () {
5351 pbkdf2Cache = pbkdf2Cache.filter(function (entry) {
5352 return entry.addDate > (new Date() - 3600000);
5354 scryptCache = scryptCache.filter(function (entry) {
5355 return entry.addDate > (new Date() - 3600000);
5359 if (!cluster.isPrimary && cluster.isPrimary !== undefined) {
5360 process.on("message", function (line) {
5364 process.send("\x12END");
5365 } else if (line == "\x14SAVECONF") {
5366 // Save configuration file
5369 process.send("\x12SAVEGOOD");
5371 process.send("\x12SAVEERR" + err.message);
5373 process.send("\x12END");
5374 } else if (line == "\x14KILLPING") {
5375 if (!reallyExiting) {
5376 process.send("\x12KILLOK");
5377 process.send("\x12END");
5379 // Refuse to send, when it's really exiting. Main process will treat the worker as hung up anyway...
5380 } else if (line == "\x14PINGPING") {
5381 if (!reallyExiting) {
5382 process.send("\x12PINGOK");
5383 process.send("\x12END");
5385 // Refuse to send, when it's really exiting. Main process will treat the worker as hung up anyway...
5386 } else if (line == "\x14KILLREQ") {
5387 if (reqcounter - reqcounterKillReq < 2) {
5388 process.send("\x12KILLTERMMSG");
5389 process.nextTick(commands.stop);
5391 reqcounterKillReq = reqcounter;
5393 } else if (commands[line.split(" ")[0]] !== undefined && commands[line.split(" ")[0]] !== null) {
5394 var argss = line.split(" ");
5395 var command = argss.shift();
5396 commands[command](argss);
5397 process.send("\x12END");
5399 process.send("Unrecognized command \"" + line.split(" ")[0] + "\".");
5400 process.send("\x12END");
5404 process.send("Can't execute command \"" + line.split(" ")[0] + "\".");
5405 process.send("\x12END");
5410 var rla = readline.createInterface({
5411 input: process.stdin,
5412 output: process.stdout,
5416 rla.on("line", function (line) {
5418 var argss = line.split(" ");
5419 var command = argss.shift();
5421 if (cluster.isPrimary !== undefined) {
5422 var allClusters = Object.keys(cluster.workers);
5423 if (command == "block") commands.block(argss);
5424 if (command == "unblock") commands.unblock(argss);
5425 if (command == "restart") {
5426 var stopError = false;
5428 for (var i = 0; i < allClusters.length; i++) {
5430 if (cluster.workers[allClusters[i]]) {
5431 cluster.workers[allClusters[i]].kill();
5437 if (stopError) serverconsole.climessage("Some SVR.JS workers might not be stopped.");
5438 SVRJSInitialized = false;
5439 closedMaster = true;
5440 var cpus = os.availableParallelism ? os.availableParallelism() : os.cpus().length;
5441 if (cpus > 16) cpus = 16;
5443 var useAvailableCores = Math.round((os.freemem()) / 50000000) - 1; // 1 core deleted for safety...
5444 if (cpus > useAvailableCores) cpus = useAvailableCores;
5446 // Nevermind... Don't want SVR.JS to fail starting, because os.freemem function is not working.
5448 if (cpus < 1) cpus = 1; // If SVR.JS is running on Haiku or if useAvailableCores = 0
5449 for (var i = 0; i < cpus; i++) {
5453 setTimeout((function (i) {
5454 return function () {
5456 if (i >= cpus - 1) {
5457 SVRJSInitialized = true;
5459 serverconsole.climessage("SVR.JS workers restarted.");
5467 if (command == "stop") {
5469 allClusters = Object.keys(cluster.workers);
5471 allClusters.forEach(function (clusterID) {
5473 if (cluster.workers[clusterID]) {
5474 cluster.workers[clusterID].on("message", msgListener);
5475 cluster.workers[clusterID].send(line);
5478 if (cluster.workers[clusterID]) {
5479 cluster.workers[clusterID].removeAllListeners("message");
5480 cluster.workers[clusterID].on("message", bruteForceListenerWrapper(cluster.workers[clusterID]));
5481 cluster.workers[clusterID].on("message", listenConnListener);
5483 serverconsole.climessage("Can't run command \"" + command + "\".");
5486 if (command == "stop") {
5487 setTimeout(function () {
5488 reallyExiting = true;
5493 if (command == "stop") {
5494 reallyExiting = true;
5498 commands[command](argss);
5500 serverconsole.climessage("Unrecognized command \"" + command + "\".");
5508 if (cluster.isPrimary || cluster.isPrimary === undefined) {
5509 // Cluster forking code
5510 if (cluster.isPrimary !== undefined && init) {
5511 var cpus = os.availableParallelism ? os.availableParallelism() : os.cpus().length;
5512 if (cpus > 16) cpus = 16;
5514 var useAvailableCores = Math.round((os.freemem()) / 50000000) - 1; // 1 core deleted for safety...
5515 if (cpus > useAvailableCores) cpus = useAvailableCores;
5517 // Nevermind... Don't want SVR.JS to fail starting, because os.freemem function is not working.
5519 if (cpus < 1) cpus = 1; // If SVR.JS is run on Haiku (os.cpus in Haiku returns empty array) or if useAvailableCores = 0
5520 for (var i = 0; i < cpus; i++) {
5524 setTimeout((function (i) {
5525 return function () {
5527 if (i >= cpus - 1) SVRJSInitialized = true;
5532 cluster.workers[Object.keys(cluster.workers)[0]].on("message", function (msg) {
5533 if (msg.length >= 8 && msg.indexOf("\x12ERRLIST") == 0) {
5534 var tries = parseInt(msg.substring(8, 9));
5535 var errCode = msg.substring(9);
5536 serverconsole.locerrmessage(serverErrorDescs[errCode] ? serverErrorDescs[errCode] : serverErrorDescs["UNKNOWN"]);
5537 serverconsole.locmessage(tries + " attempts left.");
5539 if (msg.length >= 9 && msg.indexOf("\x12ERRCRASH") == 0) {
5540 var errno = errors[msg.substring(9)];
5541 process.exit(errno ? errno : 1);
5545 // Hangup check and restart
5546 setInterval(function () {
5547 if (!closedMaster && !exiting) {
5549 if (secure && disableNonEncryptedServer) {
5550 chksocket = https.get({
5551 hostname: (typeof sport == "number" && sListenAddress) ? sListenAddress : "localhost",
5552 port: (typeof sport == "number") ? sport : undefined,
5553 socketPath: (typeof sport == "number") ? undefined : sport,
5555 "X-SVR-JS-From-Main-Thread": "true",
5556 "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")
5559 rejectUnauthorized: false
5561 chksocket.removeAllListeners("timeout");
5563 res.on("data", function () {});
5564 res.on("end", function () {});
5566 }).on("error", function () {
5568 if (!crashed) SVRJSFork();
5569 else crashed = false;
5571 }).on("timeout", function () {
5572 if (!exiting) SVRJSFork();
5575 } else if ((configJSON.enableHTTP2 == undefined ? false : configJSON.enableHTTP2) && !secure) {
5576 // It doesn't support through Unix sockets or Windows named pipes
5577 var address = ((typeof port == "number" && listenAddress) ? listenAddress : "localhost").replace(/\/@/g, "");
5578 if (address.indexOf(":") > -1) {
5579 address = "[" + address + "]";
5581 var connection = http2.connect("http://" + address + ":" + port.toString());
5582 connection.on("error", function () {
5584 if (!crashed) SVRJSFork();
5585 else crashed = false;
5588 connection.setTimeout(1620, function () {
5589 if (!exiting) SVRJSFork();
5592 chksocket = connection.request({
5594 "x-svr-js-from-main-thread": "true",
5595 "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")
5597 chksocket.on("response", function () {
5601 chksocket.on("error", function () {
5603 if (!crashed) SVRJSFork();
5604 else crashed = false;
5608 chksocket = http.get({
5609 hostname: (typeof port == "number" && listenAddress) ? listenAddress : "localhost",
5610 port: (typeof port == "number") ? port : undefined,
5611 socketPath: (typeof port == "number") ? undefined : port,
5613 "X-SVR-JS-From-Main-Thread": "true",
5614 "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")
5618 chksocket.removeAllListeners("timeout");
5620 res.on("data", function () {});
5621 res.on("end", function () {});
5623 }).on("error", function () {
5625 if (!crashed) SVRJSFork();
5626 else crashed = false;
5628 }).on("timeout", function () {
5629 if (!exiting) SVRJSFork();
5636 // Termination of unused good workers
5637 if (!disableUnusedWorkerTermination && cluster.isPrimary !== undefined) {
5638 setTimeout(function () {
5639 setInterval(function () {
5640 if (!closedMaster && !exiting) {
5641 var allClusters = Object.keys(cluster.workers);
5642 var minClusters = 0;
5643 minClusters = Math.ceil(cpus * 0.625);
5644 if (minClusters < 2) minClusters = 2;
5645 var goodWorkers = [];
5647 function checkWorker(callback, _id) {
5648 if (typeof _id === "undefined") _id = 0;
5649 if (_id >= allClusters.length) {
5654 if (cluster.workers[allClusters[_id]]) {
5655 isWorkerHungUpBuff = true;
5656 cluster.workers[allClusters[_id]].on("message", msgListener);
5657 cluster.workers[allClusters[_id]].send("\x14KILLPING");
5658 setTimeout(function () {
5659 if (isWorkerHungUpBuff) {
5660 checkWorker(callback, _id + 1);
5662 goodWorkers.push(allClusters[_id]);
5663 checkWorker(callback, _id + 1);
5667 checkWorker(callback, _id + 1);
5670 if (cluster.workers[allClusters[_id]]) {
5671 cluster.workers[allClusters[_id]].removeAllListeners("message");
5672 cluster.workers[allClusters[_id]].on("message", bruteForceListenerWrapper(cluster.workers[allClusters[_id]]));
5673 cluster.workers[allClusters[_id]].on("message", listenConnListener);
5675 checkWorker(callback, _id + 1);
5678 checkWorker(function () {
5679 if (goodWorkers.length > minClusters) {
5680 var wN = Math.floor(Math.random() * goodWorkers.length);
5681 if (wN == goodWorkers.length) return;
5683 if (cluster.workers[goodWorkers[wN]]) {
5684 isWorkerHungUpBuff = true;
5685 cluster.workers[goodWorkers[wN]].on("message", msgListener);
5686 cluster.workers[goodWorkers[wN]].send("\x14KILLREQ");
5689 if (cluster.workers[goodWorkers[wN]]) {
5690 cluster.workers[goodWorkers[wN]].removeAllListeners("message");
5691 cluster.workers[goodWorkers[wN]].on("message", bruteForceListenerWrapper(cluster.workers[goodWorkers[wN]]));
5692 cluster.workers[goodWorkers[wN]].on("message", listenConnListener);
5694 serverconsole.locwarnmessage("There was a problem while terminating unused worker process. Reason: " + err.message);
5707 // Save configuration file
5708 function saveConfig() {
5709 for (var i = 0; i < 3; i++) {
5711 var configJSONobj = {};
5712 if (fs.existsSync(__dirname + "/config.json")) configJSONobj = JSON.parse(fs.readFileSync(__dirname + "/config.json").toString());
5713 if (configJSONobj.users === undefined) configJSONobj.users = [];
5715 if (configJSONobj.key === undefined) configJSONobj.key = "cert/key.key";
5716 if (configJSONobj.cert === undefined) configJSONobj.cert = "cert/cert.crt";
5717 if (configJSONobj.sport === undefined) configJSONobj.sport = 443;
5718 if (configJSONobj.spubport === undefined) configJSONobj.spubport = 443;
5719 if (configJSONobj.sni === undefined) configJSONobj.sni = {};
5720 if (configJSONobj.enableOCSPStapling === undefined) configJSONobj.enableOCSPStapling = false;
5722 if (configJSONobj.port === undefined) configJSONobj.port = 80;
5723 if (configJSONobj.pubport === undefined) configJSONobj.pubport = 80;
5724 if (configJSONobj.domain === undefined && configJSONobj.domian !== undefined) configJSONobj.domain = configJSONobj.domian;
5725 delete configJSONobj.domian;
5726 if (configJSONobj.page404 === undefined) configJSONobj.page404 = "404.html";
5727 configJSONobj.timestamp = timestamp;
5728 configJSONobj.blacklist = blocklist.raw;
5729 if (configJSONobj.nonStandardCodes === undefined) configJSONobj.nonStandardCodes = [];
5730 if (configJSONobj.enableCompression === undefined) configJSONobj.enableCompression = true;
5731 if (configJSONobj.customHeaders === undefined) configJSONobj.customHeaders = {};
5732 if (configJSONobj.enableHTTP2 === undefined) configJSONobj.enableHTTP2 = false;
5733 if (configJSONobj.enableLogging === undefined) configJSONobj.enableLogging = true;
5734 if (configJSONobj.enableDirectoryListing === undefined) configJSONobj.enableDirectoryListing = true;
5735 if (configJSONobj.enableDirectoryListingWithDefaultHead === undefined) configJSONobj.enableDirectoryListingWithDefaultHead = false;
5736 if (configJSONobj.serverAdministratorEmail === undefined) configJSONobj.serverAdministratorEmail = "[no contact information]";
5737 if (configJSONobj.stackHidden === undefined) configJSONobj.stackHidden = false;
5738 if (configJSONobj.enableRemoteLogBrowsing === undefined) configJSONobj.enableRemoteLogBrowsing = false;
5739 if (configJSONobj.exposeServerVersion === undefined) configJSONobj.exposeServerVersion = true;
5740 if (configJSONobj.disableServerSideScriptExpose === undefined) configJSONobj.disableServerSideScriptExpose = true;
5741 if (configJSONobj.allowStatus === undefined) configJSONobj.allowStatus = true;
5742 if (configJSONobj.rewriteMap === undefined) configJSONobj.rewriteMap = [];
5743 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])$/"];
5744 if (configJSONobj.enableIPSpoofing === undefined) configJSONobj.enableIPSpoofing = false;
5745 if (configJSONobj.secure === undefined) configJSONobj.secure = false;
5746 if (configJSONobj.disableNonEncryptedServer === undefined) configJSONobj.disableNonEncryptedServer = false;
5747 if (configJSONobj.disableToHTTPSRedirect === undefined) configJSONobj.disableToHTTPSRedirect = false;
5748 if (configJSONobj.enableETag === undefined) configJSONobj.enableETag = true;
5749 if (configJSONobj.disableUnusedWorkerTermination === undefined) configJSONobj.disableUnusedWorkerTermination = false;
5750 if (configJSONobj.rewriteDirtyURLs === undefined) configJSONobj.rewriteDirtyURLs = false;
5751 if (configJSONobj.errorPages === undefined) configJSONobj.errorPages = [];
5752 if (configJSONobj.useWebRootServerSideScript === undefined) configJSONobj.useWebRootServerSideScript = true;
5753 if (configJSONobj.exposeModsInErrorPages === undefined) configJSONobj.exposeModsInErrorPages = true;
5754 if (configJSONobj.disableTrailingSlashRedirects === undefined) configJSONobj.disableTrailingSlashRedirects = false;
5755 if (configJSONobj.environmentVariables === undefined) configJSONobj.environmentVariables = {};
5756 if (configJSONobj.allowDoubleSlashes === undefined) configJSONobj.allowDoubleSlashes = false;
5758 var configString = JSON.stringify(configJSONobj, null, 2) + "\n";
5759 fs.writeFileSync(__dirname + "/config.json", configString);
5762 if (i >= 2) throw err;
5763 var now = Date.now();
5764 while (Date.now() - now < 2);
5769 // Process event listeners
5770 if (cluster.isPrimary || cluster.isPrimary === undefined) {
5771 process.on("uncaughtException", function (err) {
5773 serverconsole.locerrmessage("SVR.JS master process just crashed!!!");
5774 serverconsole.locerrmessage("Stack:");
5775 serverconsole.locerrmessage(generateErrorStack(err));
5776 process.exit(err.errno);
5778 process.on("unhandledRejection", function (err) {
5780 serverconsole.locerrmessage("SVR.JS master process just crashed!!!");
5781 serverconsole.locerrmessage("Stack:");
5782 serverconsole.locerrmessage(err.stack ? generateErrorStack(err) : String(err));
5783 process.exit(err.errno);
5785 process.on("exit", function (code) {
5787 if (!configJSONRErr && !configJSONPErr) {
5791 serverconsole.locwarnmessage("There was a problem while saving configuration file. Reason: " + err.message);
5794 deleteFolderRecursive(__dirname + "/temp");
5799 fs.mkdirSync(__dirname + "/temp");
5803 if (process.isBun && process.versions.bun && process.versions.bun[0] == "0") {
5805 fs.writeFileSync(__dirname + "/temp/serverSideScript.js", "// Placeholder server-side JavaScript to workaround Bun bug.\r\n");
5810 serverconsole.locmessage("Server closed with exit code: " + code);
5812 process.on("warning", function (warning) {
5813 serverconsole.locwarnmessage(warning.message);
5814 if (generateErrorStack(warning)) {
5815 serverconsole.locwarnmessage("Stack:");
5816 serverconsole.locwarnmessage(generateErrorStack(warning));
5819 process.on("SIGINT", function () {
5820 reallyExiting = true;
5821 if (cluster.isPrimary !== undefined) {
5823 var allClusters = Object.keys(cluster.workers);
5824 for (var i = 0; i < allClusters.length; i++) {
5826 if (cluster.workers[allClusters[i]]) {
5827 cluster.workers[allClusters[i]].send("stop");
5830 // Worker will crash with EPIPE anyway.
5834 serverconsole.locmessage("Server terminated using SIGINT");
5839 function crashHandler(err) {
5840 serverconsole.locerrmessage("SVR.JS worker just crashed!!!");
5841 serverconsole.locerrmessage("Stack:");
5842 serverconsole.locerrmessage(err.stack ? generateErrorStack(err) : String(err));
5843 process.exit(err.errno);
5846 process.on("uncaughtException", crashHandler);
5847 process.on("unhandledRejection", crashHandler);
5850 process.on("warning", function (warning) {
5851 serverconsole.locwarnmessage(warning.message);
5852 if (warning.stack) {
5853 serverconsole.locwarnmessage("Stack:");
5854 serverconsole.locwarnmessage(generateErrorStack(warning));
5863 serverconsole.locerrmessage("There was a problem starting SVR.JS!!!");
5864 serverconsole.locerrmessage("Stack:");
5865 serverconsole.locerrmessage(generateErrorStack(err));
5866 setTimeout(function () {
5867 process.exit(err.errno ? err.errno : 1);