]> SVR.JS Git server - svrjs.git/blob - svr.js
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 }
34 }
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);
55 }
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 }
69 }
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);
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!
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);
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 };
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;
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);
227 };
229 try {
230 worker.send(undefined);
231 } catch (err) {
232 if (err.message === "NOT IMPLEMENTED") {
233 sendImplemented = false;
235 console.log(err);
238 console.log = oldLog;
241 return sendImplemented;
244 if (!checkSendImplementation(newWorker)) {
245 var net = require("net");
246 var os = require("os");
248 // Create a fake IPC server for worker process to receive messages
249 var fakeWorkerIPCServer = net.createServer(function (socket) {
250 var receivedData = "";
252 socket.on("data", function (data) {
253 receivedData += data.toString();
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);
280 };
281 } else {
282 newWorker.on("exit", function () {
283 delete cluster.workers[newWorker.id];
284 });
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();
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.");
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.");
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.");
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.");
353 } else {
354 throw err;
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();
368 });
369 newWorker.on("message", bruteForceListenerWrapper(newWorker));
370 newWorker.on("message", listenConnListener);
374 var http = require("http");
375 http.STATUS_CODES[497] = "HTTP Request Sent to HTTPS Port";
376 http.STATUS_CODES[598] = "Network Read Timeout Error";
377 http.STATUS_CODES[599] = "Network Connect Timeout Error";
378 var url = require("url");
379 var dns = require("dns");
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;
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");
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();
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;
534 function createRegex(regex, isPath) {
535 var regexStrMatch = regex.match(/^\/((?:\\.|[^\/\\])*)\/([a-zA-Z0-9]*)$/);
536 if (!regexStrMatch) throw new Error("Invalid regular expression: " + regex);
537 var searchString = regexStrMatch[1];
538 var modifiers = regexStrMatch[2];
539 if (isPath && !modifiers.match(/i/i) && os.platform() == "win32") modifiers += "i";
540 return new RegExp(searchString, modifiers);
543 // Function to check if IPs are equal
544 function ipMatch(IP1, IP2) {
545 if (!IP1) return true;
546 if (!IP2) return false;
548 // Function to normalize IPv4 address (remove leading zeros)
549 function normalizeIPv4Address(address) {
550 return address.replace(/(^|\.)(?:0(?!\.|$))+/g, "");
553 // Function to expand IPv6 address to full format
554 function expandIPv6Address(address) {
555 var fullAddress = "";
556 var expandedAddress = "";
557 var validGroupCount = 8;
558 var validGroupSize = 4;
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 ? ":" : "");
569 address = address.replace(extractIpv4, ipv4);
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);
584 fullAddress += sides[1];
586 var groups = fullAddress.split(":");
587 for (var i = 0; i < validGroupCount; i++) {
588 if (groups[i].length < validGroupSize) {
589 groups[i] = "0".repeat(validGroupSize - groups[i].length) + groups[i];
591 expandedAddress += (i != validGroupCount - 1) ? groups[i] + ":" : groups[i];
593 return expandedAddress;
596 // Normalize or expand IP addresses
597 IP1 = IP1.toLowerCase();
598 if (IP1 == "localhost") IP1 = "::1";
599 if (IP1.indexOf("::ffff:") == 0) IP1 = IP1.substring(7);
600 if (IP1.indexOf(":") > -1) {
601 IP1 = expandIPv6Address(IP1);
602 } else {
603 IP1 = normalizeIPv4Address(IP1);
606 IP2 = IP2.toLowerCase();
607 if (IP2 == "localhost") IP2 = "::1";
608 if (IP2.indexOf("::ffff:") == 0) IP2 = IP2.substring(7);
609 if (IP2.indexOf(":") > -1) {
610 IP2 = expandIPv6Address(IP2);
611 } else {
612 IP2 = normalizeIPv4Address(IP2);
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;
629 } else if (hostname && hostname == hostnameM) {
630 return true;
632 return false;
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;
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, "");
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 ? ":" : "");
683 address = address.replace(extractIpv4, ipv4);
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);
698 fullAddress += sides[1];
700 var groups = fullAddress.split(":");
701 for (var i = 0; i < validGroupCount; i++) {
702 if (groups[i].length < validGroupSize) {
703 groups[i] = "0".repeat(validGroupSize - groups[i].length) + groups[i];
705 expandedAddress += (i != validGroupCount - 1) ? groups[i] + ":" : groups[i];
707 return expandedAddress;
710 // Convert IPv4 address to an integer representation
711 function ipv4ToInt(ip) {
712 var ips = ip.split(".");
713 return parseInt(ips[0]) * 16777216 + parseInt(ips[1]) * 65536 + parseInt(ips[2]) * 256 + parseInt(ips[3]);
716 // Get IPv4 CIDR block limits (min and max)
717 function getIPv4CIDRLimits(ip, cidrMask) {
718 var ipInt = ipv4ToInt(ip);
719 var exp = Math.pow(2, 32 - cidrMask);
720 var ipMin = Math.floor(ipInt / exp) * exp;
721 var ipMax = ipMin + exp - 1;
722 return {
723 min: ipMin,
724 max: ipMax
725 };
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;
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);
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);
751 return {
752 min: ipBlockMin,
753 max: ipBlockMax
754 };
757 // Check if the IPv4 address matches the given CIDR block
758 function checkIfIPv4CIDRMatches(ipInt, cidrObject) {
759 if (cidrObject.v6) return false;
760 return ipInt >= cidrObject.min && ipInt <= cidrObject.max;
763 // Check if the IPv6 address matches the given CIDR block
764 function checkIfIPv6CIDRMatches(ipBlock, cidrObject) {
765 if (!cidrObject.v6) return false;
766 for (var i = 0; i < 8; i++) {
767 if (ipBlock[i] < cidrObject.min[i] || ipBlock[i] > cidrObject.max[i]) return true;
769 return false;
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("/");
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);
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;
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 });
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);
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);
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;
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 + ")"))));
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();
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;
978 if (alias >= 1) {
979 ips.push(ifname + ":" + alias, iface.address);
980 } else {
981 ips.push(ifname, iface.address);
983 brdIPs.push(calculateBroadcastIPv4FromCidr(iface.cidr));
984 netIPs.push(calculateNetworkIPv4FromCidr(iface.cidr));
985 alias++;
986 });
987 });
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;
996 if (alias >= 1) {
997 ips.push(ifname + ":" + alias, iface.address);
998 } else {
999 ips.push(ifname, iface.address);
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;
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");
1066 });
1067 });
1068 ipRequest.on("error", function () {
1069 if (crypto.__disabled__ || ipRequestGotError) {
1070 ipRequestCompleted = true;
1071 process.emit("ipRequestCompleted");
1072 } else {
1073 ipRequestGotError = true;
1075 });
1076 ipRequest.on("timeout", function () {
1077 if (crypto.__disabled__ || ipRequestGotError) {
1078 ipRequestCompleted = true;
1079 process.emit("ipRequestCompleted");
1080 } else {
1081 ipRequestGotError = true;
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;
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");
1130 });
1131 });
1132 ipRequest2.on("error", function () {
1133 if (crypto.__disabled__ || ipRequestGotError) {
1134 ipRequestCompleted = true;
1135 process.emit("ipRequestCompleted");
1136 } else {
1137 ipRequestGotError = true;
1139 });
1140 ipRequest2.on("timeout", function () {
1141 if (crypto.__disabled__ || ipRequestGotError) {
1142 ipRequestCompleted = true;
1143 process.emit("ipRequestCompleted");
1144 } else {
1145 ipRequestGotError = true;
1147 });
1149 } else {
1150 ipRequestCompleted = true;
1153 function ipStatusCallback(callback) {
1154 if (ipRequestCompleted) {
1155 callback();
1156 } else {
1157 process.once("ipRequestCompleted", callback);
1161 var configJSON = {};
1162 var configJSONRErr = undefined;
1163 var configJSONPErr = undefined;
1164 if (fs.existsSync(__dirname + "/config.json")) {
1165 var configJSONf = "";
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;
1173 } catch (err) {
1174 configJSONRErr = err2;
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]);
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]);
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);
1293 });
1294 nonStandardCodes.push(newObject);
1295 });
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, "");
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);
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];
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] : "";
1413 });
1414 uobject.query = qobject;
1415 } else {
1416 uobject.query = Object.create(null);
1418 if (parsedURI[8]) uobject.hash = parsedURI[8];
1419 if (uobject.pathname) uobject.path = uobject.pathname + (uobject.search ? uobject.search : "");
1420 uobject.href = (uobject.protocol ? (uobject.protocol + (uobject.slashes ? "//" : "")) : "") + (uobject.auth ? (uobject.auth + "@") : "") + (uobject.hostname ? uobject.hostname : "") + (uobject.path ? uobject.path : "") + (uobject.hash ? uobject.hash : "");
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);
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;
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;
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 });
1506 if (logFile.writable) {
1507 logFile.write("[" + new Date().toISOString() + "] " + s + "\r\n");
1508 } else {
1509 throw new Error("Log file stream is closed.");
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);
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;
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;
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;
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;
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;
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;
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;
1593 console.log("\x1b[1mSERVER MESSAGE\x1b[22m: " + msg);
1594 LOG("SERVER MESSAGE: " + msg);
1595 return;
1597 };
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);
1616 }, 50); // Interval
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);
1628 } else {
1629 logSync = true;
1630 process.unsafeExit(code);
1632 };
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);
1647 // Define the temporary server-side JavaScript file name
1648 var tempServerSideScriptName = "serverSideScript.js";
1649 if (!(process.isBun && process.versions.bun && process.versions.bun[0] == "0") && cluster.isPrimary === false) {
1650 // If not the master process and it's not Bun, create a unique temporary server-side JavaScript file name for each worker
1651 tempServerSideScriptName = ".serverSideScript_w" + Math.floor(Math.random() * 65536) + ".js";
1654 // Iterate through the list of mod files
1655 modFiles.forEach(function (modFileRaw) {
1656 // Build the path to the current mod file
1657 var modFile = __dirname + "/mods/" + modFileRaw;
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;
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.
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.");
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
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 });
1737 // Wait for a short time before retrying
1738 var now = Date.now();
1739 while (Date.now() - now < 2);
1740 // Try reloading mod info
1743 } catch (err) {
1744 modLoadingErrors.push({
1745 error: err,
1746 modName: modFileRaw
1747 });
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
1784 // Add the loaded server side script to the mods list
1785 mods.push(amod);
1786 } catch (err) {
1787 SSJSError = err;
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);
1809 function S(X, n) {
1810 return (X >>> n) | (X << (32 - n));
1813 function R(X, n) {
1814 return (X >>> n);
1817 function Ch(x, y, z) {
1818 return ((x & y) ^ ((~x) & z));
1821 function Maj(x, y, z) {
1822 return ((x & y) ^ (x & z) ^ (y & z));
1825 function Sigma0256(x) {
1826 return (S(x, 2) ^ S(x, 13) ^ S(x, 22));
1829 function Sigma1256(x) {
1830 return (S(x, 6) ^ S(x, 11) ^ S(x, 25));
1833 function Gamma0256(x) {
1834 return (S(x, 7) ^ S(x, 18) ^ R(x, 3));
1837 function Gamma1256(x) {
1838 return (S(x, 17) ^ S(x, 19) ^ R(x, 10));
1841 function coreSha256(m, l) {
1842 var K = new Array(0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2);
1843 var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
1844 var W = new Array(64);
1845 var a, b, c, d, e, f, g, h, i, j;
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);
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]);
1887 return HASH;
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);
1896 return bin;
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);
1920 return utftext;
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);
1930 return str;
1933 s = Utf8Encode(s);
1934 return binb2hex(coreSha256(str2binb(s), s.length * chrsz));
1938 // Function to get URL path for use in forbidden path adding.
1939 function getInitializePath(to) {
1940 var cwd = process.cwd();
1941 if (os.platform() == "win32") {
1942 to = to.replace(/\//g, "\\");
1943 if (to[0] == "\\") to = cwd.split("\\")[0] + to;
1945 var absoluteTo = path.isAbsolute(to) ? to : (__dirname + (os.platform() == "win32" ? "\\" : "/") + to);
1946 if (os.platform() == "win32" && cwd[0] != absoluteTo[0]) return "";
1947 var relative = path.relative(cwd, absoluteTo);
1948 if (os.platform() == "win32") {
1949 return "/" + relative.replace(/\\/g, "/");
1950 } else {
1951 return "/" + relative;
1955 // Function to check if URL path name is a forbidden path.
1956 function isForbiddenPath(decodedHref, match) {
1957 var forbiddenPath = forbiddenPaths[match];
1958 if (!forbiddenPath) return false;
1959 if (typeof forbiddenPath === "string") {
1960 return decodedHref === forbiddenPath || (os.platform() === "win32" && decodedHref.toLowerCase() === forbiddenPath.toLowerCase());
1962 if (typeof forbiddenPath === "object") {
1963 return forbiddenPath.some(function (forbiddenPathSingle) {
1964 return (decodedHref === forbiddenPathSingle || (os.platform() === "win32" && decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase()));
1965 });
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));
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 });
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."
2060 };
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."
2083 };
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();
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;
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;
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;
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;
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;
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;
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;
2187 console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2188 LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2189 return;
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);
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 });
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();
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...
2278 });
2279 } catch (err) {
2280 // SNI error
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.
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.
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 });
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;
2369 socket.write(x);
2370 };
2371 res.end = function (x) {
2372 if (err.code === "ECONNRESET" || !socket.writable) {
2373 return;
2375 socket.end(x, function () {
2376 try {
2377 socket.destroy();
2378 } catch (err) {
2379 // Socket is probably already destroyed
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]);
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;
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;
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;
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;
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;
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;
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;
2483 console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2484 LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2485 return;
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();
2504 // Server error calling method
2505 function callServerError(errorCode, extName, stack, ch) {
2506 if (typeof errorCode !== "number") {
2507 throw new TypeError("HTTP error code parameter needs to be an integer.");
2510 // Handle optional parameters
2511 if (extName && typeof extName === "object") {
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.");
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.");
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;
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());
2547 } catch (err2) {
2548 callServerError(500, err2);
2550 });
2551 } else {
2552 try {
2553 callback(page404);
2554 } catch (err2) {
2555 callServerError(500, err2);
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());
2567 } catch (err2) {
2568 callServerError(500, err2);
2570 });
2575 if (!_i) _i = 0;
2576 if (_i >= list.length) {
2577 medCallback(false);
2578 return;
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);
2591 });
2595 getErrorFileName(errorPages, function (errorFile) {
2596 if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
2597 if (stack === undefined) stack = generateErrorStack(new Error("Unknown error"));
2598 if (errorCode == 500 || errorCode == 502) {
2599 serverconsole.errmessage("There was an error while processing the request!");
2600 serverconsole.errmessage("Stack:");
2601 serverconsole.errmessage(stack);
2603 if (stackHidden) stack = "[error stack hidden]";
2604 if (serverHTTPErrorDescs[errorCode] === undefined) {
2605 callServerError(501, extName, stack);
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;
2618 if (ch[chnS]) cheaders[nhn] = ch[chnS];
2619 });
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;
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();
2653 });
2656 });
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;
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;
2686 if (err.code && err.code == "ERR_HTTP_REQUEST_TIMEOUT") {
2687 serverconsole.errmessage("Client timed out.");
2688 callServerError(408);
2689 return;
2692 if (!err.rawPacket) {
2693 serverconsole.errmessage("Connection ended prematurely.");
2694 callServerError(400);
2695 return;
2698 var packetLines = err.rawPacket.toString().split("\r\n");
2699 if (packetLines.length == 0) {
2700 serverconsole.errmessage("Invalid request.");
2701 callServerError(400);
2702 return;
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;
2719 return false;
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;
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
2752 } catch (err) {
2753 serverconsole.errmessage("There was an error while determining type of malformed request.");
2754 callServerError(400);
2758 function connhandler(request, socket, head) {
2759 var reqIdInt = Math.floor(Math.random() * 16777216);
2760 if (reqIdInt == 16777216) reqIdInt = 0;
2761 var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
2762 var serverconsole = {
2763 climessage: function (msg) {
2764 if (msg.indexOf("\n") != -1) {
2765 msg.split("\n").forEach(function (nmsg) {
2766 serverconsole.climessage(nmsg);
2767 });
2768 return;
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;
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;
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;
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;
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;
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;
2836 console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2837 LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2838 return;
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();
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");
2872 modExecute(mods, vres);
2875 function reqhandler(req, res, fromMain) {
2876 if (fromMain === undefined) fromMain = true;
2877 var reqIdInt = Math.floor(Math.random() * 16777216);
2878 if (reqIdInt == 16777216) reqIdInt = 0;
2879 var reqId = "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
2880 var serverconsole = {
2881 climessage: function (msg) {
2882 if (msg.indexOf("\n") != -1) {
2883 msg.split("\n").forEach(function (nmsg) {
2884 serverconsole.climessage(nmsg);
2885 });
2886 return;
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;
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;
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;
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;
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;
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;
2954 console.log("\x1b[1mSERVER MESSAGE\x1b[22m [Request Id: " + reqId + "]: " + msg);
2955 LOG("SERVER MESSAGE [Request Id: " + reqId + "]: " + msg);
2956 return;
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;
2968 } else if (req.headers.host && req.headers.host == hostname) {
2969 return true;
2971 return false;
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;
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 });
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;
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);
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);
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;
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;
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.");
3068 if (typeof codeDescription != "string" && http.STATUS_CODES[code]) {
3069 if (!headers) headers = codeDescription;
3070 codeDescription = http.STATUS_CODES[code];
3072 lastStatusCode = code;
3074 res.writeHeadNative(code, codeDescription, headers);
3075 };
3077 var finished = false;
3078 res.on("finish", function () {
3079 if (!finished) {
3080 finished = true;
3081 serverconsole.locmessage("Client disconnected.");
3083 });
3084 res.on("close", function () {
3085 if (!finished) {
3086 finished = true;
3087 serverconsole.locmessage("Client disconnected.");
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;
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
3127 } else {
3128 isForwardedValid = false;
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, "");
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();
3159 // Server error calling method
3160 function callServerError(errorCode, extName, stack, ch) {
3161 if (typeof errorCode !== "number") {
3162 throw new TypeError("HTTP error code parameter needs to be an integer.");
3165 // Handle optional parameters
3166 if (extName && typeof extName === "object") {
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.");
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.");
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());
3196 } catch (err2) {
3197 callServerError(500, err2);
3199 });
3200 } else {
3201 try {
3202 callback(page404);
3203 } catch (err2) {
3204 callServerError(500, err2);
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());
3216 } catch (err2) {
3217 callServerError(500, err2);
3219 });
3224 if (!_i) _i = 0;
3225 if (_i >= list.length) {
3226 medCallback(false);
3227 return;
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);
3240 });
3244 getErrorFileName(errorPages, function (errorFile) {
3245 // Generate error stack if not provided
3246 if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack);
3247 if (stack === undefined) stack = generateErrorStack(new Error("Unknown error"));
3249 if (errorCode == 500 || errorCode == 502) {
3250 serverconsole.errmessage("There was an error while processing the request!");
3251 serverconsole.errmessage("Stack:");
3252 serverconsole.errmessage(stack);
3255 // Hide the error stack if specified
3256 if (stackHidden) stack = "[error stack hidden]";
3258 // Validate the error code and handle unknown codes
3259 if (serverHTTPErrorDescs[errorCode] === undefined) {
3260 callServerError(501, extName, stack);
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;
3275 if (ch[chnS]) cheaders[nhn] = ch[chnS];
3276 });
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;
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();
3311 });
3313 });
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);
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;
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;
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 = {};
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;
3390 // Otherwise, call the provided callback function with the parsed fields and files
3391 callback(fields, files);
3392 });
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;
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;
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;
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 });
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();
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;
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--;
3479 // If the component is not "." or an empty string, increment the levelDownCount
3480 else if ("." !== pathComponents[i] && "" !== pathComponents[i]) {
3481 levelDownCount++;
3485 // Calculate the overall level by subtracting levelUpCount from levelDownCount
3486 var overallLevel = levelDownCount - levelUpCount;
3488 // Return the overall level
3489 return overallLevel;
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;
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;
3515 if (allowStatus && (href == "/svrjsstatus.svr" || (os.platform() == "win32" && href.toLowerCase() == "/svrjsstatus.svr"))) {
3516 function formatRelativeTime(relativeTime) {
3517 var days = Math.floor(relativeTime / 60 / (60 * 24));
3518 var dateDiff = new Date(relativeTime * 1000);
3519 return days + " days, " + dateDiff.getUTCHours() + " hours, " + dateDiff.getUTCMinutes() + " minutes, " + dateDiff.getUTCSeconds() + " seconds";
3521 var statusBody = "";
3522 statusBody += "Server version: " + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&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;
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;
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;
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();
3597 });
3598 } else {
3599 stats = s;
3600 ext = "htm";
3601 readFrom = (readFrom + "/index.htm").replace(/\/+/g, "/");
3602 properDirectoryListingAndStaticFileServe();
3604 });
3605 } else {
3606 stats = s;
3607 ext = "html";
3608 readFrom = (readFrom + "/index.html").replace(/\/+/g, "/");
3609 properDirectoryListingAndStaticFileServe();
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();
3620 });
3621 } else {
3622 properDirectoryListingAndStaticFileServe();
3625 function properDirectoryListingAndStaticFileServe() {
3626 if (stats.isFile()) {
3627 var acceptEncoding = req.headers["accept-encoding"];
3628 if (!acceptEncoding) acceptEncoding = "";
3630 var filelen = stats.size;
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;
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;
3656 // Handle partial content request
3657 if (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 maxEnd = filelen - 1 + (ext == "html" ? head.length + foot.length : 0)
3670 var begin = 0;
3671 var end = maxEnd;
3672 if (beginOrig == "" && endOrig == "") {
3673 callServerError(416, rhd);
3674 return;
3675 } else if (beginOrig == "") {
3676 begin = end - parseInt(endOrig) + 1;
3677 } else {
3678 begin = parseInt(beginOrig);
3679 if (endOrig != "") end = parseInt(endOrig);
3681 if (begin > end || begin < 0 || begin > maxEnd) {
3682 callServerError(416, rhd);
3683 return;
3685 if (end > maxEnd) end = maxEnd;
3686 rhd["Content-Range"] = "bytes " + begin + "-" + end + "/" + filelen;
3687 rhd["Content-Length"] = end - begin + 1;
3688 delete rhd["Content-Type"];
3689 var mtype = mime.contentType(ext);
3690 if (mtype && ext != "") rhd["Content-Type"] = mtype;
3691 if (fileETag) rhd["ETag"] = fileETag;
3693 if (req.method != "HEAD") {
3694 if (ext == "html" && begin < head.length && (end - begin) < head.length) {
3695 res.writeHead(206, http.STATUS_CODES[206], hdhds);
3696 res.end(head.substring(begin, end + 1));
3697 return;
3698 } else if (ext == "html" && begin >= head.length + filelen){
3699 res.writeHead(206, http.STATUS_CODES[206], hdhds);
3700 res.end(foot.substring(begin - head.length - filelen, end - head.length - filelen + 1));
3701 return;
3703 var readStream = fs.createReadStream(readFrom, {
3704 start: ext == "html" ? Math.max(0, begin - head.length) : begin,
3705 end: ext == "html" ? Math.min(filelen, end - head.length) : end
3706 });
3707 readStream.on("error", function (err) {
3708 if (err.code == "ENOENT") {
3709 callServerError(404);
3710 serverconsole.errmessage("Resource not found.");
3711 } else if (err.code == "ENOTDIR") {
3712 callServerError(404); // Assume that file doesn't exist.
3713 serverconsole.errmessage("Resource not found.");
3714 } else if (err.code == "EACCES") {
3715 callServerError(403);
3716 serverconsole.errmessage("Access denied.");
3717 } else if (err.code == "ENAMETOOLONG") {
3718 callServerError(414);
3719 } else if (err.code == "EMFILE") {
3720 callServerError(503);
3721 } else if (err.code == "ELOOP") {
3722 callServerError(508); // The symbolic link loop is detected during file system operations.
3723 serverconsole.errmessage("Symbolic link loop detected.");
3724 } else {
3725 callServerError(500, err);
3727 }).on("open", function () {
3728 try {
3729 if (ext == "html") {
3730 function afterWriteCallback() {
3731 if(foot.length > 0 && end > head.length + filelen) {
3732 readStream.on("end", function () {
3733 res.end(foot.substring(0, end - head.length - filelen + 1));
3734 });
3736 readStream.pipe(res, {
3737 end: !(foot.length > 0 && end > head.length + filelen)
3738 });
3740 res.writeHead(206, http.STATUS_CODES[206], hdhds);
3741 if (head.length == 0 || begin > head.length) {
3742 afterWriteCallback();
3743 } else if (!res.write(head.substring(begin, head.length - begin))) {
3744 res.on("drain", afterWriteCallback);
3745 } else {
3746 process.nextTick(afterWriteCallback);
3748 } else {
3749 res.writeHead(206, http.STATUS_CODES[206], rhd);
3750 readStream.pipe(res);
3752 serverconsole.resmessage("Client successfully received content.");
3753 } catch (err) {
3754 callServerError(500, err);
3756 });
3757 } else {
3758 res.writeHead(206, http.STATUS_CODES[206], rhd);
3759 res.end();
3762 } catch (err) {
3763 callServerError(500, err);
3765 } else {
3766 // Helper function to check if compression is allowed for the file
3767 function canCompress(path, list) {
3768 var canCompress = true;
3769 for (var i = 0; i < list.length; i++) {
3770 if (createRegex(list[i], true).test(path)) {
3771 canCompress = false;
3772 break;
3775 return canCompress;
3778 var useBrotli = (ext != "br" && filelen > 256 && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/));
3779 var useDeflate = (ext != "zip" && filelen > 256 && acceptEncoding.match(/\bdeflate\b/));
3780 var useGzip = (ext != "gz" && filelen > 256 && acceptEncoding.match(/\bgzip\b/));
3782 var isCompressable = true;
3783 try {
3784 // Check for files not to compressed and compression enabling setting. Also check for browser quirks and adjust compression accordingly
3785 if((!useBrotli && !useDeflate && !useGzip) || configJSON.enableCompression !== true || !canCompress(href, dontCompress)) {
3786 isCompressable = false; // Compression is disabled
3787 } else if (ext != "html" && ext != "htm" && ext != "xhtml" && ext != "xht" && ext != "shtml") {
3788 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"]))) {
3789 isCompressable = false; // Netscape 4.x doesn't handle compressed data properly outside of HTML documents.
3790 } else if (/^w3m\/[^ ]*$/.test(req.headers["user-agent"])) {
3791 isCompressable = false; // w3m doesn't handle compressed data properly outside of HTML documents.
3793 } else {
3794 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"]))) {
3795 isCompressable = false; // Netscape 4.06-4.08 doesn't handle compressed data properly.
3798 } catch (err) {
3799 callServerError(500, err);
3800 return;
3803 // Bun 1.1 has definition for zlib.createBrotliCompress, but throws an error while invoking the function.
3804 if (process.isBun && useBrotli && isCompressable) {
3805 try {
3806 zlib.createBrotliCompress();
3807 } catch (err) {
3808 useBrotli = false;
3812 try {
3813 var hdhds = {};
3814 if (useBrotli && isCompressable) {
3815 hdhds["Content-Encoding"] = "br";
3816 } else if (useDeflate && isCompressable) {
3817 hdhds["Content-Encoding"] = "deflate";
3818 } else if (useGzip && isCompressable) {
3819 hdhds["Content-Encoding"] = "gzip";
3820 } else {
3821 if (ext == "html") {
3822 hdhds["Content-Length"] = head.length + filelen + foot.length;
3823 } else {
3824 hdhds["Content-Length"] = filelen;
3827 hdhds["Accept-Ranges"] = "bytes";
3828 delete hdhds["Content-Type"];
3829 var mtype = mime.contentType(ext);
3830 if (mtype && ext != "") hdhds["Content-Type"] = mtype;
3831 if (fileETag) hdhds["ETag"] = fileETag;
3833 if (req.method != "HEAD") {
3834 var readStream = fs.createReadStream(readFrom);
3835 readStream.on("error", function (err) {
3836 if (err.code == "ENOENT") {
3837 callServerError(404);
3838 serverconsole.errmessage("Resource not found.");
3839 } else if (err.code == "ENOTDIR") {
3840 callServerError(404); // Assume that file doesn't exist.
3841 serverconsole.errmessage("Resource not found.");
3842 } else if (err.code == "EACCES") {
3843 callServerError(403);
3844 serverconsole.errmessage("Access denied.");
3845 } else if (err.code == "ENAMETOOLONG") {
3846 callServerError(414);
3847 } else if (err.code == "EMFILE") {
3848 callServerError(503);
3849 } else if (err.code == "ELOOP") {
3850 callServerError(508); // The symbolic link loop is detected during file system operations.
3851 serverconsole.errmessage("Symbolic link loop detected.");
3852 } else {
3853 callServerError(500, err);
3855 }).on("open", function () {
3856 try {
3857 var resStream = {};
3858 if (useBrotli && isCompressable) {
3859 resStream = zlib.createBrotliCompress();
3860 resStream.pipe(res);
3861 } else if (useDeflate && isCompressable) {
3862 resStream = zlib.createDeflateRaw();
3863 resStream.pipe(res);
3864 } else if (useGzip && isCompressable) {
3865 resStream = zlib.createGzip();
3866 resStream.pipe(res);
3867 } else {
3868 resStream = res;
3870 if (ext == "html") {
3871 function afterWriteCallback() {
3872 if (foot.length > 0) {
3873 readStream.on("end", function () {
3874 resStream.end(foot);
3875 });
3877 readStream.pipe(resStream, {
3878 end: (foot.length == 0)
3879 });
3881 res.writeHead(200, http.STATUS_CODES[200], hdhds);
3882 if (head.length == 0) {
3883 afterWriteCallback();
3884 } else if (!resStream.write(head)) {
3885 resStream.on("drain", afterWriteCallback);
3886 } else {
3887 process.nextTick(afterWriteCallback);
3889 } else {
3890 res.writeHead(200, http.STATUS_CODES[200], hdhds);
3891 readStream.pipe(resStream);
3893 serverconsole.resmessage("Client successfully received content.");
3894 } catch (err) {
3895 callServerError(500, err);
3897 });
3898 } else {
3899 res.writeHead(200, http.STATUS_CODES[200], hdhds);
3900 res.end();
3901 serverconsole.resmessage("Client successfully received content.");
3903 } catch (err) {
3904 callServerError(500, err);
3907 } else if (stats.isDirectory()) {
3908 // Check if directory listing is enabled in the configuration
3909 if (checkForEnabledDirectoryListing(req.headers.host, req.socket ? req.socket.localAddress : undefined)) {
3910 var customDirListingHeader = "";
3911 var customDirListingFooter = "";
3913 function getCustomDirListingHeader(callback) {
3914 fs.readFile(("." + dHref + "/.dirhead").replace(/\/+/g, "/"), function (err, data) {
3915 if (err) {
3916 if (err.code == "ENOENT" || err.code == "EISDIR") {
3917 if (os.platform != "win32" || href != "/") {
3918 fs.readFile(("." + dHref + "/HEAD.html").replace(/\/+/g, "/"), function (err, data) {
3919 if (err) {
3920 if (err.code == "ENOENT" || err.code == "EISDIR") {
3921 callback();
3922 } else {
3923 callServerError(500, err);
3925 } else {
3926 customDirListingHeader = data.toString();
3927 callback();
3929 });
3930 } else {
3931 callback();
3933 } else {
3934 callServerError(500, err);
3936 } else {
3937 customDirListingHeader = data.toString();
3938 callback();
3940 });
3943 function getCustomDirListingFooter(callback) {
3944 fs.readFile(("." + dHref + "/.dirfoot").replace(/\/+/g, "/"), function (err, data) {
3945 if (err) {
3946 if (err.code == "ENOENT" || err.code == "EISDIR") {
3947 if (os.platform != "win32" || href != "/") {
3948 fs.readFile(("." + dHref + "/FOOT.html").replace(/\/+/g, "/"), function (err, data) {
3949 if (err) {
3950 if (err.code == "ENOENT" || err.code == "EISDIR") {
3951 callback();
3952 } else {
3953 callServerError(500, err);
3955 } else {
3956 customDirListingFooter = data.toString();
3957 callback();
3959 });
3960 } else {
3961 callback();
3963 } else {
3964 callServerError(500, err);
3966 } else {
3967 customDirListingFooter = data.toString();
3968 callback();
3970 });
3973 // Read custom header and footer content (if available)
3974 getCustomDirListingHeader(function () {
3975 getCustomDirListingFooter(function () {
3976 // Check if custom header has HTML tag
3977 var headerHasHTMLTag = customDirListingHeader.replace(/<!--(?:(?:(?!--\>)[\s\S])*|)(?:-->|$)/g, "").match(/<html(?![a-zA-Z0-9])(?:"(?:\\(?:[\s\S]|$)|[^\\"])*(?:"|$)|'(?:\\(?:[\s\S]|$)|[^\\'])*(?:'|$)|[^'">])*(?:>|$)/i);
3979 // Generate HTML head and footer based on configuration and custom content
3980 var htmlHead = (!configJSON.enableDirectoryListingWithDefaultHead || head == "" ?
3981 (!headerHasHTMLTag ?
3982 "<!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>" :
3983 customDirListingHeader.replace(/<head>/i, "<head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</title>")) :
3984 head.replace(/<head>/i, "<head><title>Directory: " + decodeURIComponent(origHref).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</title>")) +
3985 (!headerHasHTMLTag ? customDirListingHeader : "") +
3986 "<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>");
3988 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);
3990 if (fs.existsSync("." + decodeURIComponent(href) + "/.maindesc".replace(/\/+/g, "/"))) {
3991 htmlFoot = "</table><hr/>" + fs.readFileSync("." + decodeURIComponent(href) + "/.maindesc".replace(/\/+/g, "/")) + htmlFoot;
3994 fs.readdir(readFrom, function (err, list) {
3995 try {
3996 if (err) throw err;
3997 list = list.sort();
3999 // Function to get stats for all files in the directory
4000 function getStatsForAllFilesI(fileList, callback, prefix, pushArray, index) {
4001 if (fileList.length == 0) {
4002 callback(pushArray);
4003 return;
4006 fs.stat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) {
4007 if (err) {
4008 fs.lstat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) {
4009 pushArray.push({
4010 name: fileList[index],
4011 stats: err ? null : stats,
4012 errored: true
4013 });
4014 if (index < fileList.length - 1) {
4015 getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
4016 } else {
4017 callback(pushArray);
4019 });
4020 } else {
4021 pushArray.push({
4022 name: fileList[index],
4023 stats: stats,
4024 errored: false
4025 });
4026 if (index < fileList.length - 1) {
4027 getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1);
4028 } else {
4029 callback(pushArray);
4032 });
4035 // Wrapper function to get stats for all files
4036 function getStatsForAllFiles(fileList, prefix, callback) {
4037 if (!prefix) prefix = "";
4038 getStatsForAllFilesI(fileList, callback, prefix, [], 0);
4041 // Get stats for all files in the directory and generate the listing
4042 getStatsForAllFiles(list, readFrom, function (filelist) {
4043 var directoryListingRows = [];
4044 for (var i = 0; i < filelist.length; i++) {
4045 if (filelist[i].name[0] !== ".") {
4046 var estats = filelist[i].stats;
4047 var ename = filelist[i].name;
4048 var eext = ename.match(/\.([^.]+)$/);
4049 eext = eext ? eext[1] : "";
4050 var emime = eext ? mime.contentType(eext) : false;
4051 if (filelist[i].errored) {
4052 directoryListingRows.push(
4053 "<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=\"" +
4054 (href + "/" + encodeURI(ename)).replace(/\/+/g, "/") +
4055 "\">" +
4056 ename.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") +
4057 "</a></td><td>-</td><td>" +
4058 (estats ? estats.mtime.toDateString() : "-") +
4059 "</td></tr>\r\n"
4060 );
4061 } else {
4062 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=\"" +
4063 (origHref + "/" + encodeURIComponent(ename)).replace(/\/+/g, "/") +
4064 (estats.isDirectory() ? "/" : "") +
4065 "\">" +
4066 ename.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") +
4067 "</a></td><td>" +
4068 (estats.isDirectory() ? "-" : sizify(estats.size.toString())) +
4069 "</td><td>" +
4070 estats.mtime.toDateString() +
4071 "</td></tr>\r\n";
4073 // Determine the file type and set the appropriate image and alt text
4074 if (estats.isDirectory()) {
4075 entry = entry.replace("[img]", "/.dirimages/directory.png").replace("[alt]", "[DIR]");
4076 } else if (!estats.isFile()) {
4077 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=\"" +
4078 (origHref + "/" + encodeURIComponent(ename)).replace(/\/+/g, "/") +
4079 "\">" +
4080 ename.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") +
4081 "</a></td><td>-</td><td>" +
4082 estats.mtime.toDateString() +
4083 "</td></tr>\r\n";
4085 // Determine the special file types (block device, character device, etc.)
4086 if (estats.isBlockDevice()) {
4087 entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[BLK]");
4088 } else if (estats.isCharacterDevice()) {
4089 entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[CHR]");
4090 } else if (estats.isFIFO()) {
4091 entry = entry.replace("[img]", "/.dirimages/fifo.png").replace("[alt]", "[FIF]");
4092 } else if (estats.isSocket()) {
4093 entry = entry.replace("[img]", "/.dirimages/socket.png").replace("[alt]", "[SCK]");
4095 } else if (ename.match(/README|LICEN[SC]E/i)) {
4096 entry = entry.replace("[img]", "/.dirimages/important.png").replace("[alt]", "[IMP]");
4097 } else if (eext.match(/^(?:[xs]?html?|xml)$/i)) {
4098 entry = entry.replace("[img]", "/.dirimages/html.png").replace("[alt]", (eext == "xml" ? "[XML]" : "[HTM]"));
4099 } else if (eext == "js") {
4100 entry = entry.replace("[img]", "/.dirimages/javascript.png").replace("[alt]", "[JS ]");
4101 } else if (eext == "php") {
4102 entry = entry.replace("[img]", "/.dirimages/php.png").replace("[alt]", "[PHP]");
4103 } else if (eext == "css") {
4104 entry = entry.replace("[img]", "/.dirimages/css.png").replace("[alt]", "[CSS]");
4105 } else if (emime && emime.split("/")[0] == "image") {
4106 entry = entry.replace("[img]", "/.dirimages/image.png").replace("[alt]", (eext == "ico" ? "[ICO]" : "[IMG]"));
4107 } else if (emime && emime.split("/")[0] == "font") {
4108 entry = entry.replace("[img]", "/.dirimages/font.png").replace("[alt]", "[FON]");
4109 } else if (emime && emime.split("/")[0] == "audio") {
4110 entry = entry.replace("[img]", "/.dirimages/audio.png").replace("[alt]", "[AUD]");
4111 } else if ((emime && emime.split("/")[0] == "text") || eext == "json") {
4112 entry = entry.replace("[img]", "/.dirimages/text.png").replace("[alt]", (eext == "json" ? "[JSO]" : "[TXT]"));
4113 } else if (emime && emime.split("/")[0] == "video") {
4114 entry = entry.replace("[img]", "/.dirimages/video.png").replace("[alt]", "[VID]");
4115 } else if (eext.match(/^(?:zip|rar|bz2|[gb7x]z|lzma|tar)$/i)) {
4116 entry = entry.replace("[img]", "/.dirimages/archive.png").replace("[alt]", "[ARC]");
4117 } else if (eext.match(/^(?:[id]mg|iso|flp)$/i)) {
4118 entry = entry.replace("[img]", "/.dirimages/diskimage.png").replace("[alt]", "[DSK]");
4119 } else {
4120 entry = entry.replace("[img]", "/.dirimages/other.png").replace("[alt]", "[OTH]");
4122 directoryListingRows.push(entry);
4127 // Push the information about empty directory
4128 if (directoryListingRows.length == 0) {
4129 directoryListingRows.push("<tr><td></td><td>No files found</td><td></td><td></td></tr>");
4132 // Send the directory listing response
4133 res.writeHead(200, http.STATUS_CODES[200], {
4134 "Content-Type": "text/html; charset=utf-8"
4135 });
4136 res.end(htmlHead + directoryListingRows.join("") + htmlFoot);
4137 serverconsole.resmessage("Client successfully received content.");
4138 });
4140 } catch (err) {
4141 if (err.code == "ENOENT") {
4142 callServerError(404);
4143 serverconsole.errmessage("Resource not found.");
4144 } else if (err.code == "ENOTDIR") {
4145 callServerError(404); // Assume that file doesn't exist.
4146 serverconsole.errmessage("Resource not found.");
4147 } else if (err.code == "EACCES") {
4148 callServerError(403);
4149 serverconsole.errmessage("Access denied.");
4150 } else if (err.code == "ENAMETOOLONG") {
4151 callServerError(414);
4152 } else if (err.code == "EMFILE") {
4153 callServerError(503);
4154 } else if (err.code == "ELOOP") {
4155 callServerError(508); // The symbolic link loop is detected during file system operations.
4156 serverconsole.errmessage("Symbolic link loop detected.");
4157 } else {
4158 callServerError(500, err);
4161 });
4162 });
4163 });
4164 } else {
4165 // Directory listing is disabled, call 403 Forbidden error
4166 callServerError(403);
4167 serverconsole.errmessage("Directory listing is disabled.");
4169 } else {
4170 callServerError(501);
4171 serverconsole.errmessage("SVR.JS doesn't support block devices, character devices, FIFOs nor sockets.");
4172 return;
4175 });
4178 try {
4179 // Scan the block list
4180 if (blocklist.check(reqip)) {
4181 // Invoke 403 Forbidden error
4182 callServerError(403);
4183 serverconsole.errmessage("Client is in the block list.");
4184 return;
4187 if (req.url == "*") {
4188 // Handle "*" URL
4189 if (req.method == "OPTIONS") {
4190 // Respond with list of methods
4191 var hdss = getCustomHeaders();
4192 hdss["Allow"] = "GET, POST, HEAD, OPTIONS";
4193 res.writeHead(204, http.STATUS_CODES[204], hdss);
4194 res.end();
4195 return;
4196 } else {
4197 // SVR.JS doesn't understand that request, so throw an 400 error
4198 callServerError(400);
4199 return;
4203 if (req.method == "CONNECT") {
4204 // CONNECT requests should be handled in "connect" event.
4205 callServerError(501);
4206 serverconsole.errmessage("CONNECT requests aren't supported. Your JS runtime probably doesn't support 'connect' handler for HTTP library.");
4207 return;
4210 // Check for invalid X-Forwarded-For header
4211 if (!isForwardedValid) {
4212 serverconsole.errmessage("X-Forwarded-For header is invalid.");
4213 callServerError(400);
4214 return;
4217 // Sanitize URL
4218 var sanitizedHref = sanitizeURL(href, allowDoubleSlashes);
4219 var preparedReqUrl = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
4221 // Check if URL is "dirty"
4222 if (href != sanitizedHref && !isProxy) {
4223 var sanitizedURL = uobject;
4224 sanitizedURL.path = null;
4225 sanitizedURL.href = null;
4226 sanitizedURL.pathname = sanitizedHref;
4227 sanitizedURL.hostname = null;
4228 sanitizedURL.host = null;
4229 sanitizedURL.port = null;
4230 sanitizedURL.protocol = null;
4231 sanitizedURL.slashes = null;
4232 sanitizedURL = url.format(sanitizedURL);
4233 serverconsole.resmessage("URL sanitized: " + req.url + " => " + sanitizedURL);
4234 if (rewriteDirtyURLs) {
4235 req.url = sanitizedURL;
4236 try {
4237 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4238 } catch (err) {
4239 // Return an 400 error
4240 callServerError(400);
4241 serverconsole.errmessage("Bad request!");
4242 return;
4244 search = uobject.search;
4245 href = uobject.pathname;
4246 ext = href.match(/[^\/]\.([^.]+)$/);
4247 if(!ext) ext = "";
4248 else ext = ext[1].toLowerCase();
4249 try {
4250 decodedHref = decodeURIComponent(href);
4251 } catch (err) {
4252 // Return 400 error
4253 callServerError(400);
4254 serverconsole.errmessage("Bad request!");
4255 return;
4257 } else {
4258 redirect(sanitizedURL, false);
4259 return;
4261 } else if (req.url != preparedReqUrl && !isProxy) {
4262 serverconsole.resmessage("URL sanitized: " + req.url + " => " + preparedReqUrl);
4263 if (rewriteDirtyURLs) {
4264 req.url = preparedReqUrl;
4265 } else {
4266 redirect(preparedReqUrl, false);
4267 return;
4271 // Handle redirects to HTTPS
4272 if (secure && !fromMain && !disableNonEncryptedServer && !disableToHTTPSRedirect) {
4273 var hostx = req.headers.host;
4274 if (hostx === undefined) {
4275 serverconsole.errmessage("Host header is missing.");
4276 callServerError(400);
4277 return;
4280 if (isProxy) {
4281 callServerError(501);
4282 serverconsole.errmessage("This server will never be a proxy.");
4283 return;
4286 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);
4288 var destinationPort = 0;
4290 var parsedHostx = hostx.match(/(\[[^\]]*\]|[^:]*)(?::([0-9]+))?/);
4291 var hostname = parsedHostx[1];
4292 var hostPort = parsedHostx[2] ? parseInt(parsedHostx[2]) : 80;
4293 if (isNaN(hostPort)) hostPort = 80;
4295 if (hostPort == port || (port == pubport && !isPublicServer)) {
4296 destinationPort = sport;
4297 } else {
4298 destinationPort = spubport;
4301 redirect("https://" + hostname + (destinationPort == 443 ? "" : (":" + destinationPort)) + req.url);
4302 return;
4305 // Handle redirects to addresses with "www." prefix
4306 if (wwwredirect) {
4307 var hostname = req.headers.host.split(":");
4308 var hostport = null;
4309 if (hostname.length > 1 && (hostname[0] != "[" || hostname[hostname.length - 1] != "]")) hostport = hostname.pop();
4310 hostname = hostname.join(":");
4311 if (hostname == domain && hostname.indexOf("www.") != 0) {
4312 redirect((req.socket.encrypted ? "https" : "http") + "://www." + hostname + (hostport ? ":" + hostport : "") + req.url.replace(/\/+/g, "/"));
4313 return;
4317 // Handle URL rewriting
4318 function rewriteURL(address, map, callback, _fileState, _mapBegIndex) {
4319 var rewrittenURL = address;
4320 var doCallback = true;
4321 if (!isProxy) {
4322 for (var i = (_mapBegIndex ? _mapBegIndex : 0); i < map.length; i++) {
4323 var mapEntry = map[i];
4324 if (href != "/" && (mapEntry.isNotDirectory || mapEntry.isNotFile) && !_fileState) {
4325 fs.stat("." + decodeURIComponent(href), function (err, stats) {
4326 var _fileState = 3;
4327 if (err) {
4328 _fileState = 3;
4329 } else if (stats.isDirectory()) {
4330 _fileState = 2;
4331 } else if (stats.isFile()) {
4332 _fileState = 1;
4333 } else {
4334 _fileState = 3;
4336 rewriteURL(address, map, callback, _fileState, i);
4337 });
4338 doCallback = false;
4339 break;
4341 var tempRewrittenURL = rewrittenURL;
4342 if (!mapEntry.allowDoubleSlashes) {
4343 address = address.replace(/\/+/g,"/");
4344 tempRewrittenURL = address;
4346 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)) {
4347 rewrittenURL = tempRewrittenURL;
4348 try {
4349 mapEntry.replacements.forEach(function (replacement) {
4350 rewrittenURL = rewrittenURL.replace(createRegex(replacement.regex), replacement.replacement);
4351 });
4352 if (mapEntry.append) rewrittenURL += mapEntry.append;
4353 } catch (err) {
4354 doCallback = false;
4355 callback(err, null);
4357 break;
4361 if (doCallback) callback(null, rewrittenURL);
4364 // Trailing slash redirection
4365 function redirectTrailingSlashes(callback) {
4366 if (!isProxy && !disableTrailingSlashRedirects && href[href.length - 1] != "/" && origHref[origHref.length - 1] != "/") {
4367 fs.stat("." + decodeURIComponent(href), function (err, stats) {
4368 if (err || !stats.isDirectory()) {
4369 try {
4370 callback();
4371 } catch (err) {
4372 callServerError(500, err);
4374 } else {
4375 var destinationURL = uobject;
4376 destinationURL.path = null;
4377 destinationURL.href = null;
4378 destinationURL.pathname = origHref + "/";
4379 destinationURL.hostname = null;
4380 destinationURL.host = null;
4381 destinationURL.port = null;
4382 destinationURL.protocol = null;
4383 destinationURL.slashes = null;
4384 destinationURL = url.format(destinationURL);
4385 redirect(destinationURL);
4387 });
4388 } else {
4389 callback();
4393 origHref = href;
4395 // Add web root postfixes
4396 if (!isProxy) {
4397 var preparedReqUrl3 = (allowPostfixDoubleSlashes ? (href.replace(/\/+/,"/") + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "")) : req.url);
4398 var urlWithPostfix = preparedReqUrl3;
4399 var postfixPrefix = "";
4400 wwwrootPostfixPrefixesVHost.every(function (currentPostfixPrefix) {
4401 if (preparedReqUrl3.indexOf(currentPostfixPrefix) == 0) {
4402 if (currentPostfixPrefix.match(/\/+$/)) postfixPrefix = currentPostfixPrefix.replace(/\/+$/, "");
4403 else if (urlWithPostfix.length == currentPostfixPrefix.length || urlWithPostfix[currentPostfixPrefix.length] == "?" || urlWithPostfix[currentPostfixPrefix.length] == "/" || urlWithPostfix[currentPostfixPrefix.length] == "#") postfixPrefix = currentPostfixPrefix;
4404 else return true;
4405 urlWithPostfix = urlWithPostfix.substring(postfixPrefix.length);
4406 return false;
4407 } else {
4408 return true;
4410 });
4411 wwwrootPostfixesVHost.every(function (postfixEntry) {
4412 if (matchHostname(postfixEntry.host) && ipMatch(postfixEntry.ip, req.socket ? req.socket.localAddress : undefined) && !(postfixEntry.skipRegex && preparedReqUrl3.match(createRegex(postfixEntry.skipRegex)))) {
4413 urlWithPostfix = postfixPrefix + "/" + postfixEntry.postfix + urlWithPostfix;
4414 return false;
4415 } else {
4416 return true;
4418 });
4419 if (urlWithPostfix != preparedReqUrl3) {
4420 serverconsole.resmessage("Added web root postfix: " + req.url + " => " + urlWithPostfix);
4421 req.url = urlWithPostfix;
4422 try {
4423 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4424 } catch (err) {
4425 // Return an 400 error
4426 callServerError(400);
4427 serverconsole.errmessage("Bad request!");
4428 return;
4430 search = uobject.search;
4431 href = uobject.pathname;
4432 ext = href.match(/[^\/]\.([^.]+)$/);
4433 if(!ext) ext = "";
4434 else ext = ext[1].toLowerCase();
4436 try {
4437 decodedHref = decodeURIComponent(href);
4438 } catch (err) {
4439 // Return 400 error
4440 callServerError(400);
4441 serverconsole.errmessage("Bad request!");
4442 return;
4445 var sHref = sanitizeURL(href, allowDoubleSlashes);
4446 var preparedReqUrl2 = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
4448 if (req.url != preparedReqUrl2 || sHref != href.replace(/\/\.(?=\/|$)/g, "/").replace(/\/+/g, "/")) {
4449 callServerError(403);
4450 serverconsole.errmessage("Content blocked.");
4451 return;
4452 } else if (sHref != href) {
4453 var rewrittenAgainURL = uobject;
4454 rewrittenAgainURL.path = null;
4455 rewrittenAgainURL.href = null;
4456 rewrittenAgainURL.pathname = sHref;
4457 rewrittenAgainURL.hostname = null;
4458 rewrittenAgainURL.host = null;
4459 rewrittenAgainURL.port = null;
4460 rewrittenAgainURL.protocol = null;
4461 rewrittenAgainURL.slashes = null;
4462 rewrittenAgainURL = url.format(rewrittenAgainURL);
4463 serverconsole.resmessage("URL sanitized: " + req.url + " => " + rewrittenAgainURL);
4464 req.url = rewrittenAgainURL;
4465 try {
4466 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4467 } catch (err) {
4468 // Return an 400 error
4469 callServerError(400);
4470 serverconsole.errmessage("Bad request!");
4471 return;
4473 search = uobject.search;
4474 href = uobject.pathname;
4475 ext = href.match(/[^\/]\.([^.]+)$/);
4476 if(!ext) ext = "";
4477 else ext = ext[1].toLowerCase();
4478 try {
4479 decodedHref = decodeURIComponent(href);
4480 } catch (err) {
4481 // Return 400 error
4482 callServerError(400);
4483 serverconsole.errmessage("Bad request!");
4484 return;
4490 // Rewrite URLs
4491 rewriteURL(req.url, rewriteMap, function (err, rewrittenURL) {
4492 if (err) {
4493 callServerError(500, err);
4494 return;
4496 if (rewrittenURL != req.url) {
4497 serverconsole.resmessage("URL rewritten: " + req.url + " => " + rewrittenURL);
4498 req.url = rewrittenURL;
4499 try {
4500 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4501 } catch (err) {
4502 // Return an 400 error
4503 callServerError(400);
4504 serverconsole.errmessage("Bad request!");
4505 return;
4507 search = uobject.search;
4508 href = uobject.pathname;
4509 ext = href.match(/[^\/]\.([^.]+)$/);
4510 if(!ext) ext = "";
4511 else ext = ext[1].toLowerCase();
4512 try {
4513 decodedHref = decodeURIComponent(href);
4514 } catch (err) {
4515 // Return 400 error
4516 callServerError(400);
4517 serverconsole.errmessage("Bad request!");
4518 return;
4521 var sHref = sanitizeURL(href, allowDoubleSlashes);
4522 var preparedReqUrl2 = uobject.pathname + (uobject.search ? uobject.search : "") + (uobject.hash ? uobject.hash : "");
4524 if (req.url != preparedReqUrl2 || sHref != href.replace(/\/\.(?=\/|$)/g, "/").replace(/\/+/g, "/")) {
4525 callServerError(403);
4526 serverconsole.errmessage("Content blocked.");
4527 return;
4528 } else if (sHref != href) {
4529 var rewrittenAgainURL = uobject;
4530 rewrittenAgainURL.path = null;
4531 rewrittenAgainURL.href = null;
4532 rewrittenAgainURL.pathname = sHref;
4533 rewrittenAgainURL.hostname = null;
4534 rewrittenAgainURL.host = null;
4535 rewrittenAgainURL.port = null;
4536 rewrittenAgainURL.protocol = null;
4537 rewrittenAgainURL.slashes = null;
4538 rewrittenAgainURL = url.format(rewrittenAgainURL);
4539 serverconsole.resmessage("URL sanitized: " + req.url + " => " + rewrittenAgainURL);
4540 req.url = rewrittenAgainURL;
4541 try {
4542 uobject = parseURL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
4543 } catch (err) {
4544 // Return an 400 error
4545 callServerError(400);
4546 serverconsole.errmessage("Bad request!");
4547 return;
4549 search = uobject.search;
4550 href = uobject.pathname;
4551 ext = href.match(/[^\/]\.([^.]+)$/);
4552 if(!ext) ext = "";
4553 else ext = ext[1].toLowerCase();
4554 try {
4555 decodedHref = decodeURIComponent(href);
4556 } catch (err) {
4557 // Return 400 error
4558 callServerError(400);
4559 serverconsole.errmessage("Bad request!");
4560 return;
4564 // Set response headers
4565 if (!isProxy) {
4566 var hkh = getCustomHeaders();
4567 Object.keys(hkh).forEach(function (hkS) {
4568 try {
4569 res.setHeader(hkS, hkh[hkS]);
4570 } catch (err) {
4571 // Headers will not be set.
4573 });
4576 // Prepare the path (remove multiple slashes)
4577 var decodedHrefWithoutDuplicateSlashes = decodedHref.replace(/\/+/g,"/");
4579 // Check if path is forbidden
4580 if ((isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "config") || isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "certificates")) && !isProxy) {
4581 callServerError(403);
4582 serverconsole.errmessage("Access to configuration file/certificates is denied.");
4583 return;
4584 } else if (isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "temp") && !isProxy) {
4585 callServerError(403);
4586 serverconsole.errmessage("Access to temporary folder is denied.");
4587 return;
4588 } else if (isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "log") && !isProxy && (configJSON.enableLogging || configJSON.enableLogging == undefined) && !configJSON.enableRemoteLogBrowsing) {
4589 callServerError(403);
4590 serverconsole.errmessage("Access to log files is denied.");
4591 return;
4592 } else if (isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") && !isProxy && !exposeServerVersion) {
4593 callServerError(403);
4594 serverconsole.errmessage("Access to SVR.JS script is denied.");
4595 return;
4596 } else if ((isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") || isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "serverSideScripts") || isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "serverSideScriptDirectories")) && !isProxy && (configJSON.disableServerSideScriptExpose || configJSON.disableServerSideScriptExpose === undefined)) {
4597 callServerError(403);
4598 serverconsole.errmessage("Access to sources is denied.");
4599 return;
4600 } else {
4601 var nonscodeIndex = -1;
4602 var authIndex = -1;
4603 var regexI = [];
4605 // Scan for non-standard codes
4606 if (!isProxy && nonStandardCodes != undefined) {
4607 for (var i = 0; i < nonStandardCodes.length; i++) {
4608 if (matchHostname(nonStandardCodes[i].host) && ipMatch(nonStandardCodes[i].ip, req.socket ? req.socket.localAddress : undefined)) {
4609 var isMatch = false;
4610 var hrefWithoutDuplicateSlashes = href.replace(/\/+/g,"/");
4611 if (nonStandardCodes[i].regex) {
4612 // Regex match
4613 var createdRegex = createRegex(nonStandardCodes[i].regex, true);
4614 isMatch = req.url.match(createdRegex) || hrefWithoutDuplicateSlashes.match(createdRegex);
4615 regexI[i] = createdRegex;
4616 } else {
4617 // Non-regex match
4618 isMatch = nonStandardCodes[i].url == hrefWithoutDuplicateSlashes || (os.platform() == "win32" && nonStandardCodes[i].url.toLowerCase() == hrefWithoutDuplicateSlashes.toLowerCase());
4620 if (isMatch) {
4621 if (nonStandardCodes[i].scode == 401) {
4622 // HTTP authentication
4623 if (authIndex == -1) {
4624 authIndex = i;
4626 } else {
4627 if (nonscodeIndex == -1) {
4628 if ((nonStandardCodes[i].scode == 403 || nonStandardCodes[i].scode == 451) && nonStandardCodes[i].users !== undefined) {
4629 if (nonStandardCodes[i].users.check(reqip)) nonscodeIndex = i;
4630 } else {
4631 nonscodeIndex = i;
4640 // Handle non-standard codes
4641 if (nonscodeIndex > -1) {
4642 var nonscode = nonStandardCodes[nonscodeIndex];
4643 if (nonscode.scode == 301 || nonscode.scode == 302 || nonscode.scode == 307 || nonscode.scode == 308) {
4644 var location = "";
4645 if (regexI[nonscodeIndex]) {
4646 location = req.url.replace(regexI[nonscodeIndex], nonscode.location);
4647 if(location == req.url) {
4648 // Fallback replacement
4649 location = hrefWithoutDuplicateSlashes.replace(regexI[nonscodeIndex], nonscode.location);
4651 } else if (req.url.split("?")[1] == undefined || req.url.split("?")[1] == null || req.url.split("?")[1] == "" || req.url.split("?")[1] == " ") {
4652 location = nonscode.location;
4653 } else {
4654 location = nonscode.location + "?" + req.url.split("?")[1];
4656 redirect(location, nonscode.scode == 302 || nonscode.scode == 307, nonscode.scode == 307 || nonscode.scode == 308);
4657 return;
4658 } else {
4659 callServerError(nonscode.scode);
4660 if (nonscode.scode == 403) {
4661 serverconsole.errmessage("Content blocked.");
4662 } else if (nonscode.scode == 410) {
4663 serverconsole.errmessage("Content is gone.");
4664 } else if (nonscode.scode == 418) {
4665 serverconsole.errmessage("SVR.JS is always a teapot ;)");
4666 } else {
4667 serverconsole.errmessage("Client fails receiving content.");
4669 return;
4673 // Handle HTTP authentication
4674 if (authIndex > -1) {
4675 var authcode = nonStandardCodes[authIndex];
4677 // Function to check if passwords match
4678 function checkIfPasswordMatches(list, password, callback, _i) {
4679 if (!_i) _i = 0;
4680 var cb = function (hash) {
4681 if (hash == list[_i].pass) {
4682 callback(true);
4683 } else if (_i >= list.length - 1) {
4684 callback(false);
4685 } else {
4686 checkIfPasswordMatches(list, password, callback, _i + 1);
4688 };
4689 var hashedPassword = sha256(password + list[_i].salt);
4690 var cacheEntry = null;
4691 if (list[_i].scrypt) {
4692 if (!crypto.scrypt) {
4693 callServerError(500, new Error("SVR.JS doesn't support scrypt-hashed passwords on Node.JS versions without scrypt hash support."));
4694 return;
4695 } else {
4696 cacheEntry = scryptCache.find(function (entry) {
4697 return (entry.password == hashedPassword && entry.salt == list[_i].salt);
4698 });
4699 if (cacheEntry) {
4700 cb(cacheEntry.hash);
4701 } else {
4702 crypto.scrypt(password, list[_i].salt, 64, function (err, derivedKey) {
4703 if (err) {
4704 callServerError(500, err);
4705 } else {
4706 var key = derivedKey.toString("hex");
4707 scryptCache.push({
4708 hash: key,
4709 password: hashedPassword,
4710 salt: list[_i].salt,
4711 addDate: new Date()
4712 });
4713 cb(key);
4715 });
4718 } else if (list[_i].pbkdf2) {
4719 if (crypto.__disabled__ !== undefined) {
4720 callServerError(500, new Error("SVR.JS doesn't support PBKDF2-hashed passwords on Node.JS versions without crypto support."));
4721 return;
4722 } else {
4723 cacheEntry = pbkdf2Cache.find(function (entry) {
4724 return (entry.password == hashedPassword && entry.salt == list[_i].salt);
4725 });
4726 if (cacheEntry) {
4727 cb(cacheEntry.hash);
4728 } else {
4729 crypto.pbkdf2(password, list[_i].salt, 36250, 64, "sha512", function (err, derivedKey) {
4730 if (err) {
4731 callServerError(500, err);
4732 } else {
4733 var key = derivedKey.toString("hex");
4734 pbkdf2Cache.push({
4735 hash: key,
4736 password: hashedPassword,
4737 salt: list[_i].salt,
4738 addDate: new Date()
4739 });
4740 cb(key);
4742 });
4745 } else {
4746 cb(hashedPassword);
4750 function authorizedCallback(bruteProtection) {
4751 try {
4752 var ha = getCustomHeaders();
4753 ha["WWW-Authenticate"] = "Basic realm=\"" + (authcode.realm ? authcode.realm.replace(/(\\|")/g, "\\$1") : "SVR.JS HTTP Basic Authorization") + "\", charset=\"UTF-8\"";
4754 var credentials = req.headers["authorization"];
4755 if (!credentials) {
4756 callServerError(401, ha);
4757 serverconsole.errmessage("Content needs authorization.");
4758 return;
4760 var credentialsMatch = credentials.match(/^Basic (.+)$/);
4761 if (!credentialsMatch) {
4762 callServerError(401, ha);
4763 serverconsole.errmessage("Malformed credentials.");
4764 return;
4766 var decodedCredentials = Buffer.from(credentialsMatch[1], "base64").toString("utf8");
4767 var decodedCredentialsMatch = decodedCredentials.match(/^([^:]*):(.*)$/);
4768 if (!decodedCredentialsMatch) {
4769 callServerError(401, ha);
4770 serverconsole.errmessage("Malformed credentials.");
4771 return;
4773 var username = decodedCredentialsMatch[1];
4774 var password = decodedCredentialsMatch[2];
4775 var usernameMatch = [];
4776 var sha256Count = 0;
4777 var pbkdf2Count = 0;
4778 var scryptCount = 0;
4779 if (!authcode.userList || authcode.userList.indexOf(username) > -1) {
4780 usernameMatch = users.filter(function (entry) {
4781 if(entry.scrypt) {
4782 scryptCount++;
4783 } else if(entry.pbkdf2) {
4784 pbkdf2Count++;
4785 } else {
4786 sha256Count++;
4788 return entry.name == username;
4789 });
4791 if (usernameMatch.length == 0) {
4792 // Pushing false user match to prevent time-based user enumeration
4793 var fakeCredentials = {
4794 name: username,
4795 pass: "SVRJSAWebServerRunningOnNodeJS",
4796 salt: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0"
4797 };
4798 if (!process.isBun) {
4799 if (scryptCount > sha256Count && scryptCount > pbkdf2Count) {
4800 fakeCredentials.scrypt = true;
4801 } else if (pbkdf2Count > sha256Count) {
4802 fakeCredentials.pbkdf2 = true;
4805 usernameMatch.push(fakeCredentials);
4807 checkIfPasswordMatches(usernameMatch, password, function (authorized) {
4808 try {
4809 if (!authorized) {
4810 if (bruteProtection) {
4811 if (process.send) {
4812 process.send("\x12AUTHW" + reqip);
4813 } else {
4814 if (!bruteForceDb[reqip]) bruteForceDb[reqip] = {
4815 invalidAttempts: 0
4816 };
4817 bruteForceDb[reqip].invalidAttempts++;
4818 if (bruteForceDb[reqip].invalidAttempts >= 10) {
4819 bruteForceDb[reqip].lastAttemptDate = new Date();
4823 callServerError(401, ha);
4824 serverconsole.errmessage("User \"" + String(username).replace(/[\r\n]/g, "") + "\" failed to log in.");
4825 } else {
4826 if (bruteProtection) {
4827 if (process.send) {
4828 process.send("\x12AUTHR" + reqip);
4829 } else {
4830 if (bruteForceDb[reqip]) bruteForceDb[reqip] = {
4831 invalidAttempts: 0
4832 };
4835 serverconsole.reqmessage("Client is logged in as \"" + String(username).replace(/[\r\n]/g, "") + "\".");
4836 authUser = username;
4837 redirectTrailingSlashes(function () {
4838 modExecute(mods, vres);
4839 });
4841 } catch (err) {
4842 callServerError(500, err);
4843 return;
4845 });
4846 } catch (err) {
4847 callServerError(500, err);
4848 return;
4851 if (authcode.disableBruteProtection) {
4852 // Don't brute-force protect it, just do HTTP authentication
4853 authorizedCallback(false);
4854 } else if (!process.send) {
4855 // Query data from JS object database
4856 if (!bruteForceDb[reqip] || !bruteForceDb[reqip].lastAttemptDate || (new Date() - 300000 >= bruteForceDb[reqip].lastAttemptDate)) {
4857 if (bruteForceDb[reqip] && bruteForceDb[reqip].invalidAttempts >= 10) bruteForceDb[reqip] = {
4858 invalidAttempts: 5
4859 };
4860 authorizedCallback(true);
4861 } else {
4862 callServerError(429);
4863 serverconsole.errmessage("Brute force limit reached!");
4865 } else {
4866 var listenerEmitted = false;
4868 // Listen for brute-force protection response
4869 function authMessageListener(message) {
4870 if (listenerEmitted) return;
4871 if (message == "\x14AUTHA" + reqip || message == "\x14AUTHD" + reqip) {
4872 process.removeListener("message", authMessageListener);
4873 listenerEmitted = true;
4875 if (message == "\x14AUTHD" + reqip) {
4876 callServerError(429);
4877 serverconsole.errmessage("Brute force limit reached!");
4878 } else if (message == "\x14AUTHA" + reqip) {
4879 authorizedCallback(true);
4882 process.on("message", authMessageListener);
4883 process.send("\x12AUTHQ" + reqip);
4885 } else {
4886 redirectTrailingSlashes(function () {
4887 modExecute(mods, vres);
4888 });
4892 });
4893 } catch (err) {
4894 callServerError(500, err);
4898 function serverErrorHandler(err, isRedirect) {
4899 if(isRedirect) attmtsRedir--;
4900 else attmts--;
4901 if (cluster.isPrimary === undefined && (isRedirect ? attmtsRedir : attmts)) {
4902 serverconsole.locerrmessage(serverErrorDescs[err.code] ? serverErrorDescs[err.code] : serverErrorDescs["UNKNOWN"]);
4903 serverconsole.locmessage((isRedirect ? attmtsRedir : attmts) + " attempts left.");
4904 } else {
4905 try {
4906 process.send("\x12ERRLIST" + (isRedirect ? attmtsRedir : attmts) + err.code);
4907 } catch (err) {
4908 // Probably main process exited
4911 if ((isRedirect ? attmtsRedir : attmts) > 0) {
4912 (isRedirect ? server2 : server).close();
4913 setTimeout(start, 900);
4914 } else {
4915 try {
4916 if (cluster.isPrimary !== undefined) process.send("\x12ERRCRASH" + err.code);
4917 } catch (err) {
4918 // Probably main process exited
4920 setTimeout(function () {
4921 var errno = errors[err.code];
4922 process.exit(errno ? errno : 1);
4923 }, 50);
4927 server.on("error", function (err) {
4928 serverErrorHandler(err, false);
4929 });
4930 server.on("listening", function () {
4931 attmts = 5;
4932 listeningMessage();
4933 });
4936 var closedMaster = true;
4938 // IPC listener for server listening signalization
4939 function listenConnListener(msg) {
4940 if (msg == "\x12LISTEN") {
4941 listeningMessage();
4945 // IPC listener for brue force protection
4946 function bruteForceListenerWrapper(worker) {
4947 return function bruteForceListener(message) {
4948 var ip = "";
4949 if (message.substring(0, 6) == "\x12AUTHQ") {
4950 ip = message.substring(6);
4951 if (!bruteForceDb[ip] || !bruteForceDb[ip].lastAttemptDate || (new Date() - 300000 >= bruteForceDb[ip].lastAttemptDate)) {
4952 if (bruteForceDb[ip] && bruteForceDb[ip].invalidAttempts >= 10) bruteForceDb[ip] = {
4953 invalidAttempts: 5
4954 };
4955 worker.send("\x14AUTHA" + ip);
4956 } else {
4957 worker.send("\x14AUTHD" + ip);
4959 } else if (message.substring(0, 6) == "\x12AUTHR") {
4960 ip = message.substring(6);
4961 if (bruteForceDb[ip]) bruteForceDb[ip] = {
4962 invalidAttempts: 0
4963 };
4964 } else if (message.substring(0, 6) == "\x12AUTHW") {
4965 ip = message.substring(6);
4966 if (!bruteForceDb[ip]) bruteForceDb[ip] = {
4967 invalidAttempts: 0
4968 };
4969 bruteForceDb[ip].invalidAttempts++;
4970 if (bruteForceDb[ip].invalidAttempts >= 10) {
4971 bruteForceDb[ip].lastAttemptDate = new Date();
4974 };
4977 var isWorkerHungUpBuff = true;
4978 var isWorkerHungUpBuff2 = true;
4980 // General IPC message listener
4981 function msgListener(msg) {
4982 for (var i = 0; i < Object.keys(cluster.workers).length; i++) {
4983 if (msg == "\x12END") {
4984 cluster.workers[Object.keys(cluster.workers)[i]].removeAllListeners("message");
4985 cluster.workers[Object.keys(cluster.workers)[i]].on("message", bruteForceListenerWrapper(cluster.workers[Object.keys(cluster.workers)[i]]));
4986 cluster.workers[Object.keys(cluster.workers)[i]].on("message", listenConnListener);
4989 if (msg == "\x12CLOSE") {
4990 closedMaster = true;
4991 } else if (msg == "\x12LISTEN" || msg.substring(0, 4) == "\x12AUTH") {
4992 // Do nothing!
4993 } else if (msg == "\x12KILLOK") {
4994 if (typeof isWorkerHungUpBuff != "undefined") isWorkerHungUpBuff = false;
4995 } else if (msg == "\x12PINGOK") {
4996 if (typeof isWorkerHungUpBuff2 != "undefined") isWorkerHungUpBuff2 = false;
4997 } else if (msg == "\x12KILLTERMMSG") {
4998 serverconsole.locmessage("Terminating unused worker process...");
4999 } else if (msg == "\x12SAVEGOOD") {
5000 serverconsole.locmessage("Configuration saved.");
5001 } else if (msg.indexOf("\x12SAVEERR") == 0) {
5002 serverconsole.locwarnmessage("There was a problem while saving configuration file. Reason: " + msg.substring(8));
5003 } else if (msg == "\x12END") {
5004 cluster.workers[Object.keys(cluster.workers)[0]].on("message", function (msg) {
5005 if (msg.length >= 8 && msg.indexOf("\x12ERRLIST") == 0) {
5006 var tries = parseInt(msg.substring(8, 9));
5007 var errCode = msg.substring(9);
5008 serverconsole.locerrmessage(serverErrorDescs[errCode] ? serverErrorDescs[errCode] : serverErrorDescs["UNKNOWN"]);
5009 serverconsole.locmessage(tries + " attempts left.");
5011 if (msg.length >= 9 && msg.indexOf("\x12ERRCRASH") == 0) {
5012 var errno = errors[msg.substring(9)];
5013 process.exit(errno ? errno : 1);
5015 });
5016 } else {
5017 serverconsole.climessage(msg);
5021 var messageTransmitted = false;
5023 function listeningMessage() {
5024 if (typeof closedMaster !== "undefined") closedMaster = false;
5025 if (!cluster.isPrimary && cluster.isPrimary !== undefined) {
5026 process.send("\x12LISTEN");
5027 return;
5029 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$/)));
5030 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}$/));
5031 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$/)));
5032 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}$/));
5033 var accHost = host;
5034 var sAccHost = host;
5035 if (!listenToAny) accHost = listenAddress;
5036 if (!sListenToAny) sAccHost = sListenAddress;
5037 if (messageTransmitted) return;
5038 messageTransmitted = true;
5039 serverconsole.locmessage("Started server at: ");
5040 if (secure && (sListenToLocalhost || sListenToAny)) {
5041 if (typeof sport === "number") {
5042 serverconsole.locmessage("* https://localhost" + (sport == 443 ? "" : (":" + sport)));
5043 } else {
5044 serverconsole.locmessage("* " + sport); // Unix socket or Windows named pipe
5047 if (!(secure && disableNonEncryptedServer) && (listenToLocalhost || listenToAny)) {
5048 if (typeof port === "number") {
5049 serverconsole.locmessage("* http://localhost" + (port == 80 ? "" : (":" + port)));
5050 } else {
5051 serverconsole.locmessage("* " + port); // Unix socket or Windows named pipe
5054 if (secure && typeof sport === "number" && !sListenToLocalhost && (!sListenToAny || (host != "" && host != "[offline]"))) serverconsole.locmessage("* https://" + (sAccHost.indexOf(":") > -1 ? "[" + sAccHost + "]" : sAccHost) + (sport == 443 ? "" : (":" + sport)));
5055 if (!(secure && disableNonEncryptedServer) && !listenToLocalhost && (!listenToAny || (host != "" && host != "[offline]")) && typeof port === "number") serverconsole.locmessage("* http://" + (accHost.indexOf(":") > -1 ? "[" + accHost + "]" : accHost) + (port == 80 ? "" : (":" + port)));
5056 ipStatusCallback(function () {
5057 if (pubip != "") {
5058 if (secure && !sListenToLocalhost) serverconsole.locmessage("* https://" + (pubip.indexOf(":") > -1 ? "[" + pubip + "]" : pubip) + (spubport == 443 ? "" : (":" + spubport)));
5059 if (!(secure && disableNonEncryptedServer) && !listenToLocalhost) serverconsole.locmessage("* http://" + (pubip.indexOf(":") > -1 ? "[" + pubip + "]" : pubip) + (pubport == 80 ? "" : (":" + pubport)));
5061 if (domain != "") {
5062 if (secure && !sListenToLocalhost) serverconsole.locmessage("* https://" + domain + (spubport == 443 ? "" : (":" + spubport)));
5063 if (!(secure && disableNonEncryptedServer) && !listenToLocalhost) serverconsole.locmessage("* http://" + domain + (pubport == 80 ? "" : (":" + pubport)));
5065 serverconsole.locmessage("For CLI help, you can type \"help\"");
5066 });
5069 function start(init) {
5070 init = Boolean(init);
5071 if (cluster.isPrimary || cluster.isPrimary === undefined) {
5072 if (init) {
5073 for (i = 0; i < logo.length; i++) console.log(logo[i]); // Print logo
5074 console.log();
5075 console.log("Welcome to \x1b[1mSVR.JS - a web server running on Node.JS\x1b[0m");
5077 // Print warnings
5078 if (version.indexOf("Nightly-") === 0) serverconsole.locwarnmessage("This version is only for test purposes and may be unstable.");
5079 if (configJSON.enableHTTP2 && !secure) serverconsole.locwarnmessage("HTTP/2 without HTTPS may not work in web browsers. Web browsers only support HTTP/2 with HTTPS!");
5080 if (process.isBun) {
5081 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.");
5082 if (users.some(function (entry) {
5083 return entry.pbkdf2;
5084 })) serverconsole.locwarnmessage("PBKDF2 password hashing function in Bun blocks the event loop, which may result in denial of service.");
5086 if (cluster.isPrimary === undefined) serverconsole.locwarnmessage("You're running SVR.JS on single thread. Reliability may suffer, as the server is stopped after crash.");
5087 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.");
5088 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.");
5089 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).");
5090 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).");
5091 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.");
5092 if (!process.isBun && secure && process.versions && process.versions.openssl && process.versions.openssl.substring(0, 2) == "1.") {
5093 if (new Date() > new Date("11 September 2023")) {
5094 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.");
5095 } else {
5096 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.");
5099 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.");
5100 if (disableMods) serverconsole.locwarnmessage("SVR.JS is running without mods and server-side JavaScript enabled. Web applications may not work as expected");
5101 console.log();
5103 // Display mod and server-side JavaScript errors
5104 if (process.isPrimary || process.isPrimary === undefined) {
5105 modLoadingErrors.forEach(function (modLoadingError) {
5106 serverconsole.locwarnmessage("There was a problem while loading a \"" + String(modLoadingError.modName).replace(/[\r\n]/g, "") + "\" mod.");
5107 serverconsole.locwarnmessage("Stack:");
5108 serverconsole.locwarnmessage(generateErrorStack(modLoadingError.error));
5109 });
5110 if (SSJSError) {
5111 serverconsole.locwarnmessage("There was a problem while loading server-side JavaScript.");
5112 serverconsole.locwarnmessage("Stack:");
5113 serverconsole.locwarnmessage(generateErrorStack(SSJSError));
5115 if (SSJSError || modLoadingErrors.length > 0) console.log();
5118 // Print server information
5119 serverconsole.locmessage("Server version: " + version);
5120 if (process.isBun) serverconsole.locmessage("Bun version: v" + process.versions.bun);
5121 else serverconsole.locmessage("Node.JS version: " + process.version);
5122 var CPUs = os.cpus();
5123 if (CPUs.length > 0) serverconsole.locmessage("CPU: " + (CPUs.length > 1 ? CPUs.length + "x " : "") + CPUs[0].model);
5125 // Throw errors
5126 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.");
5127 if (configJSONRErr) throw new Error("Can't read SVR.JS configuration file: " + configJSONRErr.message);
5128 if (configJSONPErr) throw new Error("SVR.JS configuration parse error: " + configJSONPErr.message);
5129 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.");
5130 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");
5131 if (listenAddress) {
5132 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).");
5133 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.");
5134 if (brdIPs.indexOf(listenAddress) > -1) throw new Error("SVR.JS can't listen on broadcast address.");
5135 if (netIPs.indexOf(listenAddress) > -1) throw new Error("SVR.JS can't listen on subnet address.");
5137 if (certificateError) throw new Error("There was a problem with SSL certificate/private key: " + certificateError.message);
5138 if (wwwrootError) throw new Error("There was a problem with your web root: " + wwwrootError.message);
5139 if (sniReDos) throw new Error("Refusing to start, because the current SNI configuration would make the server vulnerable to ReDoS.");
5142 // Print server startup information
5143 if (!(secure && disableNonEncryptedServer)) serverconsole.locmessage("Starting HTTP server at " + (typeof port == "number" ? (listenAddress ? ((listenAddress.indexOf(":") > -1 ? "[" + listenAddress + "]" : listenAddress)) + ":" : "port ") : "") + port.toString() + "...");
5144 if (secure) serverconsole.locmessage("Starting HTTPS server at " + (typeof sport == "number" ? (sListenAddress ? ((sListenAddress.indexOf(":") > -1 ? "[" + sListenAddress + "]" : sListenAddress)) + ":" : "port ") : "") + sport.toString() + "...");
5148 if (!cluster.isPrimary) {
5149 try {
5150 if (typeof (secure ? sport : port) == "number" && (secure ? sListenAddress : listenAddress)) {
5151 server.listen(secure ? sport : port, secure ? sListenAddress : listenAddress);
5152 } else {
5153 server.listen(secure ? sport : port);
5155 } catch (err) {
5156 if (err.code != "ERR_SERVER_ALREADY_LISTEN") throw err;
5158 if (secure && !disableNonEncryptedServer) {
5159 try {
5160 if (typeof port == "number" && listenAddress) {
5161 server2.listen(port, listenAddress);
5162 } else {
5163 server2.listen(port);
5165 } catch (err) {
5166 if (err.code != "ERR_SERVER_ALREADY_LISTEN") throw err;
5171 // SVR.JS commmands
5172 var commands = {
5173 close: function () {
5174 try {
5175 server.close();
5176 if (secure && !disableNonEncryptedServer) {
5177 server2.close();
5179 if (cluster.isPrimary === undefined) serverconsole.climessage("Server closed.");
5180 else {
5181 process.send("Server closed.");
5182 process.send("\x12CLOSE");
5184 } catch (err) {
5185 if (cluster.isPrimary === undefined) serverconsole.climessage("Cannot close server! Reason: " + err.message);
5186 else process.send("Cannot close server! Reason: " + err.message);
5188 },
5189 open: function () {
5190 try {
5191 if (typeof (secure ? sport : port) == "number" && (secure ? sListenAddress : listenAddress)) {
5192 server.listen(secure ? sport : port, secure ? sListenAddress : listenAddress);
5193 } else {
5194 server.listen(secure ? sport : port);
5196 if (secure && !disableNonEncryptedServer) {