diff --git a/src/handlers/clientErrorHandler.js b/src/handlers/clientErrorHandler.js
new file mode 100644
index 0000000..713fa8f
--- /dev/null
+++ b/src/handlers/clientErrorHandler.js
@@ -0,0 +1,580 @@
+const fs = require("fs");
+const http = require("http");
+const generateErrorStack = require("../utils/generateErrorStack.js");
+const serverHTTPErrorDescs = require("../res/httpErrorDescriptions.js");
+const getOS = require("../utils/getOS.js");
+const generateServerString = require("../utils/generateServerString.js");
+let serverconsole = {};
+
+function clientErrorHandler(err, socket) {
+ const config = Object.assign(process.serverConfig);
+
+ config.generateServerString = () =>
+ generateServerString(config.exposeServerVersion);
+
+ // getCustomHeaders() in SVR.JS 3.x
+ config.getCustomHeaders = () => Object.assign(config.customHeaders);
+
+ // Prevent multiple error handlers from one request
+ if (socket.__assigned__) {
+ return;
+ } else {
+ socket.__assigned__ = true;
+ }
+
+ // Estimate fromMain from SVR.JS 3.x
+ let fromMain = !(
+ config.secure &&
+ !socket.encrypted &&
+ socket.localPort == config.sport
+ );
+
+ // Define response object similar to Node.JS native one
+ let res = {
+ socket: socket,
+ write: (x) => {
+ if (err.code === "ECONNRESET" || !socket.writable) {
+ return;
+ }
+ socket.write(x);
+ },
+ end: (x) => {
+ if (err.code === "ECONNRESET" || !socket.writable) {
+ return;
+ }
+ socket.end(x, function () {
+ try {
+ socket.destroy();
+ } catch (err) {
+ // Socket is probably already destroyed
+ }
+ });
+ },
+ writeHead: (code, name, headers) => {
+ if (code >= 400 && code <= 499) process.err4xxcounter++;
+ if (code >= 500 && code <= 599) process.err5xxcounter++;
+ let head = "HTTP/1.1 " + code.toString() + " " + name + "\r\n";
+ headers = Object.assign(headers);
+ headers["Date"] = new Date().toGMTString();
+ headers["Connection"] = "close";
+ Object.keys(headers).forEach(function (headername) {
+ if (headername.toLowerCase() == "set-cookie") {
+ headers[headername].forEach(function (headerValueS) {
+ if (
+ headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) ||
+ headerValueS.match(/[^\x09\x20-\x7e\x80-\xff]/)
+ )
+ throw new Error("Invalid header!!! (" + headername + ")");
+ head += headername + ": " + headerValueS;
+ });
+ } else {
+ if (
+ headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) ||
+ headers[headername].match(/[^\x09\x20-\x7e\x80-\xff]/)
+ )
+ throw new Error("Invalid header!!! (" + headername + ")");
+ head += headername + ": " + headers[headername];
+ }
+ head += "\r\n";
+ });
+ head += "\r\n";
+ res.write(head);
+ },
+ };
+
+ let reqIdInt = Math.floor(Math.random() * 16777216);
+ if (reqIdInt == 16777216) reqIdInt = 0;
+ let reqId =
+ "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
+
+ // SVR.JS log facilities
+ const logFacilities = {
+ climessage: (msg) => serverconsole.climessage(msg, reqId),
+ reqmessage: (msg) => serverconsole.reqmessage(msg, reqId),
+ resmessage: (msg) => serverconsole.resmessage(msg, reqId),
+ errmessage: (msg) => serverconsole.errmessage(msg, reqId),
+ locerrmessage: (msg) => serverconsole.locerrmessage(msg, reqId),
+ locwarnmessage: (msg) => serverconsole.locwarnmessage(msg, reqId),
+ locmessage: (msg) => serverconsole.locmessage(msg, reqId),
+ };
+
+ socket.on("close", function (hasError) {
+ if (
+ !hasError ||
+ err.code == "ERR_SSL_HTTP_REQUEST" ||
+ err.message.indexOf("http request") != -1
+ )
+ logFacilities.locmessage("Client disconnected.");
+ else logFacilities.locmessage("Client disconnected due to error.");
+ });
+ socket.on("error", function () {});
+
+ // Header and footer placeholders
+ let head = "";
+ let foot = "";
+
+ const responseEnd = (body) => {
+ // If body is Buffer, then it is converted to String anyway.
+ res.write(head + body + foot);
+ res.end();
+ };
+
+ // Server error calling method
+ const callServerError = (errorCode, extName, stack, ch) => {
+ if (typeof errorCode !== "number") {
+ throw new TypeError("HTTP error code parameter needs to be an integer.");
+ }
+
+ // Handle optional parameters
+ if (extName && typeof extName === "object") {
+ ch = stack;
+ stack = extName;
+ extName = undefined;
+ } else if (
+ typeof extName !== "string" &&
+ extName !== null &&
+ extName !== undefined
+ ) {
+ throw new TypeError("Extension name parameter needs to be a string.");
+ }
+
+ if (
+ stack &&
+ typeof stack === "object" &&
+ Object.prototype.toString.call(stack) !== "[object Error]"
+ ) {
+ ch = stack;
+ stack = undefined;
+ } else if (
+ typeof stack !== "object" &&
+ typeof stack !== "string" &&
+ stack
+ ) {
+ throw new TypeError(
+ "Error stack parameter needs to be either a string or an instance of Error object.",
+ );
+ }
+
+ // Determine error file
+ const getErrorFileName = (list, callback, _i) => {
+ if (
+ err.code == "ERR_SSL_HTTP_REQUEST" &&
+ process.version &&
+ parseInt(process.version.split(".")[0].substring(1)) >= 16
+ ) {
+ // Disable custom error page for HTTP SSL error
+ callback(errorCode.toString() + ".html");
+ return;
+ }
+
+ const 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) {
+ callServerError(500, err2);
+ }
+ },
+ );
+ } else {
+ try {
+ callback(config.page404);
+ } catch (err2) {
+ callServerError(500, err2);
+ }
+ }
+ });
+ } else {
+ fs.access(
+ "." + errorCode.toString(),
+ fs.constants.F_OK,
+ function (err) {
+ try {
+ if (err) {
+ callback(errorCode.toString() + ".html");
+ } else {
+ callback("." + errorCode.toString());
+ }
+ } catch (err2) {
+ callServerError(500, err2);
+ }
+ },
+ );
+ }
+ }
+ };
+
+ if (!_i) _i = 0;
+ if (_i >= list.length) {
+ medCallback(false);
+ return;
+ }
+
+ if (list[_i].scode != errorCode) {
+ getErrorFileName(list, callback, _i + 1);
+ return;
+ } else {
+ fs.access(list[_i].path, fs.constants.F_OK, function (err) {
+ if (err) {
+ getErrorFileName(list, callback, _i + 1);
+ } else {
+ medCallback(list[_i].path);
+ }
+ });
+ }
+ };
+
+ getErrorFileName(config.errorPages, function (errorFile) {
+ if (Object.prototype.toString.call(stack) === "[object Error]")
+ stack = generateErrorStack(stack);
+ if (stack === undefined)
+ stack = generateErrorStack(new Error("Unknown error"));
+ if (errorCode == 500 || errorCode == 502) {
+ logFacilities.errmessage(
+ "There was an error while processing the request!",
+ );
+ logFacilities.errmessage("Stack:");
+ logFacilities.errmessage(stack);
+ }
+ if (config.stackHidden) stack = "[error stack hidden]";
+ if (serverHTTPErrorDescs[errorCode] === undefined) {
+ callServerError(501, extName, stack);
+ } else {
+ let cheaders = { ...config.getCustomHeaders(), ...ch };
+ cheaders["Content-Type"] = "text/html; charset=utf-8";
+ if (errorCode == 405 && !cheaders["Allow"])
+ cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
+ if (
+ err.code == "ERR_SSL_HTTP_REQUEST" &&
+ process.version &&
+ parseInt(process.version.split(".")[0].substring(1)) >= 16
+ ) {
+ // Disable custom error page for HTTP SSL error
+ res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
+ res.write(
+ '
{errorMessage}{errorMessage}
{errorDesc}
{server}
'
+ .replace(
+ /{errorMessage}/g,
+ errorCode.toString() +
+ " " +
+ http.STATUS_CODES[errorCode]
+ .replace(/&/g, "&")
+ .replace(//g, ">"),
+ )
+ .replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
+ .replace(
+ /{stack}/g,
+ stack
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\r\n/g, "
")
+ .replace(/\n/g, "
")
+ .replace(/\r/g, "
")
+ .replace(/ {2}/g, " "),
+ )
+ .replace(
+ /{server}/g,
+ "" +
+ (
+ config.generateServerString() +
+ (!config.exposeModsInErrorPages || extName == undefined
+ ? ""
+ : " " + extName)
+ )
+ .replace(/&/g, "&")
+ .replace(//g, ">"),
+ )
+ .replace(
+ /{contact}/g,
+ config.serverAdministratorEmail
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\./g, "[dot]")
+ .replace(/@/g, "[at]"),
+ ),
+ );
+ res.end();
+ } else {
+ fs.readFile(errorFile, function (err, data) {
+ try {
+ if (err) throw err;
+ res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
+ responseEnd(
+ data
+ .toString()
+ .replace(
+ /{errorMessage}/g,
+ errorCode.toString() +
+ " " +
+ http.STATUS_CODES[errorCode]
+ .replace(/&/g, "&")
+ .replace(//g, ">"),
+ )
+ .replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
+ .replace(
+ /{stack}/g,
+ stack
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\r\n/g, "
")
+ .replace(/\n/g, "
")
+ .replace(/\r/g, "
")
+ .replace(/ {2}/g, " "),
+ )
+ .replace(
+ /{server}/g,
+ "" +
+ (
+ config.generateServerString() +
+ (!config.exposeModsInErrorPages || extName == undefined
+ ? ""
+ : " " + extName)
+ )
+ .replace(/&/g, "&")
+ .replace(//g, ">"),
+ )
+ .replace(
+ /{contact}/g,
+ config.serverAdministratorEmail
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\./g, "[dot]")
+ .replace(/@/g, "[at]"),
+ ),
+ );
+ } catch (err) {
+ let additionalError = 500;
+ if (err.code == "ENOENT") {
+ additionalError = 404;
+ } else if (err.code == "ENOTDIR") {
+ additionalError = 404; // Assume that file doesn't exist
+ } else if (err.code == "EACCES") {
+ additionalError = 403;
+ } else if (err.code == "ENAMETOOLONG") {
+ additionalError = 414;
+ } else if (err.code == "EMFILE") {
+ additionalError = 503;
+ } else if (err.code == "ELOOP") {
+ additionalError = 508;
+ }
+ res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
+ res.write(
+ (
+ '{errorMessage}{errorMessage}
{errorDesc}
' +
+ (additionalError == 404
+ ? ""
+ : "Additionally, a {additionalError} error occurred while loading an error page.
") +
+ "{server}
"
+ )
+ .replace(
+ /{errorMessage}/g,
+ errorCode.toString() +
+ " " +
+ http.STATUS_CODES[errorCode]
+ .replace(/&/g, "&")
+ .replace(//g, ">"),
+ )
+ .replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
+ .replace(
+ /{stack}/g,
+ stack
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\r\n/g, "
")
+ .replace(/\n/g, "
")
+ .replace(/\r/g, "
")
+ .replace(/ {2}/g, " "),
+ )
+ .replace(
+ /{server}/g,
+ (config.generateServerString() +
+ (config.exposeModsInErrorPages || extName == undefined)
+ ? ""
+ : " " + extName
+ )
+ .replace(/&/g, "&")
+ .replace(//g, ">"),
+ )
+ .replace(
+ /{contact}/g,
+ config.serverAdministratorEmail
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\./g, "[dot]")
+ .replace(/@/g, "[at]"),
+ )
+ .replace(/{additionalError}/g, additionalError.toString()),
+ );
+ res.end();
+ }
+ });
+ }
+ }
+ });
+ };
+ let reqip = socket.remoteAddress;
+ let reqport = socket.remotePort;
+ process.reqcounter++;
+ process.malformedcounter++;
+ logFacilities.locmessage(
+ "Somebody connected to " +
+ (config.secure && fromMain
+ ? (typeof config.sport == "number" ? "port " : "socket ") + config.sport
+ : (typeof config.port == "number" ? "port " : "socket ") +
+ config.port) +
+ "...",
+ );
+ logFacilities.reqmessage(
+ "Client " +
+ (!reqip || reqip == ""
+ ? "[unknown client]"
+ : reqip +
+ (reqport && reqport !== 0 && reqport != "" ? ":" + reqport : "")) +
+ " sent invalid request.",
+ );
+ try {
+ head = fs.existsSync("./.head")
+ ? fs.readFileSync("./.head").toString()
+ : fs.existsSync("./head.html")
+ ? fs.readFileSync("./head.html").toString()
+ : ""; // header
+ foot = fs.existsSync("./.foot")
+ ? fs.readFileSync("./.foot").toString()
+ : fs.existsSync("./foot.html")
+ ? fs.readFileSync("./foot.html").toString()
+ : ""; // footer
+
+ if (
+ (err.code &&
+ (err.code.indexOf("ERR_SSL_") == 0 ||
+ err.code.indexOf("ERR_TLS_") == 0)) ||
+ (!err.code && err.message.indexOf("SSL routines") != -1)
+ ) {
+ if (
+ err.code == "ERR_SSL_HTTP_REQUEST" ||
+ err.message.indexOf("http request") != -1
+ ) {
+ logFacilities.errmessage("Client sent HTTP request to HTTPS port.");
+ callServerError(497);
+ return;
+ } else {
+ logFacilities.errmessage(
+ "An SSL error occured: " + (err.code ? err.code : err.message),
+ );
+ callServerError(400);
+ return;
+ }
+ }
+
+ if (err.code && err.code.indexOf("ERR_HTTP2_") == 0) {
+ logFacilities.errmessage("An HTTP/2 error occured: " + err.code);
+ callServerError(400);
+ return;
+ }
+
+ if (err.code && err.code == "ERR_HTTP_REQUEST_TIMEOUT") {
+ logFacilities.errmessage("Client timed out.");
+ callServerError(408);
+ return;
+ }
+
+ if (!err.rawPacket) {
+ logFacilities.errmessage("Connection ended prematurely.");
+ callServerError(400);
+ return;
+ }
+
+ const packetLines = err.rawPacket.toString().split("\r\n");
+ if (packetLines.length == 0) {
+ logFacilities.errmessage("Invalid request.");
+ callServerError(400);
+ return;
+ }
+
+ const checkHeaders = (beginsFromFirst) => {
+ for (let i = beginsFromFirst ? 0 : 1; i < packetLines.length; i++) {
+ const header = packetLines[i];
+ if (header == "")
+ return false; // Beginning of body
+ else if (header.indexOf(":") < 1) {
+ logFacilities.errmessage("Invalid header.");
+ callServerError(400);
+ return true;
+ } else if (header.length > 8192) {
+ logFacilities.errmessage("Header too large.");
+ callServerError(431); // Headers too large
+ return true;
+ }
+ }
+ return false;
+ };
+ const packetLine1 = packetLines[0].split(" ");
+ let method = "GET";
+ let httpVersion = "HTTP/1.1";
+ if (String(packetLine1[0]).indexOf(":") > 0) {
+ if (!checkHeaders(true)) {
+ logFacilities.errmessage(
+ "The request is invalid (it may be a part of larger invalid request).",
+ );
+ callServerError(400); // Also malformed Packet
+ return;
+ }
+ }
+ if (String(packetLine1[0]).length < 50) method = packetLine1.shift();
+ if (String(packetLine1[packetLine1.length - 1]).length < 50)
+ httpVersion = packetLine1.pop();
+ if (packetLine1.length != 1) {
+ logFacilities.errmessage("The head of request is invalid.");
+ callServerError(400); // Malformed Packet
+ } else if (!httpVersion.toString().match(/^HTTP[\/]/i)) {
+ logFacilities.errmessage("Invalid protocol.");
+ callServerError(400); // bad protocol version
+ } else if (http.METHODS.indexOf(method) == -1) {
+ logFacilities.errmessage("Invalid method.");
+ callServerError(405); // Also malformed Packet
+ } else {
+ if (checkHeaders(false)) return;
+ if (packetLine1[0].length > 255) {
+ logFacilities.errmessage("URI too long.");
+ callServerError(414); // Also malformed Packet
+ } else {
+ logFacilities.errmessage("The request is invalid.");
+ callServerError(400); // Also malformed Packet
+ }
+ }
+ } catch (err) {
+ logFacilities.errmessage(
+ "There was an error while determining type of malformed request.",
+ );
+ callServerError(400);
+ }
+}
+
+module.exports = (serverconsoleO) => {
+ serverconsole = serverconsoleO;
+ return clientErrorHandler;
+};
diff --git a/src/handlers/requestHandler.js b/src/handlers/requestHandler.js
new file mode 100644
index 0000000..4912de4
--- /dev/null
+++ b/src/handlers/requestHandler.js
@@ -0,0 +1,66 @@
+const generateServerString = require("../utils/generateServerString.js");
+let serverconsole = {};
+let middleware = [];
+
+function requestHandler(req, res) {
+ let reqIdInt = Math.floor(Math.random() * 16777216);
+ if (reqIdInt == 16777216) reqIdInt = 0;
+ const reqId =
+ "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
+
+ // SVR.JS log facilities
+ const logFacilities = {
+ climessage: (msg) => serverconsole.climessage(msg, reqId),
+ reqmessage: (msg) => serverconsole.reqmessage(msg, reqId),
+ resmessage: (msg) => serverconsole.resmessage(msg, reqId),
+ errmessage: (msg) => serverconsole.errmessage(msg, reqId),
+ locerrmessage: (msg) => serverconsole.locerrmessage(msg, reqId),
+ locwarnmessage: (msg) => serverconsole.locwarnmessage(msg, reqId),
+ locmessage: (msg) => serverconsole.locmessage(msg, reqId),
+ };
+
+ // SVR.JS configuration object (modified)
+ const config = Object.assign(process.serverConfig);
+
+ let index = 0;
+
+ // Call the next middleware function
+ const next = () => {
+ const currentMiddleware = middleware[index++];
+ if (currentMiddleware) {
+ try {
+ currentMiddleware(req, res, logFacilities, config, next);
+ } catch (err) {
+ if (res.error) res.error(500, err);
+ else {
+ logFacilities.errmessage(
+ "There was an error while processing the request!",
+ );
+ logFacilities.errmessage("Stack:");
+ logFacilities.errmessage(err.stack);
+ res.writeHead(500, "Internal Server Error", {
+ Server: generateServerString(config.exposeServerVersion),
+ });
+ res.end("Error while executing the request handler");
+ }
+ }
+ } else {
+ if (res.error) res.error(404);
+ else {
+ res.writeHead(404, "Not Found", {
+ Server: generateServerString(config.exposeServerVersion),
+ });
+ res.end("Request handler missing");
+ }
+ }
+ };
+
+ // Handle middleware
+ next();
+}
+
+module.exports = (serverconsoleO, middlewareO) => {
+ serverconsole = serverconsoleO;
+ middleware = middlewareO;
+ return requestHandler;
+};
diff --git a/src/index.js b/src/index.js
index 98b511f..42f3f84 100644
--- a/src/index.js
+++ b/src/index.js
@@ -117,9 +117,9 @@ if (!fs.existsSync(process.dirname + "/temp"))
fs.mkdirSync(process.dirname + "/temp");
const cluster = require("./utils/clusterBunShim.js"); // Cluster module with shim for Bun
-const generateErrorStack = require("./utils/generateErrorStack.js");
-const serverHTTPErrorDescs = require("../res/httpErrorDescriptions.js");
-const getOS = require("./utils/getOS.js");
+//const generateErrorStack = require("./utils/generateErrorStack.js");
+//const serverHTTPErrorDescs = require("../res/httpErrorDescriptions.js");
+//const getOS = require("./utils/getOS.js");
//const parseURL = require("./utils/urlParser.js");
//const fixNodeMojibakeURL = require("./utils/urlMojibakeFixer.js");
@@ -299,630 +299,14 @@ function addMiddleware(mw) {
middleware.push(mw);
}
-function requestHandler(req, res) {
- let reqIdInt = Math.floor(Math.random() * 16777216);
- if (reqIdInt == 16777216) reqIdInt = 0;
- const reqId =
- "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
+const requestHandler = require("./handlers/requestHandler.js")(
+ serverconsole,
+ middleware,
+);
- // SVR.JS log facilities
- const logFacilities = {
- climessage: (msg) => serverconsole.climessage(msg, reqId),
- reqmessage: (msg) => serverconsole.reqmessage(msg, reqId),
- resmessage: (msg) => serverconsole.resmessage(msg, reqId),
- errmessage: (msg) => serverconsole.errmessage(msg, reqId),
- locerrmessage: (msg) => serverconsole.locerrmessage(msg, reqId),
- locwarnmessage: (msg) => serverconsole.locwarnmessage(msg, reqId),
- locmessage: (msg) => serverconsole.locmessage(msg, reqId),
- };
-
- // SVR.JS configuration object (modified)
- const config = Object.assign(process.serverConfig);
-
- let index = 0;
-
- // Call the next middleware function
- const next = () => {
- const currentMiddleware = middleware[index++];
- if (currentMiddleware) {
- try {
- currentMiddleware(req, res, logFacilities, config, next);
- } catch (err) {
- if (res.error) res.error(500, err);
- else {
- logFacilities.errmessage(
- "There was an error while processing the request!",
- );
- logFacilities.errmessage("Stack:");
- logFacilities.errmessage(err.stack);
- res.writeHead(500, "Internal Server Error", {
- Server: generateServerString(config.exposeServerVersion),
- });
- res.end("Error while executing the request handler");
- }
- }
- } else {
- if (res.error) res.error(404);
- else {
- res.writeHead(404, "Not Found", {
- Server: generateServerString(config.exposeServerVersion),
- });
- res.end("Request handler missing");
- }
- }
- };
-
- // Handle middleware
- next();
-}
-
-function clientErrorHandler(err, socket) {
- const config = Object.assign(process.serverConfig);
-
- config.generateServerString = () =>
- generateServerString(config.exposeServerVersion);
-
- // getCustomHeaders() in SVR.JS 3.x
- config.getCustomHeaders = () => Object.assign(config.customHeaders);
-
- // Prevent multiple error handlers from one request
- if (socket.__assigned__) {
- return;
- } else {
- socket.__assigned__ = true;
- }
-
- // Estimate fromMain from SVR.JS 3.x
- let fromMain = !(
- config.secure &&
- !socket.encrypted &&
- socket.localPort == config.sport
- );
-
- // Define response object similar to Node.JS native one
- let res = {
- socket: socket,
- write: (x) => {
- if (err.code === "ECONNRESET" || !socket.writable) {
- return;
- }
- socket.write(x);
- },
- end: (x) => {
- if (err.code === "ECONNRESET" || !socket.writable) {
- return;
- }
- socket.end(x, function () {
- try {
- socket.destroy();
- } catch (err) {
- // Socket is probably already destroyed
- }
- });
- },
- writeHead: (code, name, headers) => {
- if (code >= 400 && code <= 499) process.err4xxcounter++;
- if (code >= 500 && code <= 599) process.err5xxcounter++;
- let head = "HTTP/1.1 " + code.toString() + " " + name + "\r\n";
- headers = Object.assign(headers);
- headers["Date"] = new Date().toGMTString();
- headers["Connection"] = "close";
- Object.keys(headers).forEach(function (headername) {
- if (headername.toLowerCase() == "set-cookie") {
- headers[headername].forEach(function (headerValueS) {
- if (
- headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) ||
- headerValueS.match(/[^\x09\x20-\x7e\x80-\xff]/)
- )
- throw new Error("Invalid header!!! (" + headername + ")");
- head += headername + ": " + headerValueS;
- });
- } else {
- if (
- headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) ||
- headers[headername].match(/[^\x09\x20-\x7e\x80-\xff]/)
- )
- throw new Error("Invalid header!!! (" + headername + ")");
- head += headername + ": " + headers[headername];
- }
- head += "\r\n";
- });
- head += "\r\n";
- res.write(head);
- },
- };
-
- let reqIdInt = Math.floor(Math.random() * 16777216);
- if (reqIdInt == 16777216) reqIdInt = 0;
- let reqId =
- "0".repeat(6 - reqIdInt.toString(16).length) + reqIdInt.toString(16);
-
- // SVR.JS log facilities
- const logFacilities = {
- climessage: (msg) => serverconsole.climessage(msg, reqId),
- reqmessage: (msg) => serverconsole.reqmessage(msg, reqId),
- resmessage: (msg) => serverconsole.resmessage(msg, reqId),
- errmessage: (msg) => serverconsole.errmessage(msg, reqId),
- locerrmessage: (msg) => serverconsole.locerrmessage(msg, reqId),
- locwarnmessage: (msg) => serverconsole.locwarnmessage(msg, reqId),
- locmessage: (msg) => serverconsole.locmessage(msg, reqId),
- };
-
- socket.on("close", function (hasError) {
- if (
- !hasError ||
- err.code == "ERR_SSL_HTTP_REQUEST" ||
- err.message.indexOf("http request") != -1
- )
- logFacilities.locmessage("Client disconnected.");
- else logFacilities.locmessage("Client disconnected due to error.");
- });
- socket.on("error", function () {});
-
- // Header and footer placeholders
- let head = "";
- let foot = "";
-
- const responseEnd = (body) => {
- // If body is Buffer, then it is converted to String anyway.
- res.write(head + body + foot);
- res.end();
- };
-
- // Server error calling method
- const callServerError = (errorCode, extName, stack, ch) => {
- if (typeof errorCode !== "number") {
- throw new TypeError("HTTP error code parameter needs to be an integer.");
- }
-
- // Handle optional parameters
- if (extName && typeof extName === "object") {
- ch = stack;
- stack = extName;
- extName = undefined;
- } else if (
- typeof extName !== "string" &&
- extName !== null &&
- extName !== undefined
- ) {
- throw new TypeError("Extension name parameter needs to be a string.");
- }
-
- if (
- stack &&
- typeof stack === "object" &&
- Object.prototype.toString.call(stack) !== "[object Error]"
- ) {
- ch = stack;
- stack = undefined;
- } else if (
- typeof stack !== "object" &&
- typeof stack !== "string" &&
- stack
- ) {
- throw new TypeError(
- "Error stack parameter needs to be either a string or an instance of Error object.",
- );
- }
-
- // Determine error file
- const getErrorFileName = (list, callback, _i) => {
- if (
- err.code == "ERR_SSL_HTTP_REQUEST" &&
- process.version &&
- parseInt(process.version.split(".")[0].substring(1)) >= 16
- ) {
- // Disable custom error page for HTTP SSL error
- callback(errorCode.toString() + ".html");
- return;
- }
-
- const 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) {
- callServerError(500, err2);
- }
- },
- );
- } else {
- try {
- callback(config.page404);
- } catch (err2) {
- callServerError(500, err2);
- }
- }
- });
- } else {
- fs.access(
- "." + errorCode.toString(),
- fs.constants.F_OK,
- function (err) {
- try {
- if (err) {
- callback(errorCode.toString() + ".html");
- } else {
- callback("." + errorCode.toString());
- }
- } catch (err2) {
- callServerError(500, err2);
- }
- },
- );
- }
- }
- };
-
- if (!_i) _i = 0;
- if (_i >= list.length) {
- medCallback(false);
- return;
- }
-
- if (list[_i].scode != errorCode) {
- getErrorFileName(list, callback, _i + 1);
- return;
- } else {
- fs.access(list[_i].path, fs.constants.F_OK, function (err) {
- if (err) {
- getErrorFileName(list, callback, _i + 1);
- } else {
- medCallback(list[_i].path);
- }
- });
- }
- };
-
- getErrorFileName(config.errorPages, function (errorFile) {
- if (Object.prototype.toString.call(stack) === "[object Error]")
- stack = generateErrorStack(stack);
- if (stack === undefined)
- stack = generateErrorStack(new Error("Unknown error"));
- if (errorCode == 500 || errorCode == 502) {
- logFacilities.errmessage(
- "There was an error while processing the request!",
- );
- logFacilities.errmessage("Stack:");
- logFacilities.errmessage(stack);
- }
- if (config.stackHidden) stack = "[error stack hidden]";
- if (serverHTTPErrorDescs[errorCode] === undefined) {
- callServerError(501, extName, stack);
- } else {
- let cheaders = { ...config.getCustomHeaders(), ...ch };
- cheaders["Content-Type"] = "text/html; charset=utf-8";
- if (errorCode == 405 && !cheaders["Allow"])
- cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
- if (
- err.code == "ERR_SSL_HTTP_REQUEST" &&
- process.version &&
- parseInt(process.version.split(".")[0].substring(1)) >= 16
- ) {
- // Disable custom error page for HTTP SSL error
- res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
- res.write(
- '{errorMessage}{errorMessage}
{errorDesc}
{server}
'
- .replace(
- /{errorMessage}/g,
- errorCode.toString() +
- " " +
- http.STATUS_CODES[errorCode]
- .replace(/&/g, "&")
- .replace(//g, ">"),
- )
- .replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
- .replace(
- /{stack}/g,
- stack
- .replace(/&/g, "&")
- .replace(//g, ">")
- .replace(/\r\n/g, "
")
- .replace(/\n/g, "
")
- .replace(/\r/g, "
")
- .replace(/ {2}/g, " "),
- )
- .replace(
- /{server}/g,
- "" +
- (
- config.generateServerString() +
- (!config.exposeModsInErrorPages || extName == undefined
- ? ""
- : " " + extName)
- )
- .replace(/&/g, "&")
- .replace(//g, ">"),
- )
- .replace(
- /{contact}/g,
- config.serverAdministratorEmail
- .replace(/&/g, "&")
- .replace(//g, ">")
- .replace(/\./g, "[dot]")
- .replace(/@/g, "[at]"),
- ),
- );
- res.end();
- } else {
- fs.readFile(errorFile, function (err, data) {
- try {
- if (err) throw err;
- res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
- responseEnd(
- data
- .toString()
- .replace(
- /{errorMessage}/g,
- errorCode.toString() +
- " " +
- http.STATUS_CODES[errorCode]
- .replace(/&/g, "&")
- .replace(//g, ">"),
- )
- .replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
- .replace(
- /{stack}/g,
- stack
- .replace(/&/g, "&")
- .replace(//g, ">")
- .replace(/\r\n/g, "
")
- .replace(/\n/g, "
")
- .replace(/\r/g, "
")
- .replace(/ {2}/g, " "),
- )
- .replace(
- /{server}/g,
- "" +
- (
- config.generateServerString() +
- (!config.exposeModsInErrorPages || extName == undefined
- ? ""
- : " " + extName)
- )
- .replace(/&/g, "&")
- .replace(//g, ">"),
- )
- .replace(
- /{contact}/g,
- config.serverAdministratorEmail
- .replace(/&/g, "&")
- .replace(//g, ">")
- .replace(/\./g, "[dot]")
- .replace(/@/g, "[at]"),
- ),
- );
- } catch (err) {
- let additionalError = 500;
- if (err.code == "ENOENT") {
- additionalError = 404;
- } else if (err.code == "ENOTDIR") {
- additionalError = 404; // Assume that file doesn't exist
- } else if (err.code == "EACCES") {
- additionalError = 403;
- } else if (err.code == "ENAMETOOLONG") {
- additionalError = 414;
- } else if (err.code == "EMFILE") {
- additionalError = 503;
- } else if (err.code == "ELOOP") {
- additionalError = 508;
- }
- res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);
- res.write(
- (
- '{errorMessage}{errorMessage}
{errorDesc}
' +
- (additionalError == 404
- ? ""
- : "Additionally, a {additionalError} error occurred while loading an error page.
") +
- "{server}
"
- )
- .replace(
- /{errorMessage}/g,
- errorCode.toString() +
- " " +
- http.STATUS_CODES[errorCode]
- .replace(/&/g, "&")
- .replace(//g, ">"),
- )
- .replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
- .replace(
- /{stack}/g,
- stack
- .replace(/&/g, "&")
- .replace(//g, ">")
- .replace(/\r\n/g, "
")
- .replace(/\n/g, "
")
- .replace(/\r/g, "
")
- .replace(/ {2}/g, " "),
- )
- .replace(
- /{server}/g,
- (config.generateServerString() +
- (config.exposeModsInErrorPages || extName == undefined)
- ? ""
- : " " + extName
- )
- .replace(/&/g, "&")
- .replace(//g, ">"),
- )
- .replace(
- /{contact}/g,
- config.serverAdministratorEmail
- .replace(/&/g, "&")
- .replace(//g, ">")
- .replace(/\./g, "[dot]")
- .replace(/@/g, "[at]"),
- )
- .replace(/{additionalError}/g, additionalError.toString()),
- );
- res.end();
- }
- });
- }
- }
- });
- };
- let reqip = socket.remoteAddress;
- let reqport = socket.remotePort;
- process.reqcounter++;
- process.malformedcounter++;
- logFacilities.locmessage(
- "Somebody connected to " +
- (config.secure && fromMain
- ? (typeof config.sport == "number" ? "port " : "socket ") + config.sport
- : (typeof config.port == "number" ? "port " : "socket ") +
- config.port) +
- "...",
- );
- logFacilities.reqmessage(
- "Client " +
- (!reqip || reqip == ""
- ? "[unknown client]"
- : reqip +
- (reqport && reqport !== 0 && reqport != "" ? ":" + reqport : "")) +
- " sent invalid request.",
- );
- try {
- head = fs.existsSync("./.head")
- ? fs.readFileSync("./.head").toString()
- : fs.existsSync("./head.html")
- ? fs.readFileSync("./head.html").toString()
- : ""; // header
- foot = fs.existsSync("./.foot")
- ? fs.readFileSync("./.foot").toString()
- : fs.existsSync("./foot.html")
- ? fs.readFileSync("./foot.html").toString()
- : ""; // footer
-
- if (
- (err.code &&
- (err.code.indexOf("ERR_SSL_") == 0 ||
- err.code.indexOf("ERR_TLS_") == 0)) ||
- (!err.code && err.message.indexOf("SSL routines") != -1)
- ) {
- if (
- err.code == "ERR_SSL_HTTP_REQUEST" ||
- err.message.indexOf("http request") != -1
- ) {
- logFacilities.errmessage("Client sent HTTP request to HTTPS port.");
- callServerError(497);
- return;
- } else {
- logFacilities.errmessage(
- "An SSL error occured: " + (err.code ? err.code : err.message),
- );
- callServerError(400);
- return;
- }
- }
-
- if (err.code && err.code.indexOf("ERR_HTTP2_") == 0) {
- logFacilities.errmessage("An HTTP/2 error occured: " + err.code);
- callServerError(400);
- return;
- }
-
- if (err.code && err.code == "ERR_HTTP_REQUEST_TIMEOUT") {
- logFacilities.errmessage("Client timed out.");
- callServerError(408);
- return;
- }
-
- if (!err.rawPacket) {
- logFacilities.errmessage("Connection ended prematurely.");
- callServerError(400);
- return;
- }
-
- const packetLines = err.rawPacket.toString().split("\r\n");
- if (packetLines.length == 0) {
- logFacilities.errmessage("Invalid request.");
- callServerError(400);
- return;
- }
-
- const checkHeaders = (beginsFromFirst) => {
- for (let i = beginsFromFirst ? 0 : 1; i < packetLines.length; i++) {
- const header = packetLines[i];
- if (header == "")
- return false; // Beginning of body
- else if (header.indexOf(":") < 1) {
- logFacilities.errmessage("Invalid header.");
- callServerError(400);
- return true;
- } else if (header.length > 8192) {
- logFacilities.errmessage("Header too large.");
- callServerError(431); // Headers too large
- return true;
- }
- }
- return false;
- };
- const packetLine1 = packetLines[0].split(" ");
- let method = "GET";
- let httpVersion = "HTTP/1.1";
- if (String(packetLine1[0]).indexOf(":") > 0) {
- if (!checkHeaders(true)) {
- logFacilities.errmessage(
- "The request is invalid (it may be a part of larger invalid request).",
- );
- callServerError(400); // Also malformed Packet
- return;
- }
- }
- if (String(packetLine1[0]).length < 50) method = packetLine1.shift();
- if (String(packetLine1[packetLine1.length - 1]).length < 50)
- httpVersion = packetLine1.pop();
- if (packetLine1.length != 1) {
- logFacilities.errmessage("The head of request is invalid.");
- callServerError(400); // Malformed Packet
- } else if (!httpVersion.toString().match(/^HTTP[\/]/i)) {
- logFacilities.errmessage("Invalid protocol.");
- callServerError(400); // bad protocol version
- } else if (http.METHODS.indexOf(method) == -1) {
- logFacilities.errmessage("Invalid method.");
- callServerError(405); // Also malformed Packet
- } else {
- if (checkHeaders(false)) return;
- if (packetLine1[0].length > 255) {
- logFacilities.errmessage("URI too long.");
- callServerError(414); // Also malformed Packet
- } else {
- logFacilities.errmessage("The request is invalid.");
- callServerError(400); // Also malformed Packet
- }
- }
- } catch (err) {
- logFacilities.errmessage(
- "There was an error while determining type of malformed request.",
- );
- callServerError(400);
- }
-}
+const clientErrorHandler = require("./handlers/clientErrorHandler.js")(
+ serverconsole,
+);
// Create HTTP server
http