Update the design of default HTML pages
[svrjs.git] / svr.js
blobc49525a7297206665b37b8e4edbf25a9ee645227
1 // SVR.JS - a web server running on Node.JS
3 /*
4  * MIT License
5  *
6  * Copyright (c) 2018-2024 SVR.JS
7  *
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:
9  *
10  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11  *
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.
13  */
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);
21     WScript.quit();
22   } else {
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).");
26     }
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).");
30     } else {
31       throw new Error("SVR.JS doesn't work on Deno/QuickJS. SVR.JS requires use of Node.JS (or compatible JS runtime).");
32     }
33   }
36 var secure = false;
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");
53   console.log("Done!");
54   process.exit(0);
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);
65       }
66     });
67     fs.rmdirSync(path);
68   }
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");
89     process.exit(0);
90   } else if (args[i] == "--secure") {
91     secure = true;
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)) + ")");
94     process.exit(0);
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!");
103     process.exit(0);
104   } else if (args[i] == "--reset") {
105     factoryReset();
106   } else if (args[i] == "--disable-mods") {
107     disableMods = true;
108   } else if (args[i] == "--single-threaded") {
109     singlethreaded = true;
110   } else {
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");
121     process.exit(1);
122   }
125 var readline = require("readline");
126 var net = require("net");
127 var cluster = {};
128 if (!singlethreaded) {
129   try {
130     // Import cluster module
131     var cluster = require("cluster");
132   } catch (err) {
133     // Clustering is not supported!
134   }
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
146       cluster.worker = {
147         id: parseInt(process.env.NODE_UNIQUE_ID),
148         process: process,
149         isDead: function () {
150           return false;
151         },
152         send: function (message, b, c, d) {
153           process.send(message, b, c, d);
154         }
155       };
157       if (!process.send) {
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();
169           });
171           socket.on("end", function () {
172             process.emit("message", receivedData);
173           });
174         });
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);
181           });
182         };
184         process.removeFakeIPC = function () {
185           // Close IPC server
186           process.send = function () {};
187           fakeIPCServer.close();
188         };
189       }
190     }
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, {
202         env: newEnvironment,
203         stdio: ["inherit", "inherit", "inherit", "ipc"]
204       });
206       newWorker.process = newWorker;
207       newWorker.isDead = function () {
208         return newWorker.exitCode !== null || newWorker.killed;
209       };
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")) {
216           if (!worker.send) {
217             sendImplemented = false;
218           }
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");
224             } else {
225               oldLog(a, b, c, d, e, f);
226             }
227           };
229           try {
230             worker.send(undefined);
231           } catch (err) {
232             if (err.message === "NOT IMPLEMENTED") {
233               sendImplemented = false;
234             }
235             console.log(err);
236           }
238           console.log = oldLog;
239         }
241         return sendImplemented;
242       }
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();
254           });
256           socket.on("end", function () {
257             newWorker.emit("message", receivedData);
258           });
259         });
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];
266         });
268         newWorker.send = function (message, fakeParam2, fakeParam3, fakeParam4, tries) {
269           if (!tries) tries = 0;
271           try {
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);
275             });
276           } catch (err) {
277             if (tries > 50) throw err;
278             newWorker.send(message, fakeParam2, fakeParam3, fakeParam4, tries + 1);
279           }
280         };
281       } else {
282         newWorker.on("exit", function () {
283           delete cluster.workers[newWorker.id];
284         });
285       }
287       cluster.workers[newWorker.id] = newWorker;
288       cluster._workersCounter++;
289       return newWorker;
290     };
291   };
293   if (process.isBun && (cluster.isMaster === undefined || (cluster.isMaster && process.env.NODE_UNIQUE_ID))) {
294     cluster.bunShim();
295   }
297   // Shim cluster.isPrimary field
298   if (cluster.isPrimary === undefined && cluster.isMaster !== undefined) cluster.isPrimary = cluster.isMaster;
301 // ETag-related
302 var ETagDB = {};
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;
319 var exiting = false;
320 var reallyExiting = false;
321 var crashed = false;
322 var threadLimitWarned = false;
324 // SVR.JS worker forking function
325 function SVRJSFork() {
326   // Log
327   if (SVRJSInitialized) serverconsole.locmessage("Starting next thread, because previous one hung up/crashed...");
328   // Fork new worker
329   var newWorker = {};
330   try {
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.");
334     }
335     if (!(cluster.__shimmed__ && process.isBun && process.versions.bun && process.versions.bun[0] != "0" && Object.keys(cluster.workers) > 0)) {
336       newWorker = cluster.fork();
337     } else {
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.");
339     }
340   } catch (err) {
341     if (err.name == "NotImplementedError") {
342       // If cluster.fork throws a NotImplementedError, shim cluster module
343       cluster.bunShim();
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.");
347       }
348       if (!(cluster.__shimmed__ && process.isBun && process.versions.bun && process.versions.bun[0] != "0" && Object.keys(cluster.workers) > 0)) {
349         newWorker = cluster.fork();
350       } else {
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.");
352       }
353     } else {
354       throw err;
355     }
356   }
358   // Add event listeners
359   if (newWorker.on) {
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);
362     });
363     newWorker.on("exit", function () {
364       if (!exiting && Object.keys(cluster.workers).length == 0) {
365         crashed = true;
366         SVRJSFork();
367       }
368     });
369     newWorker.on("message", bruteForceListenerWrapper(newWorker));
370     newWorker.on("message", listenConnListener);
371   }
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");
380 try {
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!
383 } catch (err) {
384   // Don't use graceful-fs
386 var path = require("path");
387 var hexstrbase64 = undefined;
388 try {
389   hexstrbase64 = require("./hexstrbase64/index.js");
390 } catch (err) {
391   // Don't use hexstrbase64
393 var inspector = undefined;
394 try {
395   inspector = require("inspector");
396 } catch (err) {
397   // Don't use inspector
399 var zlib = require("zlib");
400 var tar = undefined;
401 try {
402   tar = require("tar");
403 } catch (err) {
404   tar = {
405     _errored: err
406   };
408 var formidable = undefined;
409 try {
410   formidable = require("formidable");
411 } catch (err) {
412   formidable = {
413     _errored: err
414   };
416 var ocsp = undefined;
417 var ocspCache = undefined;
418 try {
419   ocsp = require("ocsp");
420   ocspCache = new ocsp.Cache();
421 } catch (err) {
422   ocsp = {
423     _errored: err
424   };
426 var http2 = {};
427 try {
428   http2 = require("http2");
429   if (process.isBun) {
430     try {
431       http2.Http2ServerRequest();
432     } catch (err) {
433       if (err.name == "NotImplementedError" || err.code == "ERR_NOT_IMPLEMENTED") throw err;
434     }
435   }
436 } catch (err) {
437   http2.__disabled__ = null;
438   http2.createServer = function () {
439     throw new Error("HTTP/2 support is not present");
440   };
441   http2.createSecureServer = function () {
442     throw new Error("HTTP/2 support is not present");
443   };
444   http2.connect = function () {
445     throw new Error("HTTP/2 support is not present");
446   };
447   http2.get = function () {
448     throw new Error("HTTP/2 support is not present");
449   };
451 var crypto = {};
452 var https = {};
453 try {
454   crypto = require("crypto");
455   https = require("https");
456 } catch (err) {
457   crypto = {
458     __disabled__: null
459   };
460   https = {
461     createServer: function () {
462       throw new Error("Crypto support is not present");
463     },
464     connect: function () {
465       throw new Error("Crypto support is not present");
466     },
467     get: function () {
468       throw new Error("Crypto support is not present");
469     }
470   };
471   http2.createSecureServer = function () {
472     throw new Error("Crypto support is not present");
473   };
475 var mime = require("mime-types");
476 var pubip = "";
477 var listenAddress = undefined;
478 var sListenAddress = undefined;
479 var pubport = 80;
480 var port = 80;
481 var domain = "";
482 var spubport = 443;
483 var sport = 443;
484 var mods = [];
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();
491 var modInfos = [];
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" : "");
505 function getOS() {
506   var osType = os.type();
507   var platform = os.platform();
508   if (platform == "android") {
509     return "Android";
510   } else if (osType == "Windows_NT" || osType == "WindowsNT") {
511     var arch = os.arch();
512     if (arch == "ia32") {
513       return "Win32";
514     } else if (arch == "x64") {
515       return "Win64";
516     } else {
517       return "Win" + arch.toUpperCase();
518     }
519   } else if (osType.indexOf("CYGWIN") == 0) {
520     return "Cygwin";
521   } else if (osType.indexOf("MINGW") == 0) {
522     return "MinGW";
523   } else if (osType.indexOf("MSYS") == 0) {
524     return "MSYS";
525   } else if (osType.indexOf("UWIN") == 0) {
526     return "UWIN";
527   } else if (osType == "GNU") {
528     return "GNU Hurd";
529   } else {
530     return osType;
531   }
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, "");
551   }
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;
560     var ipv4 = "";
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 ? ":" : "");
568       }
569       address = address.replace(extractIpv4, ipv4);
570     }
572     if (address.indexOf("::") == -1) {
573       fullAddress = address;
574     } else {
575       var sides = address.split("::");
576       var groupsPresent = 0;
577       sides.forEach(function (side) {
578         groupsPresent += side.split(":").length;
579       });
580       fullAddress += sides[0] + ":";
581       if (validGroupCount - groupsPresent > 1) {
582         fullAddress += "0000:".repeat(validGroupCount - groupsPresent);
583       }
584       fullAddress += sides[1];
585     }
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];
590       }
591       expandedAddress += (i != validGroupCount - 1) ? groups[i] + ":" : groups[i];
592     }
593     return expandedAddress;
594   }
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);
602   } else {
603     IP1 = normalizeIPv4Address(IP1);
604   }
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);
611   } else {
612     IP2 = normalizeIPv4Address(IP2);
613   }
615   // Check if processed IPs are equal
616   if (IP1 == IP2) return true;
617   else return false;
620 function checkForEnabledDirectoryListing(hostname, localAddress) {
621   function matchHostname(hostnameM) {
622     if (typeof hostnameM == "undefined" || hostnameM == "*") {
623       return true;
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)) {
627         return true;
628       }
629     } else if (hostname && hostname == hostnameM) {
630       return true;
631     }
632     return false;
633   }
635   var main = (configJSON.enableDirectoryListing || configJSON.enableDirectoryListing === undefined);
636   if (!configJSON.enableDirectoryListingVHost) return main;
637   var vhostP = null;
638   configJSON.enableDirectoryListingVHost.every(function (vhost) {
639     if (matchHostname(vhost.host) && ipMatch(vhost.ip, localAddress)) {
640       vhostP = vhost;
641       return false;
642     } else {
643       return true;
644     }
645   });
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 = [];
655   var instance = {
656     raw: [],
657     rawtoPreparedMap: [],
658     prepared: [],
659     cidrs: []
660   };
662   // Function to normalize IPv4 address (remove leading zeros)
663   function normalizeIPv4Address(address) {
664     return address.replace(/(^|\.)(?:0(?!\.|$))+/g, "");
665   }
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;
674     var ipv4 = "";
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 ? ":" : "");
682       }
683       address = address.replace(extractIpv4, ipv4);
684     }
686     if (address.indexOf("::") == -1) {
687       fullAddress = address;
688     } else {
689       var sides = address.split("::");
690       var groupsPresent = 0;
691       sides.forEach(function (side) {
692         groupsPresent += side.split(":").length;
693       });
694       fullAddress += sides[0] + ":";
695       if (validGroupCount - groupsPresent > 1) {
696         fullAddress += "0000:".repeat(validGroupCount - groupsPresent);
697       }
698       fullAddress += sides[1];
699     }
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];
704       }
705       expandedAddress += (i != validGroupCount - 1) ? groups[i] + ":" : groups[i];
706     }
707     return expandedAddress;
708   }
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]);
714   }
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;
722     return {
723       min: ipMin,
724       max: ipMax
725     };
726   }
728   // Convert IPv6 address to an array of blocks
729   function ipv6ToBlocks(ip) {
730     var ips = ip.split(":");
731     var ip2s = [];
732     ips.forEach(function (ipe) {
733       ip2s.push(parseInt(ipe));
734     });
735     return ip2s;
736   }
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;
743     var ipBlockMin = [];
744     var ipBlockMax = [];
745     for (var i = 0; i < 8; i++) {
746       ipBlockMin.push((i < 8 - fieldsToDelete) ? ((i < 7 - fieldsToDelete) ? ipBlocks[i] : (ipBlocks[i] >> fieldMaskModify << fieldMaskModify)) : 0);
747     }
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);
750     }
751     return {
752       min: ipBlockMin,
753       max: ipBlockMax
754     };
755   }
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;
761   }
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;
768     }
769     return false;
770   }
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;
780     var cidrMask = null;
781     var isIPv6 = false;
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("/");
788     }
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) {
794       isIPv6 = true;
795       rawValue = expandIPv6Address(rawValue);
796     } else {
797       rawValue = normalizeIPv4Address(rawValue);
798     }
800     // Add the IP or CIDR block to the appropriate list
801     if (cidrMask) {
802       var cidrLimits = {};
803       if (isIPv6) {
804         cidrLimits = getIPv6CIDRLimits(rawValue, cidrMask);
805         cidrLimits.v6 = true;
806       } else {
807         cidrLimits = getIPv4CIDRLimits(rawValue, cidrMask);
808         cidrLimits.v6 = false;
809       }
810       instance.cidrs.push(cidrLimits);
811       instance.rawtoPreparedMap.push({
812         cidr: true,
813         index: cidrIndex
814       });
815     } else {
816       instance.prepared.push(rawValue);
817       instance.rawtoPreparedMap.push({
818         cidr: false,
819         index: beginIndex
820       });
821     }
822   };
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);
831     if (map.cidr) {
832       instance.cidrs.splice(map.index, 1);
833     } else {
834       instance.prepared.splice(map.index, 1);
835     }
836     return true;
837   };
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;
842     var isIPv6 = 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) {
849       isIPv6 = true;
850       rawValue = expandIPv6Address(rawValue);
851     } else {
852       rawValue = normalizeIPv4Address(rawValue);
853     }
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);
865     });
866   };
868   // Add initial raw block list values to the instance
869   rawBlockList.forEach(function (rbe) {
870     instance.add(rbe);
871   });
873   return instance;
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);
884   })) {
885     return errorObject.stack;
886   }
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("@");
896       var location = "";
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 + ")"))));
902     }
903   });
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)
916   var ip = ipCA[0];
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();
923   }).join(".");
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)
933   var ip = ipCA[0];
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();
940   }).join(".");
943 var inspectorURL = undefined;
944 try {
945   if (inspector) {
946     inspectorURL = inspector.url();
947   }
948 } catch (err) {
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
961 var ifaces = {};
962 var ifaceEx = null;
963 try {
964   ifaces = os.networkInterfaces();
965 } catch (err) {
966   ifaceEx = err;
968 var ips = [];
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) {
973   var alias = 0;
974   ifaces[ifname].forEach(function (iface) {
975     if (iface.family !== "IPv4" || iface.internal !== false) {
976       return;
977     }
978     if (alias >= 1) {
979       ips.push(ifname + ":" + alias, iface.address);
980     } else {
981       ips.push(ifname, iface.address);
982     }
983     brdIPs.push(calculateBroadcastIPv4FromCidr(iface.cidr));
984     netIPs.push(calculateNetworkIPv4FromCidr(iface.cidr));
985     alias++;
986   });
989 if (ips.length == 0) {
990   Object.keys(ifaces).forEach(function (ifname) {
991     var alias = 0;
992     ifaces[ifname].forEach(function (iface) {
993       if (iface.family !== "IPv6" || iface.internal !== false) {
994         return;
995       }
996       if (alias >= 1) {
997         ips.push(ifname + ":" + alias, iface.address);
998       } else {
999         ips.push(ifname, iface.address);
1000       }
1001       alias++;
1002     });
1003   });
1006 // Server startup attempt counter
1007 var attmts = 5;
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),
1025     path: "/",
1026     headers: {
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")
1028     },
1029     timeout: 5000
1030   }, function (res) {
1031     ipRequest.removeAllListeners("timeout");
1032     res.on("data", function (d) {
1033       if (res.statusCode != 200) {
1034         ipRequestCompleted = true;
1035         process.emit("ipRequestCompleted");
1036         return;
1037       }
1038       pubip = d.toString();
1039       if (domain) {
1040         ipRequestCompleted = true;
1041         process.emit("ipRequestCompleted");
1042       } else {
1043         var callbackDone = false;
1045         var dnsTimeout = setTimeout(function () {
1046           callbackDone = true;
1047           ipRequestCompleted = true;
1048           process.emit("ipRequestCompleted");
1049         }, 3000);
1051         try {
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");
1058           });
1059         } catch (err) {
1060           clearTimeout(dnsTimeout);
1061           callbackDone = true;
1062           ipRequestCompleted = true;
1063           process.emit("ipRequestCompleted");
1064         }
1065       }
1066     });
1067   });
1068   ipRequest.on("error", function () {
1069     if (crypto.__disabled__ || ipRequestGotError) {
1070       ipRequestCompleted = true;
1071       process.emit("ipRequestCompleted");
1072     } else {
1073       ipRequestGotError = true;
1074     }
1075   });
1076   ipRequest.on("timeout", function () {
1077     if (crypto.__disabled__ || ipRequestGotError) {
1078       ipRequestCompleted = true;
1079       process.emit("ipRequestCompleted");
1080     } else {
1081       ipRequestGotError = true;
1082     }
1083   });
1085   if (!crypto.__disabled) {
1086     var ipRequest2 = https.get({
1087       host: "api.seeip.org",
1088       port: 443,
1089       path: "/",
1090       headers: {
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")
1092       },
1093       timeout: 5000
1094     }, function (res) {
1095       ipRequest2.removeAllListeners("timeout");
1096       res.on("data", function (d) {
1097         if (res.statusCode != 200) {
1098           ipRequestCompleted = true;
1099           process.emit("ipRequestCompleted");
1100           return;
1101         }
1102         pubip = d.toString();
1103         if (domain) {
1104           ipRequestCompleted = true;
1105           process.emit("ipRequestCompleted");
1106         } else {
1107           var callbackDone = false;
1109           var dnsTimeout = setTimeout(function () {
1110             callbackDone = true;
1111             ipRequestCompleted = true;
1112             process.emit("ipRequestCompleted");
1113           }, 3000);
1115           try {
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");
1122             });
1123           } catch (err) {
1124             clearTimeout(dnsTimeout);
1125             callbackDone = true;
1126             ipRequestCompleted = true;
1127             process.emit("ipRequestCompleted");
1128           }
1129         }
1130       });
1131     });
1132     ipRequest2.on("error", function () {
1133       if (crypto.__disabled__ || ipRequestGotError) {
1134         ipRequestCompleted = true;
1135         process.emit("ipRequestCompleted");
1136       } else {
1137         ipRequestGotError = true;
1138       }
1139     });
1140     ipRequest2.on("timeout", function () {
1141       if (crypto.__disabled__ || ipRequestGotError) {
1142         ipRequestCompleted = true;
1143         process.emit("ipRequestCompleted");
1144       } else {
1145         ipRequestGotError = true;
1146       }
1147     });
1148   }
1149 } else {
1150   ipRequestCompleted = true;
1153 function ipStatusCallback(callback) {
1154   if (ipRequestCompleted) {
1155     callback();
1156   } else {
1157     process.once("ipRequestCompleted", callback);
1158   }
1161 var configJSON = {};
1162 var configJSONRErr = undefined;
1163 var configJSONPErr = undefined;
1164 if (fs.existsSync(__dirname + "/config.json")) {
1165   var configJSONf = "";
1166   try {
1167     configJSONf = fs.readFileSync(__dirname + "/config.json"); // Read JSON File
1168     try {
1169       configJSON = JSON.parse(configJSONf); // Parse JSON
1170     } catch (err2) {
1171       configJSONPErr = err2;
1172     }
1173   } catch (err) {
1174     configJSONRErr = err2;
1175   }
1178 // Default server configuration properties
1179 var wwwredirect = false;
1180 var rawBlockList = [];
1181 var users = [];
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;
1190 var sni = {};
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);
1214   } else {
1215     var portLMatch = port.match(/^(\[[^ \]@\/\\]+\]|[^ \]\[:@\/\\]+):([0-9]+)$/);
1216     if (portLMatch) {
1217       listenAddress = portLMatch[1].replace(/^\[|\]$/g, "").replace(/^::ffff:/i, "");
1218       port = parseInt(portLMatch[2]);
1219     }
1220   }
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);
1228   } else {
1229     var sportLMatch = sport.match(/^(\[[^ \]@\/\\]+\]|[^ \]\[:@\/\\]+):([0-9]+)$/);
1230     if (sportLMatch) {
1231       sListenAddress = sportLMatch[1].replace(/^\[|\]$/g, "").replace(/^::ffff:/i, "");
1232       sport = parseInt(sportLMatch[2]);
1233     }
1234   }
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;
1264 try {
1265   if (cluster.isPrimary || cluster.isPrimary === undefined) process.chdir(configJSON.wwwroot != undefined ? configJSON.wwwroot : __dirname);
1266 } catch (err) {
1267   wwwrootError = err;
1270 try {
1271   Object.keys(environmentVariables).forEach(function (key) {
1272     process.env[key] = environmentVariables[key];
1273   });
1274 } catch (err) {
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) {
1286   var newObject = {};
1287   Object.keys(nonStandardCodeRaw).forEach(function (nsKey) {
1288     if (nsKey != "users") {
1289       newObject[nsKey] = nonStandardCodeRaw[nsKey];
1290     } else {
1291       newObject["users"] = ipBlockList(nonStandardCodeRaw.users);
1292     }
1293   });
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)) + ")";
1300 } else {
1301   customHeaders["Server"] = "SVR.JS";
1304 function getCustomHeaders() {
1305   return JSON.parse(JSON.stringify(customHeaders));
1308 var vnum = 0;
1309 try {
1310   vnum = process.config.variables.node_module_version;
1311 } catch (err) {
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;
1329   });
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();
1334   });
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, "");
1344   }
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
1367   // Match 5: port
1368   // Match 6: path name
1369   // Match 7: query string
1370   // Match 8: hash
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);
1385   }
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];
1394   if (parsedURI[4]) {
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];
1398   }
1399   if (parsedURI[5]) uobject.port = parsedURI[5];
1400   if (parsedURI[6]) uobject.pathname = parsedURI[6];
1401   if (parsedURI[7]) {
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(/([^&=]*)(?:=([^&]*))?/);
1409         if (parsedQP) {
1410           qobject[parsedQP[1]] = parsedQP[2] ? parsedQP[2] : "";
1411         }
1412       }
1413     });
1414     uobject.query = qobject;
1415   } else {
1416     uobject.query = Object.create(null);
1417   }
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 : "");
1422   return uobject;
1425 // Node.JS mojibake URL fixing function
1426 function fixNodeMojibakeURL(string) {
1427   var encoded = "";
1429   //Encode URLs
1430   Buffer.from(string, "latin1").forEach(function (value) {
1431     if (value > 127) {
1432       encoded += "%" + (value < 16 ? "0" : "") + value.toString(16).toUpperCase();
1433     } else {
1434       encoded += String.fromCodePoint(value);
1435     }
1436   });
1438   //Upper case the URL encodings
1439   return encoded.replace(/%[0-9a-f-A-F]{2}/g, function (match) {
1440     return match.toUpperCase();
1441   });
1444 // SSL-related
1445 var key = "";
1446 var cert = "";
1448 if (secure) {
1449   if (!configJSON.key) configJSON.key = "cert/key.key";
1450   if (!configJSON.cert) configJSON.cert = "cert/cert.crt";
1451 } else {
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")) {
1459   saveConfig();
1462 var certificateError = null;
1463 var sniReDos = false;
1465 // Load SNI
1466 if (secure) {
1467   try {
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(/\*[^*.:]*\*[^*.:]*(?:\.|:|$)/)) {
1474         sniReDos = true;
1475       }
1476       sniCredentials.push({
1477         name: sniName,
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()
1480       });
1481     });
1482   } catch (err) {
1483     certificateError = err;
1484   }
1487 var logFile = undefined;
1488 var logSync = false;
1490 // Logging function
1491 function LOG(s) {
1492   try {
1493     if (configJSON.enableLogging || configJSON.enableLogging == undefined) {
1494       if (logSync) {
1495         fs.appendFileSync(__dirname + "/log/" + (cluster.isPrimary ? "master" : (cluster.isPrimary === undefined ? "singlethread" : "worker")) + "-" + timestamp + ".log", "[" + new Date().toISOString() + "] " + s + "\r\n");
1496       } else {
1497         if (!logFile) {
1498           logFile = fs.createWriteStream(__dirname + "/log/" + (cluster.isPrimary ? "master" : (cluster.isPrimary === undefined ? "singlethread" : "worker")) + "-" + timestamp + ".log", {
1499             flags: "a",
1500             autoClose: false
1501           });
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);
1504           });
1505         }
1506         if (logFile.writable) {
1507           logFile.write("[" + new Date().toISOString() + "] " + s + "\r\n");
1508         } else {
1509           throw new Error("Log file stream is closed.");
1510         }
1511       }
1512     }
1513   } catch (err) {
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);
1515   }
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);
1524       });
1525       return;
1526     }
1527     console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m: " + msg);
1528     LOG("SERVER CLI MESSAGE: " + msg);
1529     return;
1530   },
1531   reqmessage: function (msg) {
1532     if (msg.indexOf("\n") != -1) {
1533       msg.split("\n").forEach(function (nmsg) {
1534         serverconsole.reqmessage(nmsg);
1535       });
1536       return;
1537     }
1538     console.log("\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m: " + msg + "\x1b[37m\x1b[0m");
1539     LOG("SERVER REQUEST MESSAGE: " + msg);
1540     return;
1541   },
1542   resmessage: function (msg) {
1543     if (msg.indexOf("\n") != -1) {
1544       msg.split("\n").forEach(function (nmsg) {
1545         serverconsole.resmessage(nmsg);
1546       });
1547       return;
1548     }
1549     console.log("\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m: " + msg + "\x1b[37m\x1b[0m");
1550     LOG("SERVER RESPONSE MESSAGE: " + msg);
1551     return;
1552   },
1553   errmessage: function (msg) {
1554     if (msg.indexOf("\n") != -1) {
1555       msg.split("\n").forEach(function (nmsg) {
1556         serverconsole.errmessage(nmsg);
1557       });
1558       return;
1559     }
1560     console.log("\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m: " + msg + "\x1b[37m\x1b[0m");
1561     LOG("SERVER RESPONSE ERROR MESSAGE: " + msg);
1562     return;
1563   },
1564   locerrmessage: function (msg) {
1565     if (msg.indexOf("\n") != -1) {
1566       msg.split("\n").forEach(function (nmsg) {
1567         serverconsole.locerrmessage(nmsg);
1568       });
1569       return;
1570     }
1571     console.log("\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m: " + msg + "\x1b[40m\x1b[0m");
1572     LOG("SERVER ERROR MESSAGE: " + msg);
1573     return;
1574   },
1575   locwarnmessage: function (msg) {
1576     if (msg.indexOf("\n") != -1) {
1577       msg.split("\n").forEach(function (nmsg) {
1578         serverconsole.locwarnmessage(nmsg);
1579       });
1580       return;
1581     }
1582     console.log("\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m: " + msg + "\x1b[40m\x1b[0m");
1583     LOG("SERVER WARNING MESSAGE: " + msg);
1584     return;
1585   },
1586   locmessage: function (msg) {
1587     if (msg.indexOf("\n") != -1) {
1588       msg.split("\n").forEach(function (nmsg) {
1589         serverconsole.locmessage(nmsg);
1590       });
1591       return;
1592     }
1593     console.log("\x1b[1mSERVER MESSAGE\x1b[22m: " + msg);
1594     LOG("SERVER MESSAGE: " + msg);
1595     return;
1596   }
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) {
1603     try {
1604       logFile.close(function () {
1605         logFile = undefined;
1606         logSync = true;
1607         process.unsafeExit(code);
1608       });
1609       if (process.isBun) {
1610         setInterval(function () {
1611           if (!logFile.writable) {
1612             logFile = undefined;
1613             logSync = true;
1614             process.unsafeExit(code);
1615           }
1616         }, 50); // Interval
1617       }
1618       setTimeout(function () {
1619         logFile = undefined;
1620         logSync = true;
1621         process.unsafeExit(code);
1622       }, 10000); // timeout
1623     } catch (err) {
1624       logFile = undefined;
1625       logSync = true;
1626       process.unsafeExit(code);
1627     }
1628   } else {
1629     logSync = true;
1630     process.unsafeExit(code);
1631   }
1634 // SVR.JS mod loader
1635 var modLoadingErrors = [];
1636 var SSJSError = undefined;
1638 // Load mods if the `disableMods` flag is not set
1639 if (!disableMods) {
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);
1645   }
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";
1652   }
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;
1659     try {
1660       // Try creating the modloader folder (if not already exists)
1661       try {
1662         fs.mkdirSync(__dirname + "/temp/" + modloaderFolderName);
1663       } catch (err) {
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");
1668           try {
1669             fs.mkdirSync(__dirname + "/temp/" + modloaderFolderName);
1670           } catch (err) {
1671             // If there was another error, throw it
1672             if (err.code != "EEXIST") throw err;
1673           }
1674         }
1675       }
1677       // Create a subfolder for the current mod within the modloader folder
1678       fs.mkdirSync(__dirname + "/temp/" + modloaderFolderName + "/" + modFileRaw);
1679     } catch (err) {
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.
1683     }
1685     // Check if the current mod file is a regular file
1686     if (fs.statSync(modFile).isFile()) {
1687       try {
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;
1692           tar.x({
1693             file: modFile,
1694             sync: true,
1695             C: __dirname + "/temp/" + modloaderFolderName + "/" + modFileRaw
1696           });
1697         } else {
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.");
1700         }
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++) {
1708           try {
1709             Mod = require("./temp/" + modloaderFolderName + "/" + modFileRaw + "/index.js");
1710             mod = new Mod();
1711             break;
1712           } catch (err) {
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
1718           }
1719         }
1721         // Add the loaded mod to the mods list
1722         mods.push(mod);
1724         // Attempt to read the mod's info file, retrying up to 3 times
1725         for (var j = 0; j < 3; j++) {
1726           try {
1727             modInfos.push(JSON.parse(fs.readFileSync(__dirname + "/temp/" + modloaderFolderName + "/" + modFileRaw + "/mod.info")));
1728             break;
1729           } catch (err) {
1730             if (j >= 2) {
1731               // If failed to read info file, add a placeholder entry to modInfos with an error message
1732               modInfos.push({
1733                 name: "Unknown mod (" + modFileRaw + ";" + err.message + ")",
1734                 version: "ERROR"
1735               });
1736             }
1737             // Wait for a short time before retrying
1738             var now = Date.now();
1739             while (Date.now() - now < 2);
1740             // Try reloading mod info
1741           }
1742         }
1743       } catch (err) {
1744         modLoadingErrors.push({
1745           error: err,
1746           modName: modFileRaw
1747         });
1748       }
1749     }
1750   });
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()) {
1758     try {
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++) {
1771         try {
1772           aMod = require("./temp/" + tempServerSideScriptName);
1773           amod = new aMod();
1774           break;
1775         } catch (err) {
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
1781         }
1782       }
1784       // Add the loaded server side script to the mods list
1785       mods.push(amod);
1786     } catch (err) {
1787       SSJSError = err;
1788     }
1789   }
1793 // SHA256 function
1794 function sha256(s) {
1795   if (crypto.__disabled__ === undefined) {
1796     var hash = crypto.createHash("SHA256");
1797     hash.update(s);
1798     return hash.digest("hex");
1799   } else {
1800     var chrsz = 8;
1801     var hexcase = 0;
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);
1807     }
1809     function S(X, n) {
1810       return (X >>> n) | (X << (32 - n));
1811     }
1813     function R(X, n) {
1814       return (X >>> n);
1815     }
1817     function Ch(x, y, z) {
1818       return ((x & y) ^ ((~x) & z));
1819     }
1821     function Maj(x, y, z) {
1822       return ((x & y) ^ (x & z) ^ (y & z));
1823     }
1825     function Sigma0256(x) {
1826       return (S(x, 2) ^ S(x, 13) ^ S(x, 22));
1827     }
1829     function Sigma1256(x) {
1830       return (S(x, 6) ^ S(x, 11) ^ S(x, 25));
1831     }
1833     function Gamma0256(x) {
1834       return (S(x, 7) ^ S(x, 18) ^ R(x, 3));
1835     }
1837     function Gamma1256(x) {
1838       return (S(x, 17) ^ S(x, 19) ^ R(x, 10));
1839     }
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;
1846       var T1, T2;
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) {
1852         a = HASH[0];
1853         b = HASH[1];
1854         c = HASH[2];
1855         d = HASH[3];
1856         e = HASH[4];
1857         f = HASH[5];
1858         g = HASH[6];
1859         h = HASH[7];
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));
1868           h = g;
1869           g = f;
1870           f = e;
1871           e = safeAdd(d, T1);
1872           d = c;
1873           c = b;
1874           b = a;
1875           a = safeAdd(T1, T2);
1876         }
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]);
1886       }
1887       return HASH;
1888     }
1890     function str2binb(str) {
1891       var bin = Array();
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);
1895       }
1896       return bin;
1897     }
1899     function Utf8Encode(string) {
1900       string = string.replace(/\r\n/g, "\n");
1901       var utftext = "";
1903       for (var n = 0; n < string.length; n++) {
1905         var c = string.charCodeAt(n);
1907         if (c < 128) {
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);
1912         } else {
1913           utftext += String.fromCharCode((c >> 12) | 224);
1914           utftext += String.fromCharCode(((c >> 6) & 63) | 128);
1915           utftext += String.fromCharCode((c & 63) | 128);
1916         }
1918       }
1920       return utftext;
1921     }
1923     function binb2hex(binarray) {
1924       var hexTab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
1925       var str = "";
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);
1929       }
1930       return str;
1931     }
1933     s = Utf8Encode(s);
1934     return binb2hex(coreSha256(str2binb(s), s.length * chrsz));
1935   }
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;
1944   }
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, "/");
1950   } else {
1951     return "/" + relative;
1952   }
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());
1961   }
1962   if (typeof forbiddenPath === "object") {
1963     return forbiddenPath.some(function (forbiddenPathSingle) {
1964       return (decodedHref === forbiddenPathSingle || (os.platform() === "win32" && decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase()));
1965     });
1966   }
1967   return false;
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));
1976   }
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)));
1980     });
1981   }
1982   return false;
1985 // Set up forbidden paths
1986 var forbiddenPaths = {};
1988 forbiddenPaths.config = getInitializePath("./config.json");
1989 forbiddenPaths.certificates = [];
1990 if (secure) {
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));
1996   });
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");
2002 } else {
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) {
2087   var reqcounter = 0;
2088   var malformedcounter = 0;
2089   var err4xxcounter = 0;
2090   var err5xxcounter = 0;
2091   var reqcounterKillReq = 0;
2092   var server = {};
2093   var server2 = {};
2094   try {
2095     server2 = http.createServer({
2096       requireHostHeader: false
2097     });
2098   } catch (err) {
2099     server2 = http.createServer();
2100   }
2101   server2.on("request", function (req, res) {
2102     reqhandler(req, res, false);
2103   });
2104   server2.on("checkExpectation", reqhandler);
2105   server2.on("clientError", function (err, socket) {
2106     reqerrhandler(err, socket, false);
2107   });
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);
2118             });
2119             return;
2120           }
2121           console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2122           LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
2123           return;
2124         },
2125         reqmessage: function (msg) {
2126           if (msg.indexOf("\n") != -1) {
2127             msg.split("\n").forEach(function (nmsg) {
2128               serverconsole.reqmessage(nmsg);
2129             });
2130             return;
2131           }
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);
2134           return;
2135         },
2136         resmessage: function (msg) {
2137           if (msg.indexOf("\n") != -1) {
2138             msg.split("\n").forEach(function (nmsg) {
2139               serverconsole.resmessage(nmsg);
2140             });
2141             return;
2142           }
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);
2145           return;
2146         },
2147         errmessage: function (msg) {
2148           if (msg.indexOf("\n") != -1) {
2149             msg.split("\n").forEach(function (nmsg) {
2150               serverconsole.errmessage(nmsg);
2151             });
2152             return;
2153           }
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);
2156           return;
2157         },
2158         locerrmessage: function (msg) {
2159           if (msg.indexOf("\n") != -1) {
2160             msg.split("\n").forEach(function (nmsg) {
2161               serverconsole.locerrmessage(nmsg);
2162             });
2163             return;
2164           }
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);
2167           return;
2168         },
2169         locwarnmessage: function (msg) {
2170           if (msg.indexOf("\n") != -1) {
2171             msg.split("\n").forEach(function (nmsg) {
2172               serverconsole.locwarnmessage(nmsg);
2173             });
2174             return;
2175           }
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);
2178           return;
2179         },
2180         locmessage: function (msg) {
2181           if (msg.indexOf("\n") != -1) {
2182             msg.split("\n").forEach(function (nmsg) {
2183               serverconsole.locmessage(nmsg);
2184             });
2185             return;
2186           }
2187           console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2188           LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2189           return;
2190         }
2191       };
2192       socket.on("close", function (hasError) {
2193         if (!hasError) serverconsole.locmessage("Client disconnected.");
2194         else serverconsole.locmessage("Client disconnected due to error.");
2195       });
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 + "...");
2200       reqcounter++;
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");
2205     });
2206   } else {
2207     server2.on("connect", connhandler);
2208   }
2209   server2.on("error", function (err) {
2210     serverErrorHandler(err, true);
2211   });
2212   server2.on("listening", function () {
2213     attmtsRedir = 5;
2214     listeningMessage();
2215   });
2217   if (configJSON.enableHTTP2 == true) {
2218     if (secure) {
2219       server = http2.createSecureServer({
2220         allowHTTP1: true,
2221         requireHostHeader: false,
2222         key: key,
2223         cert: cert,
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
2232       });
2233     } else {
2234       server = http2.createServer({
2235         allowHTTP1: true,
2236         requireHostHeader: false,
2237         settings: configJSON.http2Settings
2238       });
2239     }
2240   } else {
2241     if (secure) {
2242       server = https.createServer({
2243         key: key,
2244         cert: cert,
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
2253       });
2254     } else {
2255       try {
2256         server = http.createServer({
2257           requireHostHeader: false
2258         });
2259       } catch (err) {
2260         server = http.createServer();
2261       }
2262     }
2263   }
2264   if (secure) {
2265     try {
2266       sniCredentials.forEach(function (sniCredentialsSingle) {
2267         server.addContext(sniCredentialsSingle.name, {
2268           cert: sniCredentialsSingle.cert,
2269           key: sniCredentialsSingle.key
2270         });
2271         try {
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");
2275         } catch (ex) {
2276           // Can't replace regex, ignoring...
2277         }
2278       });
2279     } catch (err) {
2280       // SNI error
2281     }
2282   }
2283   server.on("request", reqhandler);
2284   server.on("checkExpectation", reqhandler);
2285   server.on("connect", connhandler);
2286   server.on("clientError", reqerrhandler);
2288   if (secure) {
2289     server.prependListener("connection", function (sock) {
2290       sock.reallyDestroy = sock.destroy;
2291       sock.destroy = function () {
2292         sock.toDestroy = true;
2293       };
2294     });
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);
2304         };
2305         sock.write = function (a, b, c) {
2306           sock._parent.write(a, b, c);
2307         };
2308         sock.end = function (a, b, c) {
2309           sock._parent.end(a, b, c);
2310         };
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) {
2317           try {
2318             sock._parent.destroy(a, b, c);
2319             sock.destroyed = sock._parent.destroyed;
2320           } catch (err) {
2321             // Socket is probably already destroyed.
2322           }
2323         };
2324       } else {
2325         sock._parent.destroy = sock._parent.reallyDestroy;
2326         try {
2327           if (sock._parent.toDestroy) sock._parent.destroy();
2328         } catch (err) {
2329           // Socket is probably already destroyed.
2330         }
2331       }
2332     });
2334     server.prependListener("secureConnection", function (sock) {
2335       sock._parent.destroy = sock._parent.reallyDestroy;
2336       delete sock._parent.reallyDestroy;
2337     });
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);
2345           var options = {
2346             url: uri,
2347             ocsp: req.data
2348           };
2350           ocspCache.request(req.id, options, callback);
2351         });
2352       });
2353     }
2354   }
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
2363     var res = {};
2364     res.socket = socket;
2365     res.write = function (x) {
2366       if (err.code === "ECONNRESET" || !socket.writable) {
2367         return;
2368       }
2369       socket.write(x);
2370     };
2371     res.end = function (x) {
2372       if (err.code === "ECONNRESET" || !socket.writable) {
2373         return;
2374       }
2375       socket.end(x, function () {
2376         try {
2377           socket.destroy();
2378         } catch (err) {
2379           // Socket is probably already destroyed
2380         }
2381       });
2382     };
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);
2395           });
2396         } else {
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]);
2399         }
2400         head += "\r\n";
2401       });
2402       head += ("\r\n");
2403       res.write(head);
2404     };
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);
2414           });
2415           return;
2416         }
2417         console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2418         LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
2419         return;
2420       },
2421       reqmessage: function (msg) {
2422         if (msg.indexOf("\n") != -1) {
2423           msg.split("\n").forEach(function (nmsg) {
2424             serverconsole.reqmessage(nmsg);
2425           });
2426           return;
2427         }
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);
2430         return;
2431       },
2432       resmessage: function (msg) {
2433         if (msg.indexOf("\n") != -1) {
2434           msg.split("\n").forEach(function (nmsg) {
2435             serverconsole.resmessage(nmsg);
2436           });
2437           return;
2438         }
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);
2441         return;
2442       },
2443       errmessage: function (msg) {
2444         if (msg.indexOf("\n") != -1) {
2445           msg.split("\n").forEach(function (nmsg) {
2446             serverconsole.errmessage(nmsg);
2447           });
2448           return;
2449         }
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);
2452         return;
2453       },
2454       locerrmessage: function (msg) {
2455         if (msg.indexOf("\n") != -1) {
2456           msg.split("\n").forEach(function (nmsg) {
2457             serverconsole.locerrmessage(nmsg);
2458           });
2459           return;
2460         }
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);
2463         return;
2464       },
2465       locwarnmessage: function (msg) {
2466         if (msg.indexOf("\n") != -1) {
2467           msg.split("\n").forEach(function (nmsg) {
2468             serverconsole.locwarnmessage(nmsg);
2469           });
2470           return;
2471         }
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);
2474         return;
2475       },
2476       locmessage: function (msg) {
2477         if (msg.indexOf("\n") != -1) {
2478           msg.split("\n").forEach(function (nmsg) {
2479             serverconsole.locmessage(nmsg);
2480           });
2481           return;
2482         }
2483         console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2484         LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2485         return;
2486       }
2487     };
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.");
2491     });
2492     socket.on("error", function () {});
2494     // Header and footer placeholders
2495     var head = "";
2496     var foot = "";
2498     function responseEnd(body) {
2499       // If body is Buffer, then it is converted to String anyway.
2500       res.write(head + body + foot);
2501       res.end();
2502     }
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.");
2508       }
2510       // Handle optional parameters
2511       if (extName && typeof extName === "object") {
2512         ch = stack;
2513         stack = extName;
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.");
2517       }
2519       if (stack && typeof stack === "object" && Object.prototype.toString.call(stack) !== "[object Error]") {
2520         ch = stack;
2521         stack = undefined;
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.");
2524       }
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");
2531           return;
2532         }
2534         function medCallback(p) {
2535           if (p) callback(p);
2536           else {
2537             if (errorCode == 404) {
2538               fs.access(page404, fs.constants.F_OK, function (err) {
2539                 if (err) {
2540                   fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
2541                     try {
2542                       if (err) {
2543                         callback(errorCode.toString() + ".html");
2544                       } else {
2545                         callback("." + errorCode.toString());
2546                       }
2547                     } catch (err2) {
2548                       callServerError(500, err2);
2549                     }
2550                   });
2551                 } else {
2552                   try {
2553                     callback(page404);
2554                   } catch (err2) {
2555                     callServerError(500, err2);
2556                   }
2557                 }
2558               });
2559             } else {
2560               fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
2561                 try {
2562                   if (err) {
2563                     callback(errorCode.toString() + ".html");
2564                   } else {
2565                     callback("." + errorCode.toString());
2566                   }
2567                 } catch (err2) {
2568                   callServerError(500, err2);
2569                 }
2570               });
2571             }
2572           }
2573         }
2575         if (!_i) _i = 0;
2576         if (_i >= list.length) {
2577           medCallback(false);
2578           return;
2579         }
2581         if (list[_i].scode != errorCode) {
2582           getErrorFileName(list, callback, _i + 1);
2583           return;
2584         } else {
2585           fs.access(list[_i].path, fs.constants.F_OK, function (err) {
2586             if (err) {
2587               getErrorFileName(list, callback, _i + 1);
2588             } else {
2589               medCallback(list[_i].path);
2590             }
2591           });
2592         }
2593       }
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);
2602         }
2603         if (stackHidden) stack = "[error stack hidden]";
2604         if (serverHTTPErrorDescs[errorCode] === undefined) {
2605           callServerError(501, extName, stack);
2606         } else {
2607           var cheaders = getCustomHeaders();
2608           if (ch) {
2609             var chon = Object.keys(cheaders);
2610             Object.keys(ch).forEach(function (chnS) {
2611               var nhn = chnS;
2612               for (var j = 0; j < chon.length; j++) {
2613                 if (chon[j].toLowerCase() == chnS.toLowerCase()) {
2614                   nhn = chon[j];
2615                   break;
2616                 }
2617               }
2618               if (ch[chnS]) cheaders[nhn] = ch[chnS];
2619             });
2620           }
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/ {2}/g, "&nbsp;&nbsp;")).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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\./g, "[dot]").replace(/@/g, "[at]")));
2627             res.end();
2628           } else {
2629             fs.readFile(errorFile, function (err, data) {
2630               try {
2631                 if (err) throw err;
2632                 res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
2633                 responseEnd(data.toString().replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/ {2}/g, "&nbsp;&nbsp;")).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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\./g, "[dot]").replace(/@/g, "[at]")));
2634               } catch (err) {
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;
2648                 }
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/ {2}/g, "&nbsp;&nbsp;")).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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).replace(/{contact}/g, serverAdmin.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\./g, "[dot]").replace(/@/g, "[at]")).replace(/{additionalError}/g, additionalError.toString()));
2651                 res.end();
2652               }
2653             });
2654           }
2655         }
2656       });
2657     }
2658     var reqip = socket.remoteAddress;
2659     var reqport = socket.remotePort;
2660     reqcounter++;
2661     malformedcounter++;
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.");
2664     try {
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);
2672           return;
2673         } else {
2674           serverconsole.errmessage("An SSL error occured: " + (err.code ? err.code : err.message));
2675           callServerError(400);
2676           return;
2677         }
2678       }
2680       if (err.code && err.code.indexOf("ERR_HTTP2_") == 0) {
2681         serverconsole.errmessage("An HTTP/2 error occured: " + err.code);
2682         callServerError(400);
2683         return;
2684       }
2686       if (err.code && err.code == "ERR_HTTP_REQUEST_TIMEOUT") {
2687         serverconsole.errmessage("Client timed out.");
2688         callServerError(408);
2689         return;
2690       }
2692       if (!err.rawPacket) {
2693         serverconsole.errmessage("Connection ended prematurely.");
2694         callServerError(400);
2695         return;
2696       }
2698       var packetLines = err.rawPacket.toString().split("\r\n");
2699       if (packetLines.length == 0) {
2700         serverconsole.errmessage("Invalid request.");
2701         callServerError(400);
2702         return;
2703       }
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);
2712             return true;
2713           } else if (header.length > 8192) {
2714             serverconsole.errmessage("Header too large.");
2715             callServerError(431); // Headers too large
2716             return true;
2717           }
2718         }
2719         return false;
2720       }
2721       var packetLine1 = packetLines[0].split(" ");
2722       var method = "GET";
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
2728           return;
2729         }
2730       }
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
2742       } else {
2743         if (checkHeaders(false)) return;
2744         if (packetLine1[0].length > 255) {
2745           serverconsole.errmessage("URI too long.");
2746           callServerError(414); // Also malformed Packet
2747         } else {
2748           serverconsole.errmessage("The request is invalid.");
2749           callServerError(400); // Also malformed Packet
2750         }
2751       }
2752     } catch (err) {
2753       serverconsole.errmessage("There was an error while determining type of malformed request.");
2754       callServerError(400);
2755     }
2756   }
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);
2767           });
2768           return;
2769         }
2770         console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2771         LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
2772         return;
2773       },
2774       reqmessage: function (msg) {
2775         if (msg.indexOf("\n") != -1) {
2776           msg.split("\n").forEach(function (nmsg) {
2777             serverconsole.reqmessage(nmsg);
2778           });
2779           return;
2780         }
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);
2783         return;
2784       },
2785       resmessage: function (msg) {
2786         if (msg.indexOf("\n") != -1) {
2787           msg.split("\n").forEach(function (nmsg) {
2788             serverconsole.resmessage(nmsg);
2789           });
2790           return;
2791         }
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);
2794         return;
2795       },
2796       errmessage: function (msg) {
2797         if (msg.indexOf("\n") != -1) {
2798           msg.split("\n").forEach(function (nmsg) {
2799             serverconsole.errmessage(nmsg);
2800           });
2801           return;
2802         }
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);
2805         return;
2806       },
2807       locerrmessage: function (msg) {
2808         if (msg.indexOf("\n") != -1) {
2809           msg.split("\n").forEach(function (nmsg) {
2810             serverconsole.locerrmessage(nmsg);
2811           });
2812           return;
2813         }
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);
2816         return;
2817       },
2818       locwarnmessage: function (msg) {
2819         if (msg.indexOf("\n") != -1) {
2820           msg.split("\n").forEach(function (nmsg) {
2821             serverconsole.locwarnmessage(nmsg);
2822           });
2823           return;
2824         }
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);
2827         return;
2828       },
2829       locmessage: function (msg) {
2830         if (msg.indexOf("\n") != -1) {
2831           msg.split("\n").forEach(function (nmsg) {
2832             serverconsole.locmessage(nmsg);
2833           });
2834           return;
2835         }
2836         console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2837         LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2838         return;
2839       }
2840     };
2841     var req = request;
2842     socket.on("close", function (hasError) {
2843       if (!hasError) serverconsole.locmessage("Client disconnected.");
2844       else serverconsole.locmessage("Client disconnected due to error.");
2845     });
2846     socket.on("error", function () {});
2848     var reqip = socket.remoteAddress;
2849     var reqport = socket.remotePort;
2850     reqcounter++;
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) {
2856       var proxyMods = [];
2857       mods.forEach(function (mod) {
2858         if (mod.proxyCallback !== undefined) proxyMods.push(mod);
2859       });
2861       var modFunction = ffinals;
2862       proxyMods.reverse().forEach(function (proxyMod) {
2863         modFunction = proxyMod.proxyCallback(req, socket, head, configJSON, serverconsole, modFunction);
2864       });
2865       modFunction();
2866     }
2868     function vres() {
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");
2871     }
2872     modExecute(mods, vres);
2873   }
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);
2885           });
2886           return;
2887         }
2888         console.log("\x1b[1mSERVER CLI MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2889         LOG("SERVER CLI MESSAGE [Request Id: " + reqId + "]: " + msg);
2890         return;
2891       },
2892       reqmessage: function (msg) {
2893         if (msg.indexOf("\n") != -1) {
2894           msg.split("\n").forEach(function (nmsg) {
2895             serverconsole.reqmessage(nmsg);
2896           });
2897           return;
2898         }
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);
2901         return;
2902       },
2903       resmessage: function (msg) {
2904         if (msg.indexOf("\n") != -1) {
2905           msg.split("\n").forEach(function (nmsg) {
2906             serverconsole.resmessage(nmsg);
2907           });
2908           return;
2909         }
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);
2912         return;
2913       },
2914       errmessage: function (msg) {
2915         if (msg.indexOf("\n") != -1) {
2916           msg.split("\n").forEach(function (nmsg) {
2917             serverconsole.errmessage(nmsg);
2918           });
2919           return;
2920         }
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);
2923         return;
2924       },
2925       locerrmessage: function (msg) {
2926         if (msg.indexOf("\n") != -1) {
2927           msg.split("\n").forEach(function (nmsg) {
2928             serverconsole.locerrmessage(nmsg);
2929           });
2930           return;
2931         }
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);
2934         return;
2935       },
2936       locwarnmessage: function (msg) {
2937         if (msg.indexOf("\n") != -1) {
2938           msg.split("\n").forEach(function (nmsg) {
2939             serverconsole.locwarnmessage(nmsg);
2940           });
2941           return;
2942         }
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);
2945         return;
2946       },
2947       locmessage: function (msg) {
2948         if (msg.indexOf("\n") != -1) {
2949           msg.split("\n").forEach(function (nmsg) {
2950             serverconsole.locmessage(nmsg);
2951           });
2952           return;
2953         }
2954         console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2955         LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2956         return;
2957       }
2958     };
2960     function matchHostname(hostname) {
2961       if (typeof hostname == "undefined" || hostname == "*") {
2962         return true;
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)) {
2966           return true;
2967         }
2968       } else if (req.headers.host && req.headers.host == hostname) {
2969         return true;
2970       }
2971       return false;
2972     }
2974     function getCustomHeaders() {
2975       var ph = JSON.parse(JSON.stringify(customHeaders));
2976       if (configJSON.customHeadersVHost) {
2977         var vhostP = null;
2978         configJSON.customHeadersVHost.every(function (vhost) {
2979           if (matchHostname(vhost.host) && ipMatch(vhost.ip, req.socket ? req.socket.localAddress : undefined)) {
2980             vhostP = vhost;
2981             return false;
2982           } else {
2983             return true;
2984           }
2985         });
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];
2990           });
2991         }
2992       }
2993       Object.keys(ph).forEach(function (phk) {
2994         if (typeof ph[phk] == "string") ph[phk] = ph[phk].replace(/\{path\}/g, req.url);
2995       });
2996       return ph;
2997     }
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) {
3006         var table = 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];
3014         });
3015         if (res.stream && res.stream.destroyed) {
3016           return false;
3017         } else {
3018           return res.writeHeadNodeApi(a, table);
3019         }
3020       };
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);
3024         return false;
3025       };
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);
3036       }
3037     }
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);
3042       res.end();
3043       return;
3044     }
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)) {
3053         if (headWritten) {
3054           process.emitWarning("res.writeHead called multiple times.", {
3055             code: "WARN_SVRJS_MULTIPLE_WRITEHEAD"
3056           });
3057           return res;
3058         } else {
3059           headWritten = true;
3060         }
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.");
3065         } else {
3066           serverconsole.resmessage("Server responded with " + code.toString() + " code.");
3067         }
3068         if (typeof codeDescription != "string" && http.STATUS_CODES[code]) {
3069           if (!headers) headers = codeDescription;
3070           codeDescription = http.STATUS_CODES[code];
3071         }
3072         lastStatusCode = code;
3073       }
3074       res.writeHeadNative(code, codeDescription, headers);
3075     };
3077     var finished = false;
3078     res.on("finish", function () {
3079       if (!finished) {
3080         finished = true;
3081         serverconsole.locmessage("Client disconnected.");
3082       }
3083     });
3084     res.on("close", function () {
3085       if (!finished) {
3086         finished = true;
3087         serverconsole.locmessage("Client disconnected.");
3088       }
3089     });
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!!!");
3096       return;
3097     }
3099     // Set up X-Forwarded-For
3100     var reqip = req.socket.remoteAddress;
3101     var reqport = req.socket.remotePort;
3102     var oldip = "";
3103     var oldport = "";
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;
3112           reqport = null;
3113           try {
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;
3124           } catch (err) {
3125             // Address setting failed
3126           }
3127         } else {
3128           isForwardedValid = false;
3129         }
3130       }
3131     }
3133     reqcounter++;
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, "");
3140     }
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
3150     var head = "";
3151     var foot = "";
3153     function responseEnd(body) {
3154       // If body is Buffer, then it is converted to String anyway.
3155       res.write(head + body + foot);
3156       res.end();
3157     }
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.");
3163       }
3165       // Handle optional parameters
3166       if (extName && typeof extName === "object") {
3167         ch = stack;
3168         stack = extName;
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.");
3172       }
3174       if (stack && typeof stack === "object" && Object.prototype.toString.call(stack) !== "[object Error]") {
3175         ch = stack;
3176         stack = undefined;
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.");
3179       }
3181       // Determine error file
3182       function getErrorFileName(list, callback, _i) {
3183         function medCallback(p) {
3184           if (p) callback(p);
3185           else {
3186             if (errorCode == 404) {
3187               fs.access(page404, fs.constants.F_OK, function (err) {
3188                 if (err) {
3189                   fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
3190                     try {
3191                       if (err) {
3192                         callback(errorCode.toString() + ".html");
3193                       } else {
3194                         callback("." + errorCode.toString());
3195                       }
3196                     } catch (err2) {
3197                       callServerError(500, err2);
3198                     }
3199                   });
3200                 } else {
3201                   try {
3202                     callback(page404);
3203                   } catch (err2) {
3204                     callServerError(500, err2);
3205                   }
3206                 }
3207               });
3208             } else {
3209               fs.access("." + errorCode.toString(), fs.constants.F_OK, function (err) {
3210                 try {
3211                   if (err) {
3212                     callback(errorCode.toString() + ".html");
3213                   } else {
3214                     callback("." + errorCode.toString());
3215                   }
3216                 } catch (err2) {
3217                   callServerError(500, err2);
3218                 }
3219               });
3220             }
3221           }
3222         }
3224         if (!_i) _i = 0;
3225         if (_i >= list.length) {
3226           medCallback(false);
3227           return;
3228         }
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);
3232           return;
3233         } else {
3234           fs.access(list[_i].path, fs.constants.F_OK, function (err) {
3235             if (err) {
3236               getErrorFileName(list, callback, _i + 1);
3237             } else {
3238               medCallback(list[_i].path);
3239             }
3240           });
3241         }
3242       }
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);
3253         }
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);
3261         } else {
3262           var cheaders = getCustomHeaders();
3264           // Process custom headers if provided
3265           if (ch) {
3266             var chon = Object.keys(cheaders);
3267             Object.keys(ch).forEach(function (chnS) {
3268               var nhn = chnS;
3269               for (var j = 0; j < chon.length; j++) {
3270                 if (chon[j].toLowerCase() == chnS.toLowerCase()) {
3271                   nhn = chon[j];
3272                   break;
3273                 }
3274               }
3275               if (ch[chnS]) cheaders[nhn] = ch[chnS];
3276             });
3277           }
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) {
3286             try {
3287               if (err) throw err;
3288               res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
3289               responseEnd(data.toString().replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/ {2}/g, "&nbsp;&nbsp;")).replace(/{path}/g, req.url.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + ((req.headers.host == undefined || isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"))).replace(/{contact}/g, serverAdmin.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\./g, "[dot]").replace(/@/g, "[at]"))); // Replace placeholders in error response
3290             } catch (err) {
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;
3305               }
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/ {2}/g, "&nbsp;&nbsp;")).replace(/{path}/g, req.url.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + ((req.headers.host == undefined || isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"))).replace(/{contact}/g, serverAdmin.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\./g, "[dot]").replace(/@/g, "[at]")).replace(/{additionalError}/g, additionalError.toString())); // Replace placeholders in error response
3309               res.end();
3310             }
3311           });
3312         }
3313       });
3314     }
3316     try {
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
3319     } catch (err) {
3320       callServerError(500, err);
3321     }
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);
3347       // End the response
3348       res.end();
3350       // Return from the function
3351       return;
3352     }
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);
3364         return;
3365       }
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
3371       if (!callback) {
3372         callback = options;
3373         formidableOptions = {};
3374       }
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
3385         if (err) {
3386           if (err.httpCode) callServerError(err.httpCode);
3387           else callServerError(400);
3388           return;
3389         }
3390         // Otherwise, call the provided callback function with the parsed fields and files
3391         callback(fields, files);
3392       });
3393     }
3395     // Authenticated user variable
3396     var authUser = null;
3398     // URL-related objects.
3399     var uobject = {};
3400     try {
3401       uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
3402     } catch (err) {
3403       // Return an 400 error
3404       callServerError(400);
3405       serverconsole.errmessage("Bad request!");
3406       return;
3407     }
3408     var search = uobject.search;
3409     var href = uobject.pathname;
3410     var ext = href.match(/[^\/]\.([^.]+)$/);
3411     if(!ext) ext = "";
3412     else ext = ext[1].toLowerCase();
3413     var decodedHref = "";
3414     try {
3415       decodedHref = decodeURIComponent(href);
3416     } catch (err) {
3417       // Return an 400 error
3418       callServerError(400);
3419       serverconsole.errmessage("Bad request!");
3420       return;
3421     }
3422     var origHref = href; // Placeholder origHref
3424     if (req.headers["expect"] && req.headers["expect"] != "100-continue") {
3425       // Expectations not met.
3426       callServerError(417);
3427       return;
3428     }
3430     // Mod execution function
3431     function modExecute(mods, ffinals) {
3432       // Prepare modFunction
3433       var modFunction = ffinals;
3434       var useMods = mods.slice();
3436       if (isProxy) {
3437         // Get list of forward proxy mods
3438         useMods = [];
3439         mods.forEach(function (mod) {
3440           if (mod.proxyCallback !== undefined) useMods.push(mod);
3441         });
3442       }
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);
3446       });
3448       // Execute modFunction
3449       modFunction();
3450     }
3452     var vresCalled = false;
3454     function vres() {
3455       if (vresCalled) {
3456         process.emitWarning("elseCallback() invoked multiple times.", {
3457           code: "WARN_SVRJS_MULTIPLE_ELSECALLBACK"
3458         });
3459         return;
3460       } else {
3461         vresCalled = true;
3462       }
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]) {
3477             levelUpCount--;
3478           }
3479           // If the component is not "." or an empty string, increment the levelDownCount
3480           else if ("." !== pathComponents[i] && "" !== pathComponents[i]) {
3481             levelDownCount++;
3482           }
3483         }
3485         // Calculate the overall level by subtracting levelUpCount from levelDownCount
3486         var overallLevel = levelDownCount - levelUpCount;
3488         // Return the overall level
3489         return overallLevel;
3490       }
3493       if (isProxy) {
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</i></p></body></html>");
3498         res.end();
3499         serverconsole.errmessage("SVR.JS doesn't support proxy without proxy mod.");
3500         return;
3501       }
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);
3507         res.end();
3508         return;
3509       } else if (req.method != "GET" && req.method != "POST" && req.method != "HEAD") {
3510         callServerError(405);
3511         serverconsole.errmessage("Invaild method: " + req.method);
3512         return;
3513       }
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";
3520         }
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "<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"
3539         });
3540         res.end((head == "" ? "<!DOCTYPE html><html><head><title>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) + "</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /><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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) + "</title>")) + "<h1>SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) + "</h1>" + statusBody + (foot == "" ? "</body></html>" : foot));
3541         return;
3542       }
3544       var dHref = decodeURIComponent(href);
3545       var readFrom = "." + dHref;
3546       var dirImagesMissing = false;
3547       fs.stat(readFrom, function (err, stats) {
3548         if (err) {
3549           if (err.code == "ENOENT") {
3550             if (__dirname != process.cwd() && dHref.match(/^\/\.dirimages\/(?:(?!\.png$).)+\.png$/)) {
3551               dirImagesMissing = true;
3552               readFrom = __dirname + dHref;
3553             } else {
3554               callServerError(404);
3555               serverconsole.errmessage("Resource not found.");
3556               return;
3557             }
3558           } else if (err.code == "ENOTDIR") {
3559             callServerError(404); // Assume that file doesn't exist.
3560             serverconsole.errmessage("Resource not found.");
3561             return;
3562           } else if (err.code == "EACCES") {
3563             callServerError(403);
3564             serverconsole.errmessage("Access denied.");
3565             return;
3566           } else if (err.code == "ENAMETOOLONG") {
3567             callServerError(414);
3568             return;
3569           } else if (err.code == "EMFILE") {
3570             callServerError(503);
3571             return;
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.");
3575             return;
3576           } else {
3577             callServerError(500, err);
3578             return;
3579           }
3580         }
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();
3591                     } else {
3592                       stats = s;
3593                       ext = "xhtml";
3594                       readFrom = (readFrom + "/index.xhtml").replace(/\/+/g, "/");
3595                       properDirectoryListingAndStaticFileServe();
3596                     }
3597                   });
3598                 } else {
3599                   stats = s;
3600                   ext = "htm";
3601                   readFrom = (readFrom + "/index.htm").replace(/\/+/g, "/");
3602                   properDirectoryListingAndStaticFileServe();
3603                 }
3604               });
3605             } else {
3606               stats = s;
3607               ext = "html";
3608               readFrom = (readFrom + "/index.html").replace(/\/+/g, "/");
3609               properDirectoryListingAndStaticFileServe();
3610             }
3611           });
3612         } else if (dirImagesMissing) {
3613           fs.stat(readFrom, function (e, s) {
3614             if (e || !s.isFile()) {
3615               properDirectoryListingAndStaticFileServe();
3616             } else {
3617               stats = s;
3618               properDirectoryListingAndStaticFileServe();
3619             }
3620           });
3621         } else {
3622           properDirectoryListingAndStaticFileServe();
3623         }
3625         function properDirectoryListingAndStaticFileServe() {
3626           if (stats.isFile()) {
3627             var acceptEncoding = req.headers["accept-encoding"];
3628             if (!acceptEncoding) acceptEncoding = "";
3630             var filelen = stats.size;
3632             // ETag code
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], {
3640                   "ETag": clientETag
3641                 });
3642                 res.end();
3643                 return;
3644               }
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, {
3650                   "ETag": clientETag
3651                 });
3652                 return;
3653               }
3654             }
3656             // Handle partial content request
3657             if (ext != "html" && req.headers["range"]) {
3658               try {
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]*)/);
3663                 if (!regexmatch) {
3664                   callServerError(416, rhd);
3665                 } else {
3666                   // Process the partial content request
3667                   var beginOrig = regexmatch[1];
3668                   var endOrig = regexmatch[2];
3669                   var begin = 0;
3670                   var end = filelen - 1;
3671                   if (beginOrig == "" && endOrig == "") {
3672                     callServerError(416, rhd);
3673                     return;
3674                   } else if (beginOrig == "") {
3675                     begin = end - parseInt(endOrig) + 1;
3676                   } else {
3677                     begin = parseInt(beginOrig);
3678                     if (endOrig != "") end = parseInt(endOrig);
3679                   }
3680                   if (begin > end || begin < 0 || begin > filelen - 1) {
3681                     callServerError(416, rhd);
3682                     return;
3683                   }
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, {
3692                       start: begin,
3693                       end: end
3694                     });
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.");
3712                       } else {
3713                         callServerError(500, err);
3714                       }
3715                     }).on("open", function () {
3716                       try {
3717                         res.writeHead(206, http.STATUS_CODES[206], rhd);
3718                         readStream.pipe(res);
3719                         serverconsole.resmessage("Client successfully received content.");
3720                       } catch (err) {
3721                         callServerError(500, err);
3722                       }
3723                     });
3724                   } else {
3725                     res.writeHead(206, http.STATUS_CODES[206], rhd);
3726                     res.end();
3727                   }
3728                 }
3729               } catch (err) {
3730                 callServerError(500, err);
3731               }
3732             } else {
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;
3739                     break;
3740                   }
3741                 }
3742                 return canCompress;
3743               }
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;
3750               try {
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.
3759                   }
3760                 } else {
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.
3763                   }
3764                 }
3765               } catch (err) {
3766                 callServerError(500, err);
3767                 return;
3768               }
3770               // Bun 1.1 has definition for zlib.createBrotliCompress, but throws an error while invoking the function.
3771               if (process.isBun && useBrotli && isCompressable) {
3772                 try {
3773                   zlib.createBrotliCompress();
3774                 } catch (err) {
3775                   useBrotli = false;
3776                 }
3777               }
3779               try {
3780                 var hdhds = {};
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";
3787                 } else {
3788                   if (ext == "html") {
3789                     hdhds["Content-Length"] = head.length + filelen + foot.length;
3790                   } else {
3791                     hdhds["Content-Length"] = filelen;
3792                   }
3793                 }
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.");
3818                     } else {
3819                       callServerError(500, err);
3820                     }
3821                   }).on("open", function () {
3822                     try {
3823                       var resStream = {};
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);
3833                       } else {
3834                         resStream = res;
3835                       }
3836                       if (ext == "html") {
3837                         function afterWriteCallback() {
3838                           readStream.on("end", function () {
3839                             resStream.end(foot);
3840                           });
3841                           readStream.pipe(resStream, {
3842                             end: false
3843                           });
3844                         }
3845                         res.writeHead(200, http.STATUS_CODES[200], hdhds);
3846                         if (!resStream.write(head)) {
3847                           resStream.on("drain", afterWriteCallback);
3848                         } else {
3849                           process.nextTick(afterWriteCallback);
3850                         }
3851                       } else {
3852                         res.writeHead(200, http.STATUS_CODES[200], hdhds);
3853                         readStream.pipe(resStream);
3854                       }
3855                       serverconsole.resmessage("Client successfully received content.");
3856                     } catch (err) {
3857                       callServerError(500, err);
3858                     }
3859                   });
3860                 } else {
3861                   res.writeHead(200, http.STATUS_CODES[200], hdhds);
3862                   res.end();
3863                   serverconsole.resmessage("Client successfully received content.");
3864                 }
3865               } catch (err) {
3866                 callServerError(500, err);
3867               }
3868             }
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) {
3877                   if (err) {
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) {
3881                           if (err) {
3882                             if (err.code == "ENOENT" || err.code == "EISDIR") {
3883                               callback();
3884                             } else {
3885                               callServerError(500, err);
3886                             }
3887                           } else {
3888                             customDirListingHeader = data.toString();
3889                             callback();
3890                           }
3891                         });
3892                       } else {
3893                         callback();
3894                       }
3895                     } else {
3896                       callServerError(500, err);
3897                     }
3898                   } else {
3899                     customDirListingHeader = data.toString();
3900                     callback();
3901                   }
3902                 });
3903               }
3905               function getCustomDirListingFooter(callback) {
3906                 fs.readFile(("." + dHref + "/.dirfoot").replace(/\/+/g, "/"), function (err, data) {
3907                   if (err) {
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) {
3911                           if (err) {
3912                             if (err.code == "ENOENT" || err.code == "EISDIR") {
3913                               callback();
3914                             } else {
3915                               callServerError(500, err);
3916                             }
3917                           } else {
3918                             customDirListingFooter = data.toString();
3919                             callback();
3920                           }
3921                         });
3922                       } else {
3923                         callback();
3924                       }
3925                     } else {
3926                       callServerError(500, err);
3927                     }
3928                   } else {
3929                     customDirListingFooter = data.toString();
3930                     callback();
3931                   }
3932                 });
3933               }
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</title>")) :
3946                     head.replace(/<head>/i, "<head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</title>")) +
3947                       (!headerHasHTMLTag ? customDirListingHeader : "") +
3948                       "<h1>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</h1><table id=\"directoryListing\"> <tr> <th></th> <th>Filename</th> <th>Size</th> <th>Date</th> </tr>" + (checkPathLevel(decodeURIComponent(origHref)) < 1 ? "" : "<tr><td style=\"width: 24px;\"><img src=\"/.dirimages/return.png\" width=\"24px\" height=\"24px\" alt=\"[RET]\" /></td><td style=\"word-wrap: break-word; word-break: break-word; overflow-wrap: break-word;\"><a href=\"" + (origHref).replace(/\/+/g, "/").replace(/\/[^\/]*\/?$/, "/") + "\">Return</a></td><td></td><td></td></tr>");
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + (req.headers.host == undefined ? "" : " on " + String(req.headers.host).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) + "</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;
3954                   }
3956                   fs.readdir(readFrom, function (err, list) {
3957                     try {
3958                       if (err) throw err;
3959                       list = list.sort();
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);
3965                           return;
3966                         }
3968                         fs.stat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) {
3969                           if (err) {
3970                             fs.lstat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) {
3971                               pushArray.push({
3972                                 name: fileList[index],
3973                                 stats: err ? null : stats,
3974                                 errored: true
3975                               });
3976                               if (index < fileList.length - 1) {
3977                                 getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
3978                               } else {
3979                                 callback(pushArray);
3980                               }
3981                             });
3982                           } else {
3983                             pushArray.push({
3984                               name: fileList[index],
3985                               stats: stats,
3986                               errored: false
3987                             });
3988                             if (index < fileList.length - 1) {
3989                               getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
3990                             } else {
3991                               callback(pushArray);
3992                             }
3993                           }
3994                         });
3995                       }
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);
4001                       }
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, "/") +
4017                                   "\">" +
4018                                   ename.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") +
4019                                   "</a></td><td>-</td><td>" +
4020                                   (estats ? estats.mtime.toDateString() : "-") +
4021                                   "</td></tr>\r\n"
4022                               );
4023                             } else {
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() ? "/" : "") +
4027                                   "\">" +
4028                                   ename.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") +
4029                                   "</a></td><td>" +
4030                                   (estats.isDirectory() ? "-" : sizify(estats.size.toString())) +
4031                                   "</td><td>" +
4032                                   estats.mtime.toDateString() +
4033                                   "</td></tr>\r\n";
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, "/") +
4041                                     "\">" +
4042                                     ename.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") +
4043                                     "</a></td><td>-</td><td>" +
4044                                     estats.mtime.toDateString() +
4045                                     "</td></tr>\r\n";
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]");
4056                                 }
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]");
4081                               } else {
4082                                 entry = entry.replace("[img]", "/.dirimages/other.png").replace("[alt]", "[OTH]");
4083                               }
4084                               directoryListingRows.push(entry);
4085                             }
4086                           }
4087                         }
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>");
4092                         }
4094                         // Send the directory listing response
4095                         res.writeHead(200, http.STATUS_CODES[200], {
4096                           "Content-Type": "text/html; charset=utf-8"
4097                         });
4098                         res.end(htmlHead + directoryListingRows.join("") + htmlFoot);
4099                         serverconsole.resmessage("Client successfully received content.");
4100                       });
4102                     } catch (err) {
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.");
4119                       } else {
4120                         callServerError(500, err);
4121                       }
4122                     }
4123                   });
4124                 });
4125               });
4126             } else {
4127               // Directory listing is disabled, call 403 Forbidden error
4128               callServerError(403);
4129               serverconsole.errmessage("Directory listing is disabled.");
4130             }
4131           } else {
4132             callServerError(501);
4133             serverconsole.errmessage("SVR.JS doesn't support block devices, character devices, FIFOs nor sockets.");
4134             return;
4135           }
4136         }
4137       });
4138     }
4140     try {
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.");
4146         return;
4147       }
4149       if (req.url == "*") {
4150         // Handle "*" 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);
4156           res.end();
4157           return;
4158         } else {
4159           // SVR.JS doesn't understand that request, so throw an 400 error
4160           callServerError(400);
4161           return;
4162         }
4163       }
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.");
4169         return;
4170       }
4172       // Check for invalid X-Forwarded-For header
4173       if (!isForwardedValid) {
4174         serverconsole.errmessage("X-Forwarded-For header is invalid.");
4175         callServerError(400);
4176         return;
4177       }
4179       // Sanitize URL
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;
4198           try {
4199             uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4200           } catch (err) {
4201             // Return an 400 error
4202             callServerError(400);
4203             serverconsole.errmessage("Bad request!");
4204             return;
4205           }
4206           search = uobject.search;
4207           href = uobject.pathname;
4208           ext = href.match(/[^\/]\.([^.]+)$/);
4209           if(!ext) ext = "";
4210           else ext = ext[1].toLowerCase();
4211           try {
4212             decodedHref = decodeURIComponent(href);
4213           } catch (err) {
4214             // Return 400 error
4215             callServerError(400);
4216             serverconsole.errmessage("Bad request!");
4217             return;
4218           }
4219         } else {
4220           redirect(sanitizedURL, false);
4221           return;
4222         }
4223       } else if (req.url != preparedReqUrl && !isProxy) {
4224         serverconsole.resmessage("URL sanitized: " + req.url + " => " + preparedReqUrl);
4225         if (rewriteDirtyURLs) {
4226           req.url = preparedReqUrl;
4227         } else {
4228           redirect(preparedReqUrl, false);
4229           return;
4230         }
4231       }
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);
4239           return;
4240         }
4242         if (isProxy) {
4243           callServerError(501);
4244           serverconsole.errmessage("This server will never be a proxy.");
4245           return;
4246         }
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;
4259         } else {
4260           destinationPort = spubport;
4261         }
4263         redirect("https://" + hostname + (destinationPort == 443 ? "" : (":" + destinationPort)) + req.url);
4264         return;
4265       }
4267       // Handle redirects to addresses with "www." prefix
4268       if (wwwredirect) {
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, "/"));
4275           return;
4276         }
4277       }
4279       // Handle URL rewriting
4280       function rewriteURL(address, map, callback, _fileState, _mapBegIndex) {
4281         var rewrittenURL = address;
4282         var doCallback = true;
4283         if (!isProxy) {
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) {
4288                 var _fileState = 3;
4289                 if (err) {
4290                   _fileState = 3;
4291                 } else if (stats.isDirectory()) {
4292                   _fileState = 2;
4293                 } else if (stats.isFile()) {
4294                   _fileState = 1;
4295                 } else {
4296                   _fileState = 3;
4297                 }
4298                 rewriteURL(address, map, callback, _fileState, i);
4299               });
4300               doCallback = false;
4301               break;
4302             }
4303             var tempRewrittenURL = rewrittenURL;
4304             if (!mapEntry.allowDoubleSlashes) {
4305               address = address.replace(/\/+/g,"/");
4306               tempRewrittenURL = address;
4307             }
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;
4310               try {
4311                 mapEntry.replacements.forEach(function (replacement) {
4312                   rewrittenURL = rewrittenURL.replace(createRegex(replacement.regex), replacement.replacement);
4313                 });
4314                 if (mapEntry.append) rewrittenURL += mapEntry.append;
4315               } catch (err) {
4316                 doCallback = false;
4317                 callback(err, null);
4318               }
4319               break;
4320             }
4321           }
4322         }
4323         if (doCallback) callback(null, rewrittenURL);
4324       }
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()) {
4331               try {
4332                 callback();
4333               } catch (err) {
4334                 callServerError(500, err);
4335               }
4336             } else {
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);
4348             }
4349           });
4350         } else {
4351           callback();
4352         }
4353       }
4355       origHref = href;
4357       // Add web root postfixes
4358       if (!isProxy) {
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;
4366             else return true;
4367             urlWithPostfix = urlWithPostfix.substring(postfixPrefix.length);
4368             return false;
4369           } else {
4370             return true;
4371           }
4372         });
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;
4376             return false;
4377           } else {
4378             return true;
4379           }
4380         });
4381         if (urlWithPostfix != preparedReqUrl3) {
4382           serverconsole.resmessage("Added web root postfix: " + req.url + " => " + urlWithPostfix);
4383           req.url = urlWithPostfix;
4384           try {
4385             uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4386           } catch (err) {
4387             // Return an 400 error
4388             callServerError(400);
4389             serverconsole.errmessage("Bad request!");
4390             return;
4391           }
4392           search = uobject.search;
4393           href = uobject.pathname;
4394           ext = href.match(/[^\/]\.([^.]+)$/);
4395           if(!ext) ext = "";
4396           else ext = ext[1].toLowerCase();
4398           try {
4399             decodedHref = decodeURIComponent(href);
4400           } catch (err) {
4401             // Return 400 error
4402             callServerError(400);
4403             serverconsole.errmessage("Bad request!");
4404             return;
4405           }
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.");
4413             return;
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;
4427             try {
4428               uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4429             } catch (err) {
4430               // Return an 400 error
4431               callServerError(400);
4432               serverconsole.errmessage("Bad request!");
4433               return;
4434             }
4435             search = uobject.search;
4436             href = uobject.pathname;
4437             ext = href.match(/[^\/]\.([^.]+)$/);
4438             if(!ext) ext = "";
4439             else ext = ext[1].toLowerCase();
4440             try {
4441               decodedHref = decodeURIComponent(href);
4442             } catch (err) {
4443               // Return 400 error
4444               callServerError(400);
4445               serverconsole.errmessage("Bad request!");
4446               return;
4447             }
4448           }
4449         }
4450       }
4452       // Rewrite URLs
4453       rewriteURL(req.url, rewriteMap, function (err, rewrittenURL) {
4454         if (err) {
4455           callServerError(500, err);
4456           return;
4457         }
4458         if (rewrittenURL != req.url) {
4459           serverconsole.resmessage("URL rewritten: " + req.url + " => " + rewrittenURL);
4460           req.url = rewrittenURL;
4461           try {
4462             uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4463           } catch (err) {
4464             // Return an 400 error
4465             callServerError(400);
4466             serverconsole.errmessage("Bad request!");
4467             return;
4468           }
4469           search = uobject.search;
4470           href = uobject.pathname;
4471           ext = href.match(/[^\/]\.([^.]+)$/);
4472           if(!ext) ext = "";
4473           else ext = ext[1].toLowerCase();
4474           try {
4475             decodedHref = decodeURIComponent(href);
4476           } catch (err) {
4477             // Return 400 error
4478             callServerError(400);
4479             serverconsole.errmessage("Bad request!");
4480             return;
4481           }
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.");
4489             return;
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;
4503             try {
4504               uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4505             } catch (err) {
4506               // Return an 400 error
4507               callServerError(400);
4508               serverconsole.errmessage("Bad request!");
4509               return;
4510             }
4511             search = uobject.search;
4512             href = uobject.pathname;
4513             ext = href.match(/[^\/]\.([^.]+)$/);
4514             if(!ext) ext = "";
4515             else ext = ext[1].toLowerCase();
4516             try {
4517               decodedHref = decodeURIComponent(href);
4518             } catch (err) {
4519               // Return 400 error
4520               callServerError(400);
4521               serverconsole.errmessage("Bad request!");
4522               return;
4523             }
4524           }
4525         }
4526         // Set response headers
4527         if (!isProxy) {
4528           var hkh = getCustomHeaders();
4529           Object.keys(hkh).forEach(function (hkS) {
4530             try {
4531               res.setHeader(hkS, hkh[hkS]);
4532             } catch (err) {
4533               // Headers will not be set.
4534             }
4535           });
4536         }
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.");
4545           return;
4546         } else if (isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "temp") && !isProxy) {
4547           callServerError(403);
4548           serverconsole.errmessage("Access to temporary folder is denied.");
4549           return;
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.");
4553           return;
4554         } else if (isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") && !isProxy && !exposeServerVersion) {
4555           callServerError(403);
4556           serverconsole.errmessage("Access to SVR.JS script is denied.");
4557           return;
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.");
4561           return;
4562         } else {
4563           var nonscodeIndex = -1;
4564           var authIndex = -1;
4565           var regexI = [];
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) {
4574                   // Regex match
4575                   var createdRegex = createRegex(nonStandardCodes[i].regex, true);
4576                   isMatch = req.url.match(createdRegex) || hrefWithoutDuplicateSlashes.match(createdRegex);
4577                   regexI[i] = createdRegex;
4578                 } else {
4579                   // Non-regex match
4580                   isMatch = nonStandardCodes[i].url == hrefWithoutDuplicateSlashes || (os.platform() == "win32" && nonStandardCodes[i].url.toLowerCase() == hrefWithoutDuplicateSlashes.toLowerCase());
4581                 }
4582                 if (isMatch) {
4583                   if (nonStandardCodes[i].scode == 401) {
4584                     // HTTP authentication
4585                     if (authIndex == -1) {
4586                       authIndex = i;
4587                     }
4588                   } else {
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;
4592                       } else {
4593                         nonscodeIndex = i;
4594                       }
4595                     }
4596                   }
4597                 }
4598               }
4599             }
4600           }
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) {
4606               var location = "";
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);
4612                 }
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;
4615               } else {
4616                 location = nonscode.location + "?" + req.url.split("?")[1];
4617               }
4618               redirect(location, nonscode.scode == 302 || nonscode.scode == 307, nonscode.scode == 307 || nonscode.scode == 308);
4619               return;
4620             } else {
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 ;)");
4628               } else {
4629                 serverconsole.errmessage("Client fails receiving content.");
4630               }
4631               return;
4632             }
4633           }
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) {
4641               if (!_i) _i = 0;
4642               var cb = function (hash) {
4643                 if (hash == list[_i].pass) {
4644                   callback(true);
4645                 } else if (_i >= list.length - 1) {
4646                   callback(false);
4647                 } else {
4648                   checkIfPasswordMatches(list, password, callback, _i + 1);
4649                 }
4650               };
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."));
4656                   return;
4657                 } else {
4658                   cacheEntry = scryptCache.find(function (entry) {
4659                     return (entry.password == hashedPassword && entry.salt == list[_i].salt);
4660                   });
4661                   if (cacheEntry) {
4662                     cb(cacheEntry.hash);
4663                   } else {
4664                     crypto.scrypt(password, list[_i].salt, 64, function (err, derivedKey) {
4665                       if (err) {
4666                         callServerError(500, err);
4667                       } else {
4668                         var key = derivedKey.toString("hex");
4669                         scryptCache.push({
4670                           hash: key,
4671                           password: hashedPassword,
4672                           salt: list[_i].salt,
4673                           addDate: new Date()
4674                         });
4675                         cb(key);
4676                       }
4677                     });
4678                   }
4679                 }
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."));
4683                   return;
4684                 } else {
4685                   cacheEntry = pbkdf2Cache.find(function (entry) {
4686                     return (entry.password == hashedPassword && entry.salt == list[_i].salt);
4687                   });
4688                   if (cacheEntry) {
4689                     cb(cacheEntry.hash);
4690                   } else {
4691                     crypto.pbkdf2(password, list[_i].salt, 36250, 64, "sha512", function (err, derivedKey) {
4692                       if (err) {
4693                         callServerError(500, err);
4694                       } else {
4695                         var key = derivedKey.toString("hex");
4696                         pbkdf2Cache.push({
4697                           hash: key,
4698                           password: hashedPassword,
4699                           salt: list[_i].salt,
4700                           addDate: new Date()
4701                         });
4702                         cb(key);
4703                       }
4704                     });
4705                   }
4706                 }
4707               } else {
4708                 cb(hashedPassword);
4709               }
4710             }
4712             function authorizedCallback(bruteProtection) {
4713               try {
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"];
4717                 if (!credentials) {
4718                   callServerError(401, ha);
4719                   serverconsole.errmessage("Content needs authorization.");
4720                   return;
4721                 }
4722                 var credentialsMatch = credentials.match(/^Basic (.+)$/);
4723                 if (!credentialsMatch) {
4724                   callServerError(401, ha);
4725                   serverconsole.errmessage("Malformed credentials.");
4726                   return;
4727                 }
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.");
4733                   return;
4734                 }
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) {
4743                     if(entry.scrypt) {
4744                       scryptCount++;
4745                     } else if(entry.pbkdf2) {
4746                       pbkdf2Count++;
4747                     } else {
4748                       sha256Count++;
4749                     }
4750                     return entry.name == username;
4751                   });
4752                 }
4753                 if (usernameMatch.length == 0) {
4754                   // Pushing false user match to prevent time-based user enumeration
4755                   var fakeCredentials = {
4756                     name: username,
4757                     pass: "SVRJSAWebServerRunningOnNodeJS",
4758                     salt: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0"
4759                   };
4760                   if (!process.isBun) {
4761                     if (scryptCount > sha256Count && scryptCount > pbkdf2Count) {
4762                       fakeCredentials.scrypt = true;
4763                     } else if (pbkdf2Count > sha256Count) {
4764                       fakeCredentials.pbkdf2 = true;
4765                     }
4766                   }
4767                   usernameMatch.push(fakeCredentials);
4768                 }
4769                 checkIfPasswordMatches(usernameMatch, password, function (authorized) {
4770                   try {
4771                     if (!authorized) {
4772                       if (bruteProtection) {
4773                         if (process.send) {
4774                           process.send("\x12AUTHW" + reqip);
4775                         } else {
4776                           if (!bruteForceDb[reqip]) bruteForceDb[reqip] = {
4777                             invalidAttempts: 0
4778                           };
4779                           bruteForceDb[reqip].invalidAttempts++;
4780                           if (bruteForceDb[reqip].invalidAttempts >= 10) {
4781                             bruteForceDb[reqip].lastAttemptDate = new Date();
4782                           }
4783                         }
4784                       }
4785                       callServerError(401, ha);
4786                       serverconsole.errmessage("User \"" + String(username).replace(/[\r\n]/g, "") + "\" failed to log in.");
4787                     } else {
4788                       if (bruteProtection) {
4789                         if (process.send) {
4790                           process.send("\x12AUTHR" + reqip);
4791                         } else {
4792                           if (bruteForceDb[reqip]) bruteForceDb[reqip] = {
4793                             invalidAttempts: 0
4794                           };
4795                         }
4796                       }
4797                       serverconsole.reqmessage("Client is logged in as \"" + String(username).replace(/[\r\n]/g, "") + "\".");
4798                       authUser = username;
4799                       redirectTrailingSlashes(function () {
4800                         modExecute(mods, vres);
4801                       });
4802                     }
4803                   } catch (err) {
4804                     callServerError(500, err);
4805                     return;
4806                   }
4807                 });
4808               } catch (err) {
4809                 callServerError(500, err);
4810                 return;
4811               }
4812             }
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] = {
4820                   invalidAttempts: 5
4821                 };
4822                 authorizedCallback(true);
4823               } else {
4824                 callServerError(429);
4825                 serverconsole.errmessage("Brute force limit reached!");
4826               }
4827             } else {
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;
4836                 }
4837                 if (message == "\x14AUTHD" + reqip) {
4838                   callServerError(429);
4839                   serverconsole.errmessage("Brute force limit reached!");
4840                 } else if (message == "\x14AUTHA" + reqip) {
4841                   authorizedCallback(true);
4842                 }
4843               }
4844               process.on("message", authMessageListener);
4845               process.send("\x12AUTHQ" + reqip);
4846             }
4847           } else {
4848             redirectTrailingSlashes(function () {
4849               modExecute(mods, vres);
4850             });
4851           }
4852         }
4854       });
4855     } catch (err) {
4856       callServerError(500, err);
4857     }
4858   }
4860   function serverErrorHandler(err, isRedirect) {
4861     if(isRedirect) attmtsRedir--;
4862     else attmts--;
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.");
4866     } else {
4867       try {
4868         process.send("\x12ERRLIST" + (isRedirect ? attmtsRedir : attmts) + err.code);
4869       } catch (err) {
4870         // Probably main process exited
4871       }
4872     }
4873     if ((isRedirect ? attmtsRedir : attmts) > 0) {
4874       (isRedirect ? server2 : server).close();
4875       setTimeout(start, 900);
4876     } else {
4877       try {
4878         if (cluster.isPrimary !== undefined) process.send("\x12ERRCRASH" + err.code);
4879       } catch (err) {
4880         // Probably main process exited
4881       }
4882       setTimeout(function () {
4883         var errno = errors[err.code];
4884         process.exit(errno ? errno : 1);
4885       }, 50);
4886     }
4887   }
4889   server.on("error", function (err) {
4890     serverErrorHandler(err, false);
4891   });
4892   server.on("listening", function () {
4893     attmts = 5;
4894     listeningMessage();
4895   });
4898 var closedMaster = true;
4900 // IPC listener for server listening signalization
4901 function listenConnListener(msg) {
4902   if (msg == "\x12LISTEN") {
4903     listeningMessage();
4904   }
4907 // IPC listener for brue force protection
4908 function bruteForceListenerWrapper(worker) {
4909   return function bruteForceListener(message) {
4910     var ip = "";
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] = {
4915           invalidAttempts: 5
4916         };
4917         worker.send("\x14AUTHA" + ip);
4918       } else {
4919         worker.send("\x14AUTHD" + ip);
4920       }
4921     } else if (message.substring(0, 6) == "\x12AUTHR") {
4922       ip = message.substring(6);
4923       if (bruteForceDb[ip]) bruteForceDb[ip] = {
4924         invalidAttempts: 0
4925       };
4926     } else if (message.substring(0, 6) == "\x12AUTHW") {
4927       ip = message.substring(6);
4928       if (!bruteForceDb[ip]) bruteForceDb[ip] = {
4929         invalidAttempts: 0
4930       };
4931       bruteForceDb[ip].invalidAttempts++;
4932       if (bruteForceDb[ip].invalidAttempts >= 10) {
4933         bruteForceDb[ip].lastAttemptDate = new Date();
4934       }
4935     }
4936   };
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);
4949     }
4950   }
4951   if (msg == "\x12CLOSE") {
4952     closedMaster = true;
4953   } else if (msg == "\x12LISTEN" || msg.substring(0, 4) == "\x12AUTH") {
4954     // Do nothing!
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.");
4972       }
4973       if (msg.length >= 9 && msg.indexOf("\x12ERRCRASH") == 0) {
4974         var errno = errors[msg.substring(9)];
4975         process.exit(errno ? errno : 1);
4976       }
4977     });
4978   } else {
4979     serverconsole.climessage(msg);
4980   }
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");
4989     return;
4990   }
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}$/));
4995   var accHost = host;
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)));
5005     } else {
5006       serverconsole.locmessage("* " + sport); // Unix socket or Windows named pipe
5007     }
5008   }
5009   if (!(secure && disableNonEncryptedServer) && (listenToLocalhost || listenToAny)) {
5010     if (typeof port === "number") {
5011       serverconsole.locmessage("* http://localhost" + (port == 80 ? "" : (":" + port)));
5012     } else {
5013       serverconsole.locmessage("* " + port); // Unix socket or Windows named pipe
5014     }
5015   }
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 () {
5019     if (pubip != "") {
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)));
5022     }
5023     if (domain != "") {
5024       if (secure && !sListenToLocalhost) serverconsole.locmessage("* https://" + domain + (spubport == 443 ? "" : (":" + spubport)));
5025       if (!(secure && disableNonEncryptedServer) && !listenToLocalhost) serverconsole.locmessage("* http://" + domain + (pubport == 80 ? "" : (":" + pubport)));
5026     }
5027     serverconsole.locmessage("For CLI help, you can type \"help\"");
5028   });
5031 function start(init) {
5032   init = Boolean(init);
5033   if (cluster.isPrimary || cluster.isPrimary === undefined) {
5034     if (init) {
5035       for (i = 0; i < logo.length; i++) console.log(logo[i]); // Print logo
5036       console.log();
5037       console.log("Welcome to \x1b[1mSVR.JS - a web server running on Node.JS\x1b[0m");
5039       // Print warnings
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.");
5047       }
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.");
5057         } else {
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.");
5059         }
5060       }
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");
5063       console.log();
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));
5071         });
5072         if (SSJSError) {
5073           serverconsole.locwarnmessage("There was a problem while loading server-side JavaScript.");
5074           serverconsole.locwarnmessage("Stack:");
5075           serverconsole.locwarnmessage(generateErrorStack(SSJSError));
5076         }
5077         if (SSJSError || modLoadingErrors.length > 0) console.log();
5078       }
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);
5087       // Throw errors
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.");
5098       }
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.");
5102     }
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() + "...");
5107   }
5110   if (!cluster.isPrimary) {
5111     try {
5112       if (typeof (secure ? sport : port) == "number" && (secure ? sListenAddress : listenAddress)) {
5113         server.listen(secure ? sport : port, secure ? sListenAddress : listenAddress);
5114       } else {
5115         server.listen(secure ? sport : port);
5116       }
5117     } catch (err) {
5118       if (err.code != "ERR_SERVER_ALREADY_LISTEN") throw err;
5119     }
5120     if (secure && !disableNonEncryptedServer) {
5121       try {
5122         if (typeof port == "number" && listenAddress) {
5123           server2.listen(port, listenAddress);
5124         } else {
5125           server2.listen(port);
5126         }
5127       } catch (err) {
5128         if (err.code != "ERR_SERVER_ALREADY_LISTEN") throw err;
5129       }
5130     }
5131   }
5133   // SVR.JS commmands
5134   var commands = {
5135     close: function () {
5136       try {
5137         server.close();
5138         if (secure && !disableNonEncryptedServer) {
5139           server2.close();
5140         }
5141         if (cluster.isPrimary === undefined) serverconsole.climessage("Server closed.");
5142         else {
5143           process.send("Server closed.");
5144           process.send("\x12CLOSE");
5145         }
5146       } catch (err) {
5147         if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot close server! Reason: " + err.message);
5148         else process.send("Cannot close server! Reason: " + err.message);
5149       }
5150     },
5151     open: function () {
5152       try {
5153         if (typeof (secure ? sport : port) == "number" && (secure ? sListenAddress : listenAddress)) {
5154           server.listen(secure ? sport : port, secure ? sListenAddress : listenAddress);
5155         } else {
5156           server.listen(secure ? sport : port);
5157         }
5158         if (secure && !disableNonEncryptedServer) {
5159           if (typeof port == "number" && listenAddress) {
5160             server2.listen(port, listenAddress);
5161           } else {
5162             server2.listen(port);
5163           }
5164         }
5165         if (cluster.isPrimary === undefined) serverconsole.climessage("Server opened.");
5166         else {
5167           process.send("Server opened.");
5168         }
5169       } catch (err) {
5170         if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot open server! Reason: " + err.message);
5171         else process.send("Cannot open server! Reason: " + err.message);
5172       }
5173     },
5174     help: function () {
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(" "));
5177     },
5178     mods: function () {
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);
5184       }
5185       if (modInfos.length == 0) {
5186         if (cluster.isPrimary === undefined) serverconsole.climessage("No mods installed.");
5187         else process.send("No mods installed.");
5188       }
5189     },
5190     stop: function (retcode) {
5191       reallyExiting = true;
5192       clearInterval(passwordHashCacheIntervalId);
5193       if ((!cluster.isPrimary && cluster.isPrimary !== undefined) && server.listening) {
5194         try {
5195           server.close(function () {
5196             if (server2.listening) {
5197               try {
5198                 server2.close(function () {
5199                   if (!process.removeFakeIPC) {
5200                     if (typeof retcode == "number") {
5201                       process.exit(retcode);
5202                     } else {
5203                       process.exit(0);
5204                     }
5205                   }
5206                 });
5207               } catch (err) {
5208                 if (!process.removeFakeIPC) {
5209                   if (typeof retcode == "number") {
5210                     process.exit(retcode);
5211                   } else {
5212                     process.exit(0);
5213                   }
5214                 }
5215               }
5216             } else {
5217               if (!process.removeFakeIPC) {
5218                 if (typeof retcode == "number") {
5219                   process.exit(retcode);
5220                 } else {
5221                   process.exit(0);
5222                 }
5223               }
5224             }
5225           });
5226         } catch (err) {
5227           if (typeof retcode == "number") {
5228             process.exit(retcode);
5229           } else {
5230             process.exit(0);
5231           }
5232         }
5233         if (process.removeFakeIPC) process.removeFakeIPC();
5234       } else {
5235         if (typeof retcode == "number") {
5236           process.exit(retcode);
5237         } else {
5238           process.exit(0);
5239         }
5240       }
5241     },
5242     clear: function () {
5243       console.clear();
5244     },
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.");
5249       } else {
5250         for (var i = 0; i < ip.length; i++) {
5251           if (ip[i] != "localhost" && ip[i].indexOf(":") == -1) {
5252             ip[i] = "::ffff:" + ip[i];
5253           }
5254           if (!blocklist.check(ip[i])) {
5255             blocklist.add(ip[i]);
5256           }
5257         }
5258         if (cluster.isPrimary === undefined) serverconsole.climessage("IPs successfully blocked.");
5259         else if (!cluster.isPrimary) process.send("IPs successfully blocked.");
5260       }
5261     },
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.");
5266       } else {
5267         for (var i = 0; i < ip.length; i++) {
5268           if (ip[i].indexOf(":") == -1) {
5269             ip[i] = "::ffff:" + ip[i];
5270           }
5271           blocklist.remove(ip[i]);
5272         }
5273         if (cluster.isPrimary === undefined) serverconsole.climessage("IPs successfully unblocked.");
5274         else if (!cluster.isPrimary) process.send("IPs successfully unblocked.");
5275       }
5276     },
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.");
5280     }
5281   };
5284   if (init) {
5285     if (cluster.isPrimary === undefined) {
5286       setInterval(function () {
5287         try {
5288           saveConfig();
5289           serverconsole.locmessage("Configuration saved.");
5290         } catch (err) {
5291           throw new Error(err);
5292         }
5293       }, 300000);
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) {
5302             callback();
5303             return;
5304           }
5305           try {
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);
5313                 } else {
5314                   goodWorkers.push(allClusters[_id]);
5315                   checkWorker(callback, _id + 1);
5316                 }
5317               }, 250);
5318             } else {
5319               checkWorker(callback, _id + 1);
5320             }
5321           } catch (err) {
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);
5326             }
5327             checkWorker(callback, _id + 1);
5328           }
5329         }
5330         checkWorker(function () {
5331           var wN = Math.floor(Math.random() * goodWorkers.length); //Send a configuration saving message to a random worker.
5332           try {
5333             if (cluster.workers[goodWorkers[wN]]) {
5334               isWorkerHungUpBuff2 = true;
5335               cluster.workers[goodWorkers[wN]].on("message", msgListener);
5336               cluster.workers[goodWorkers[wN]].send("\x14SAVECONF");
5337             }
5338           } catch (err) {
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);
5343             }
5344             serverconsole.locwarnmessage("There was a problem while saving configuration file. Reason: " + err.message);
5345           }
5346         });
5347       }, 300000);
5348     }
5349     if (!cluster.isPrimary) {
5350       passwordHashCacheIntervalId = setInterval(function () {
5351         pbkdf2Cache = pbkdf2Cache.filter(function (entry) {
5352           return entry.addDate > (new Date() - 3600000);
5353         });
5354         scryptCache = scryptCache.filter(function (entry) {
5355           return entry.addDate > (new Date() - 3600000);
5356         });
5357       }, 1800000);
5358     }
5359     if (!cluster.isPrimary && cluster.isPrimary !== undefined) {
5360       process.on("message", function (line) {
5361         try {
5362           if (line == "") {
5363             // Does Nothing
5364             process.send("\x12END");
5365           } else if (line == "\x14SAVECONF") {
5366             // Save configuration file
5367             try {
5368               saveConfig();
5369               process.send("\x12SAVEGOOD");
5370             } catch (err) {
5371               process.send("\x12SAVEERR" + err.message);
5372             }
5373             process.send("\x12END");
5374           } else if (line == "\x14KILLPING") {
5375             if (!reallyExiting) {
5376               process.send("\x12KILLOK");
5377               process.send("\x12END");
5378             }
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");
5384             }
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);
5390             } else {
5391               reqcounterKillReq = reqcounter;
5392             }
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");
5398           } else {
5399             process.send("Unrecognized command \"" + line.split(" ")[0] + "\".");
5400             process.send("\x12END");
5401           }
5402         } catch (err) {
5403           if (line != "") {
5404             process.send("Can't execute command \"" + line.split(" ")[0] + "\".");
5405             process.send("\x12END");
5406           }
5407         }
5408       });
5409     } else {
5410       var rla = readline.createInterface({
5411         input: process.stdin,
5412         output: process.stdout,
5413         prompt: ""
5414       });
5415       rla.prompt();
5416       rla.on("line", function (line) {
5417         line = line.trim();
5418         var argss = line.split(" ");
5419         var command = argss.shift();
5420         if (line != "") {
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;
5427               exiting = true;
5428               for (var i = 0; i < allClusters.length; i++) {
5429                 try {
5430                   if (cluster.workers[allClusters[i]]) {
5431                     cluster.workers[allClusters[i]].kill();
5432                   }
5433                 } catch (err) {
5434                   stopError = true;
5435                 }
5436               }
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;
5442               try {
5443                 var useAvailableCores = Math.round((os.freemem()) / 50000000) - 1; // 1 core deleted for safety...
5444                 if (cpus > useAvailableCores) cpus = useAvailableCores;
5445               } catch (err) {
5446                 // Nevermind... Don't want SVR.JS to fail starting, because os.freemem function is not working.
5447               }
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++) {
5450                 if (i == 0) {
5451                   SVRJSFork();
5452                 } else {
5453                   setTimeout((function (i) {
5454                     return function () {
5455                       SVRJSFork();
5456                       if (i >= cpus - 1) {
5457                         SVRJSInitialized = true;
5458                         exiting = false;
5459                         serverconsole.climessage("SVR.JS workers restarted.");
5460                       }
5461                     };
5462                   })(i), i * 6.6);
5463                 }
5464               }
5465               return;
5466             }
5467             if (command == "stop") {
5468               exiting = true;
5469               allClusters = Object.keys(cluster.workers);
5470             }
5471             allClusters.forEach(function (clusterID) {
5472               try {
5473                 if (cluster.workers[clusterID]) {
5474                   cluster.workers[clusterID].on("message", msgListener);
5475                   cluster.workers[clusterID].send(line);
5476                 }
5477               } catch (err) {
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);
5482                 }
5483                 serverconsole.climessage("Can't run command \"" + command + "\".");
5484               }
5485             });
5486             if (command == "stop") {
5487               setTimeout(function () {
5488                 reallyExiting = true;
5489                 process.exit(0);
5490               }, 50);
5491             }
5492           } else {
5493             if (command == "stop") {
5494               reallyExiting = true;
5495               process.exit(0);
5496             }
5497             try {
5498               commands[command](argss);
5499             } catch (err) {
5500               serverconsole.climessage("Unrecognized command \"" + command + "\".");
5501             }
5502           }
5503         }
5504         rla.prompt();
5505       });
5506     }
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;
5513         try {
5514           var useAvailableCores = Math.round((os.freemem()) / 50000000) - 1; // 1 core deleted for safety...
5515           if (cpus > useAvailableCores) cpus = useAvailableCores;
5516         } catch (err) {
5517           // Nevermind... Don't want SVR.JS to fail starting, because os.freemem function is not working.
5518         }
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++) {
5521           if (i == 0) {
5522             SVRJSFork();
5523           } else {
5524             setTimeout((function (i) {
5525               return function () {
5526                 SVRJSFork();
5527                 if (i >= cpus - 1) SVRJSInitialized = true;
5528               };
5529             })(i), i * 6.6);
5530           }
5531         }
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.");
5538           }
5539           if (msg.length >= 9 && msg.indexOf("\x12ERRCRASH") == 0) {
5540             var errno = errors[msg.substring(9)];
5541             process.exit(errno ? errno : 1);
5542           }
5543         });
5545         // Hangup check and restart
5546         setInterval(function () {
5547           if (!closedMaster && !exiting) {
5548             var chksocket = {};
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,
5554                 headers: {
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")
5557                 },
5558                 timeout: 1620,
5559                 rejectUnauthorized: false
5560               }, function (res) {
5561                 chksocket.removeAllListeners("timeout");
5562                 res.destroy();
5563                 res.on("data", function () {});
5564                 res.on("end", function () {});
5565                 crashed = false;
5566               }).on("error", function () {
5567                 if (!exiting) {
5568                   if (!crashed) SVRJSFork();
5569                   else crashed = false;
5570                 }
5571               }).on("timeout", function () {
5572                 if (!exiting) SVRJSFork();
5573                 crashed = true;
5574               });
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 + "]";
5580               }
5581               var connection = http2.connect("http://" + address + ":" + port.toString());
5582               connection.on("error", function () {
5583                 if (!exiting) {
5584                   if (!crashed) SVRJSFork();
5585                   else crashed = false;
5586                 }
5587               });
5588               connection.setTimeout(1620, function () {
5589                 if (!exiting) SVRJSFork();
5590                 crashed = true;
5591               });
5592               chksocket = connection.request({
5593                 ":path": "/",
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")
5596               });
5597               chksocket.on("response", function () {
5598                 connection.close();
5599                 crashed = false;
5600               });
5601               chksocket.on("error", function () {
5602                 if (!exiting) {
5603                   if (!crashed) SVRJSFork();
5604                   else crashed = false;
5605                 }
5606               });
5607             } else {
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,
5612                 headers: {
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")
5615                 },
5616                 timeout: 1620
5617               }, function (res) {
5618                 chksocket.removeAllListeners("timeout");
5619                 res.destroy();
5620                 res.on("data", function () {});
5621                 res.on("end", function () {});
5622                 crashed = false;
5623               }).on("error", function () {
5624                 if (!exiting) {
5625                   if (!crashed) SVRJSFork();
5626                   else crashed = false;
5627                 }
5628               }).on("timeout", function () {
5629                 if (!exiting) SVRJSFork();
5630                 crashed = true;
5631               });
5632             }
5633           }
5634         }, 4550);
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) {
5650                     callback();
5651                     return;
5652                   }
5653                   try {
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);
5661                         } else {
5662                           goodWorkers.push(allClusters[_id]);
5663                           checkWorker(callback, _id + 1);
5664                         }
5665                       }, 250);
5666                     } else {
5667                       checkWorker(callback, _id + 1);
5668                     }
5669                   } catch (err) {
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);
5674                     }
5675                     checkWorker(callback, _id + 1);
5676                   }
5677                 }
5678                 checkWorker(function () {
5679                   if (goodWorkers.length > minClusters) {
5680                     var wN = Math.floor(Math.random() * goodWorkers.length);
5681                     if (wN == goodWorkers.length) return;
5682                     try {
5683                       if (cluster.workers[goodWorkers[wN]]) {
5684                         isWorkerHungUpBuff = true;
5685                         cluster.workers[goodWorkers[wN]].on("message", msgListener);
5686                         cluster.workers[goodWorkers[wN]].send("\x14KILLREQ");
5687                       }
5688                     } catch (err) {
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);
5693                       }
5694                       serverconsole.locwarnmessage("There was a problem while terminating unused worker process. Reason: " + err.message);
5695                     }
5696                   }
5697                 });
5698               }
5699             }, 300000);
5700           }, 2000);
5701         }
5702       }
5703     }
5704   }
5707 // Save configuration file
5708 function saveConfig() {
5709   for (var i = 0; i < 3; i++) {
5710     try {
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 = [];
5714       if (secure) {
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;
5721       }
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);
5760       break;
5761     } catch (err) {
5762       if (i >= 2) throw err;
5763       var now = Date.now();
5764       while (Date.now() - now < 2);
5765     }
5766   }
5769 // Process event listeners
5770 if (cluster.isPrimary || cluster.isPrimary === undefined) {
5771   process.on("uncaughtException", function (err) {
5772     // CRASH HANDLER
5773     serverconsole.locerrmessage("SVR.JS master process just crashed!!!");
5774     serverconsole.locerrmessage("Stack:");
5775     serverconsole.locerrmessage(generateErrorStack(err));
5776     process.exit(err.errno);
5777   });
5778   process.on("unhandledRejection", function (err) {
5779     // CRASH HANDLER
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);
5784   });
5785   process.on("exit", function (code) {
5786     try {
5787       if (!configJSONRErr && !configJSONPErr) {
5788         saveConfig();
5789       }
5790     } catch (err) {
5791       serverconsole.locwarnmessage("There was a problem while saving configuration file. Reason: " + err.message);
5792     }
5793     try {
5794       deleteFolderRecursive(__dirname + "/temp");
5795     } catch (err) {
5796       // Error!
5797     }
5798     try {
5799       fs.mkdirSync(__dirname + "/temp");
5800     } catch (err) {
5801       // Error!
5802     }
5803     if (process.isBun && process.versions.bun && process.versions.bun[0] == "0") {
5804       try {
5805         fs.writeFileSync(__dirname + "/temp/serverSideScript.js", "// Placeholder server-side JavaScript to workaround Bun bug.\r\n");
5806       } catch (err) {
5807         // Error!
5808       }
5809     }
5810     serverconsole.locmessage("Server closed with exit code: " + code);
5811   });
5812   process.on("warning", function (warning) {
5813     serverconsole.locwarnmessage(warning.message);
5814     if (generateErrorStack(warning)) {
5815       serverconsole.locwarnmessage("Stack:");
5816       serverconsole.locwarnmessage(generateErrorStack(warning));
5817     }
5818   });
5819   process.on("SIGINT", function () {
5820     reallyExiting = true;
5821     if (cluster.isPrimary !== undefined) {
5822       exiting = true;
5823       var allClusters = Object.keys(cluster.workers);
5824       for (var i = 0; i < allClusters.length; i++) {
5825         try {
5826           if (cluster.workers[allClusters[i]]) {
5827             cluster.workers[allClusters[i]].send("stop");
5828           }
5829         } catch (err) {
5830           // Worker will crash with EPIPE anyway.
5831         }
5832       }
5833     }
5834     serverconsole.locmessage("Server terminated using SIGINT");
5835     process.exit();
5836   });
5837 } else {
5838   // Crash handler
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);
5844   }
5846   process.on("uncaughtException", crashHandler);
5847   process.on("unhandledRejection", crashHandler);
5849   // Warning handler
5850   process.on("warning", function (warning) {
5851     serverconsole.locwarnmessage(warning.message);
5852     if (warning.stack) {
5853       serverconsole.locwarnmessage("Stack:");
5854       serverconsole.locwarnmessage(generateErrorStack(warning));
5855     }
5856   });
5859 // Start SVR.JS!
5860 try {
5861   start(true);
5862 } catch (err) {
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);
5868   }, 10);