diff --git a/src/handlers/requestHandler.js b/src/handlers/requestHandler.js
index 4912de4..59e041e 100644
--- a/src/handlers/requestHandler.js
+++ b/src/handlers/requestHandler.js
@@ -1,3 +1,11 @@
+const http = require("http");
+const fs = require("fs");
+const net = require("net");
+const generateErrorStack = require("../utils/generateErrorStack.js");
+const serverHTTPErrorDescs = require("../res/httpErrorDescriptions.js");
+const fixNodeMojibakeURL = require("../utils/urlMojibakeFixer.js");
+const ipMatch = require("../utils/ipMatch.js");
+const matchHostname = require("../utils/matchHostname.js");
const generateServerString = require("../utils/generateServerString.js");
let serverconsole = {};
let middleware = [];
@@ -22,6 +30,681 @@ function requestHandler(req, res) {
// SVR.JS configuration object (modified)
const config = Object.assign(process.serverConfig);
+ config.generateServerString = () => {
+ return generateServerString(config.exposeServerVersion);
+ };
+
+ // getCustomHeaders() in SVR.JS 3.x
+ config.getCustomHeaders = () => {
+ let ph = Object.assign(config.customHeaders);
+ if (config.customHeadersVHost) {
+ let vhostP = null;
+ config.customHeadersVHost.every(function (vhost) {
+ if (
+ matchHostname(vhost.host, req.headers.host) &&
+ ipMatch(vhost.ip, req.socket ? req.socket.localAddress : undefined)
+ ) {
+ vhostP = vhost;
+ return false;
+ } else {
+ return true;
+ }
+ });
+ if (vhostP && vhostP.headers) {
+ const phNu = Object.assign(vhostP.headers);
+ Object.keys(phNu).forEach(function (phNuK) {
+ ph[phNuK] = phNu[phNuK];
+ });
+ }
+ }
+ Object.keys(ph).forEach(function (phk) {
+ if (typeof ph[phk] == "string")
+ ph[phk] = ph[phk].replace(/\{path\}/g, req.url);
+ });
+ ph["Server"] = config.generateServerString();
+ return ph;
+ };
+
+ // Estimate fromMain from SVR.JS 3.x
+ let fromMain = !(config.secure && !req.socket.encrypted);
+
+ // Make HTTP/1.x API-based scripts compatible with HTTP/2.0 API
+ if (config.enableHTTP2 == true && req.httpVersion == "2.0") {
+ // Set HTTP/1.x methods (to prevent process warnings)
+ res.writeHeadNodeApi = res.writeHead;
+ res.setHeaderNodeApi = res.setHeader;
+
+ res.writeHead = function (a, b, c) {
+ let table = c;
+ if (typeof b == "object") table = b;
+ if (table == undefined) table = this.tHeaders;
+ if (table == undefined) table = {};
+ table = Object.assign(table);
+ Object.keys(table).forEach(function (key) {
+ const al = key.toLowerCase();
+ if (
+ al == "transfer-encoding" ||
+ al == "connection" ||
+ al == "keep-alive" ||
+ al == "upgrade"
+ )
+ delete table[key];
+ });
+ if (res.stream && res.stream.destroyed) {
+ return false;
+ } else {
+ return res.writeHeadNodeApi(a, table);
+ }
+ };
+ res.setHeader = function (headerName, headerValue) {
+ const al = headerName.toLowerCase();
+ if (
+ al != "transfer-encoding" &&
+ al != "connection" &&
+ al != "keep-alive" &&
+ al != "upgrade"
+ )
+ return res.setHeaderNodeApi(headerName, headerValue);
+ return false;
+ };
+
+ // Set HTTP/1.x headers
+ if (!req.headers.host) req.headers.host = req.headers[":authority"];
+ if (!req.url) req.url = req.headers[":path"];
+ if (!req.protocol) req.protocol = req.headers[":scheme"];
+ if (!req.method) req.method = req.headers[":method"];
+ if (
+ req.headers[":path"] == undefined ||
+ req.headers[":method"] == undefined
+ ) {
+ let err = new Error(
+ 'Either ":path" or ":method" pseudoheader is missing.',
+ );
+ if (Buffer.alloc) err.rawPacket = Buffer.alloc(0);
+ if (req.socket && req.socket.server)
+ req.socket.server.emit("clientError", err, req.socket);
+ }
+ }
+
+ /*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)) {
+ let headers = config.getCustomHeaders();
+ res.writeHead(204, http.STATUS_CODES[204], headers);
+ res.end();
+ return;
+ }*/
+
+ req.url = fixNodeMojibakeURL(req.url);
+
+ let headWritten = false;
+ let lastStatusCode = null;
+ res.writeHeadNative = res.writeHead;
+ res.writeHead = (code, codeDescription, headers) => {
+ if (
+ !(
+ headWritten &&
+ process.isBun &&
+ code === lastStatusCode &&
+ codeDescription === undefined &&
+ codeDescription === undefined
+ )
+ ) {
+ if (headWritten) {
+ process.emitWarning("res.writeHead called multiple times.", {
+ code: "WARN_SVRJS_MULTIPLE_WRITEHEAD",
+ });
+ return res;
+ } else {
+ headWritten = true;
+ }
+ if (code >= 400 && code <= 599) {
+ if (code >= 400 && code <= 499) process.err4xxcounter++;
+ else if (code >= 500 && code <= 599) process.err5xxcounter++;
+ logFacilities.errmessage(
+ "Server responded with " + code.toString() + " code.",
+ );
+ } else {
+ logFacilities.resmessage(
+ "Server responded with " + code.toString() + " code.",
+ );
+ }
+ if (typeof codeDescription != "string" && http.STATUS_CODES[code]) {
+ if (!headers) headers = codeDescription;
+ codeDescription = http.STATUS_CODES[code];
+ }
+ lastStatusCode = code;
+ }
+ res.writeHeadNative(code, codeDescription, headers);
+ };
+
+ let finished = false;
+ res.on("finish", () => {
+ if (!finished) {
+ finished = true;
+ logFacilities.locmessage("Client disconnected.");
+ }
+ });
+ res.on("close", () => {
+ if (!finished) {
+ finished = true;
+ logFacilities.locmessage("Client disconnected.");
+ }
+ });
+
+ req.isProxy = false;
+ if (req.url[0] != "/" && req.url != "*") req.isProxy = true;
+ logFacilities.locmessage(
+ "Somebody connected to " +
+ (config.secure && fromMain
+ ? (typeof config.sport == "number" ? "port " : "socket ") + config.sport
+ : (typeof config.port == "number" ? "port " : "socket ") +
+ config.port) +
+ "...",
+ );
+
+ if (req.socket == null) {
+ logFacilities.errmessage("Client socket is null!!!");
+ return;
+ }
+
+ // Set up X-Forwarded-For
+ let reqip = req.socket.remoteAddress;
+ let reqport = req.socket.remotePort;
+ let oldip = "";
+ let oldport = "";
+ let isForwardedValid = true;
+ if (config.enableIPSpoofing) {
+ if (req.headers["x-forwarded-for"] != undefined) {
+ let preparedReqIP = req.headers["x-forwarded-for"]
+ .split(",")[0]
+ .replace(/ /g, "");
+ let preparedReqIPvalid = net.isIP(preparedReqIP);
+ if (preparedReqIPvalid) {
+ if (
+ preparedReqIPvalid == 4 &&
+ req.socket.remoteAddress &&
+ req.socket.remoteAddress.indexOf(":") > -1
+ )
+ preparedReqIP = "::ffff:" + preparedReqIP;
+ reqip = preparedReqIP;
+ reqport = null;
+ try {
+ oldport = req.socket.remotePort;
+ oldip = req.socket.remoteAddress;
+ req.socket.realRemotePort = reqport;
+ req.socket.realRemoteAddress = reqip;
+ req.socket.originalRemotePort = oldport;
+ req.socket.originalRemoteAddress = oldip;
+ res.socket.realRemotePort = reqport;
+ res.socket.realRemoteAddress = reqip;
+ res.socket.originalRemotePort = oldport;
+ res.socket.originalRemoteAddress = oldip;
+ } catch (err) {
+ // Address setting failed
+ }
+ } else {
+ isForwardedValid = false;
+ }
+ }
+ }
+
+ process.reqcounter++;
+
+ // Process the Host header
+ let oldHostHeader = req.headers.host;
+ if (typeof req.headers.host == "string") {
+ req.headers.host = req.headers.host.toLowerCase();
+ if (!req.headers.host.match(/^\.+$/))
+ req.headers.host = req.headers.host.replace(/\.$/g, "");
+ }
+
+ logFacilities.reqmessage(
+ "Client " +
+ (!reqip || reqip == ""
+ ? "[unknown client]"
+ : reqip +
+ (reqport && reqport !== 0 && reqport != "" ? ":" + reqport : "")) +
+ " wants " +
+ (req.method == "GET"
+ ? "content in "
+ : req.method == "POST"
+ ? "to post content in "
+ : req.method == "PUT"
+ ? "to add content in "
+ : req.method == "DELETE"
+ ? "to delete content in "
+ : req.method == "PATCH"
+ ? "to patch content in "
+ : "to access content using " + req.method + " method in ") +
+ (req.headers.host == undefined || req.isProxy ? "" : req.headers.host) +
+ req.url,
+ );
+ if (req.headers["user-agent"] != undefined)
+ logFacilities.reqmessage("Client uses " + req.headers["user-agent"]);
+ if (oldHostHeader && oldHostHeader != req.headers.host)
+ logFacilities.resmessage(
+ "Host name rewritten: " + oldHostHeader + " => " + req.headers.host,
+ );
+
+ // Header and footer placeholders
+ res.head = "";
+ res.foot = "";
+
+ res.responseEnd = (body) => {
+ // If body is Buffer, then it is converted to String anyway.
+ res.write(res.head + body + res.foot);
+ res.end();
+ };
+
+ // Server error calling method
+ res.error = (errorCode, extName, stack, ch) => {
+ if (typeof errorCode !== "number") {
+ throw new TypeError("HTTP error code parameter needs to be an integer.");
+ }
+
+ // Handle optional parameters
+ if (extName && typeof extName === "object") {
+ ch = stack;
+ stack = extName;
+ extName = undefined;
+ } else if (
+ typeof extName !== "string" &&
+ extName !== null &&
+ extName !== undefined
+ ) {
+ throw new TypeError("Extension name parameter needs to be a string.");
+ }
+
+ if (
+ stack &&
+ typeof stack === "object" &&
+ Object.prototype.toString.call(stack) !== "[object Error]"
+ ) {
+ ch = stack;
+ stack = undefined;
+ } else if (
+ typeof stack !== "object" &&
+ typeof stack !== "string" &&
+ stack
+ ) {
+ throw new TypeError(
+ "Error stack parameter needs to be either a string or an instance of Error object.",
+ );
+ }
+
+ // Determine error file
+ function getErrorFileName(list, callback, _i) {
+ function medCallback(p) {
+ if (p) callback(p);
+ else {
+ if (errorCode == 404) {
+ fs.access(config.page404, fs.constants.F_OK, function (err) {
+ if (err) {
+ fs.access(
+ "." + errorCode.toString(),
+ fs.constants.F_OK,
+ function (err) {
+ try {
+ if (err) {
+ callback(errorCode.toString() + ".html");
+ } else {
+ callback("." + errorCode.toString());
+ }
+ } catch (err2) {
+ res.error(500, err2);
+ }
+ },
+ );
+ } else {
+ try {
+ callback(config.page404);
+ } catch (err2) {
+ res.error(500, err2);
+ }
+ }
+ });
+ } else {
+ fs.access(
+ "." + errorCode.toString(),
+ fs.constants.F_OK,
+ function (err) {
+ try {
+ if (err) {
+ callback(errorCode.toString() + ".html");
+ } else {
+ callback("." + errorCode.toString());
+ }
+ } catch (err2) {
+ res.error(500, err2);
+ }
+ },
+ );
+ }
+ }
+ }
+
+ if (!_i) _i = 0;
+ if (_i >= list.length) {
+ medCallback(false);
+ return;
+ }
+
+ if (
+ list[_i].scode != errorCode ||
+ !(
+ matchHostname(list[_i].host, req.headers.host) &&
+ ipMatch(list[_i].ip, req.socket ? req.socket.localAddress : undefined)
+ )
+ ) {
+ getErrorFileName(list, callback, _i + 1);
+ return;
+ } else {
+ fs.access(list[_i].path, fs.constants.F_OK, function (err) {
+ if (err) {
+ getErrorFileName(list, callback, _i + 1);
+ } else {
+ medCallback(list[_i].path);
+ }
+ });
+ }
+ }
+
+ getErrorFileName(config.errorPages, function (errorFile) {
+ // Generate error stack if not provided
+ if (Object.prototype.toString.call(stack) === "[object Error]")
+ stack = generateErrorStack(stack);
+ if (stack === undefined)
+ stack = generateErrorStack(new Error("Unknown error"));
+
+ if (errorCode == 500 || errorCode == 502) {
+ logFacilities.errmessage(
+ "There was an error while processing the request!",
+ );
+ logFacilities.errmessage("Stack:");
+ logFacilities.errmessage(stack);
+ }
+
+ // Hide the error stack if specified
+ if (config.stackHidden) stack = "[error stack hidden]";
+
+ // Validate the error code and handle unknown codes
+ if (serverHTTPErrorDescs[errorCode] === undefined) {
+ res.error(501, extName, stack);
+ } else {
+ // Process custom headers if provided
+ let cheaders = { ...config.getCustomHeaders(), ...ch };
+
+ cheaders["Content-Type"] = "text/html; charset=utf-8";
+
+ // Set default Allow header for 405 error if not provided
+ if (errorCode == 405 && !cheaders["Allow"])
+ cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
+
+ // Read the error file and replace placeholders with error information
+ fs.readFile(errorFile, function (err, data) {
+ try {
+ if (err) throw err;
+ res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
+ res.responseEnd(
+ data
+ .toString()
+ .replace(
+ /{errorMessage}/g,
+ errorCode.toString() +
+ " " +
+ http.STATUS_CODES[errorCode]
+ .replace(/&/g, "&")
+ .replace(//g, ">"),
+ )
+ .replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
+ .replace(
+ /{stack}/g,
+ stack
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\r\n/g, "
")
+ .replace(/\n/g, "
")
+ .replace(/\r/g, "
")
+ .replace(/ {2}/g, " "),
+ )
+ .replace(
+ /{path}/g,
+ req.url
+ .replace(/&/g, "&")
+ .replace(//g, ">"),
+ )
+ .replace(
+ /{server}/g,
+ "" +
+ (
+ config.generateServerString() +
+ (!config.exposeModsInErrorPages || extName == undefined
+ ? ""
+ : " " + extName)
+ )
+ .replace(/&/g, "&")
+ .replace(//g, ">") +
+ (req.headers.host == undefined || req.isProxy
+ ? ""
+ : " on " +
+ String(req.headers.host)
+ .replace(/&/g, "&")
+ .replace(//g, ">")),
+ )
+ .replace(
+ /{contact}/g,
+ config.serverAdministratorEmail
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\./g, "[dot]")
+ .replace(/@/g, "[at]"),
+ ),
+ ); // Replace placeholders in error response
+ } catch (err) {
+ let additionalError = 500;
+ // Handle additional error cases
+ if (err.code == "ENOENT") {
+ additionalError = 404;
+ } else if (err.code == "ENOTDIR") {
+ additionalError = 404; // Assume that file doesn't exist
+ } else if (err.code == "EACCES") {
+ additionalError = 403;
+ } else if (err.code == "ENAMETOOLONG") {
+ additionalError = 414;
+ } else if (err.code == "EMFILE") {
+ additionalError = 503;
+ } else if (err.code == "ELOOP") {
+ additionalError = 508;
+ }
+
+ res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
+ res.write(
+ (
+ '
{errorDesc}
' + + (additionalError == 404 + ? "" + : "Additionally, a {additionalError} error occurred while loading an error page.
") + + "{server}
" + ) + .replace( + /{errorMessage}/g, + errorCode.toString() + + " " + + http.STATUS_CODES[errorCode] + .replace(/&/g, "&") + .replace(//g, ">"), + ) + .replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]) + .replace( + /{stack}/g, + stack + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/\r\n/g, "{errorDesc}
' + - (additionalError == 404 - ? "" - : "Additionally, a {additionalError} error occurred while loading an error page.
") + - "{server}
" - ) - .replace( - /{errorMessage}/g, - errorCode.toString() + - " " + - http.STATUS_CODES[errorCode] - .replace(/&/g, "&") - .replace(//g, ">"), - ) - .replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]) - .replace( - /{stack}/g, - stack - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/\r\n/g, "