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