Compare commits
2 commits
fbdb3f93d4
...
000ae75904
Author | SHA1 | Date | |
---|---|---|---|
000ae75904 | |||
ee4c3dcda6 |
17 changed files with 1208 additions and 22 deletions
|
@ -119,7 +119,7 @@ esbuild.build({
|
|||
bundle: true,
|
||||
outfile: "dist/svr.js", // Output file
|
||||
platform: "node",
|
||||
target: "es2020",
|
||||
target: "es2017",
|
||||
plugins: [
|
||||
esbuildCopyPlugin.copy({
|
||||
resolveFrom: __dirname,
|
||||
|
|
|
@ -26,5 +26,5 @@ export default [
|
|||
}
|
||||
},
|
||||
pluginJs.configs.recommended,
|
||||
eslintPluginPrettierRecommended,
|
||||
eslintPluginPrettierRecommended
|
||||
];
|
||||
|
|
97
src/index.js
97
src/index.js
|
@ -1,4 +1,93 @@
|
|||
var http = require("http");
|
||||
http.createServer((req, res) => {
|
||||
res.end("Hello World!");
|
||||
}).listen(3000);
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const cluster = require("./utils/clusterBunShim.js"); // Cluster module with shim for Bun
|
||||
const sanitizeURL = require("./utils/urlSanitizer.js");
|
||||
//const generateErrorStack = require("./utils/generateErrorStack.js");
|
||||
//const serverHTTPErrorDescs = require("./res/httpErrorDescriptions.js");
|
||||
const getOS = require("./utils/getOS.js");
|
||||
const svrjsInfo = require("../svrjs.json");
|
||||
const version = svrjsInfo.version;
|
||||
//const parseURL = require("./utils/urlParser.js");
|
||||
//const fixNodeMojibakeURL = require("./utils/urlMojibakeFixer.js");
|
||||
|
||||
// Create log, mods and temp directories, if they don't exist.
|
||||
if (!fs.existsSync(__dirname + "/log")) fs.mkdirSync(__dirname + "/log");
|
||||
if (!fs.existsSync(__dirname + "/mods")) fs.mkdirSync(__dirname + "/mods");
|
||||
if (!fs.existsSync(__dirname + "/temp")) fs.mkdirSync(__dirname + "/temp");
|
||||
|
||||
const serverconsoleConstructor = require("./utils/serverconsole.js");
|
||||
|
||||
let configJSON = {};
|
||||
|
||||
// TODO: configuration from config.json
|
||||
if (!configJSON.page404) configJSON.page404 = "404.html"
|
||||
if (!configJSON.errorPages) configJSON.errorPages = [];
|
||||
if (!configJSON.stackHidden) configJSON.stackHidden = true;
|
||||
if (!configJSON.exposeServerVersion) configJSON.exposeServerVersion = false;
|
||||
if (!configJSON.exposeModsInErrorPages) configJSON.exposeModsInErrorPages = false;
|
||||
if (!configJSON.enableLogging) configJSON.enableLogging = true;
|
||||
if (!configJSON.serverAdministratorEmail) configJSON.serverAdministratorEmail = "webmaster@svrjs.org";
|
||||
|
||||
const serverconsole = serverconsoleConstructor(configJSON.enableLogging);
|
||||
|
||||
let middleware = [
|
||||
require("./middleware/core.js")
|
||||
];
|
||||
|
||||
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);
|
||||
|
||||
// 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(configJSON);
|
||||
|
||||
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);
|
||||
else {
|
||||
res.writeHead(500, "Internal Server Error", {
|
||||
Server: (config.exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS")
|
||||
});
|
||||
res.end("Error while executing the request handler");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (res.error) res.error(404);
|
||||
else {
|
||||
res.writeHead(404, "Not Found", {
|
||||
Server: (config.exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS")
|
||||
});
|
||||
res.end("Request handler missing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle middleware
|
||||
next();
|
||||
}
|
||||
|
||||
// Create HTTP server
|
||||
http.createServer(requestHandler).listen(3000);
|
||||
|
|
192
src/middleware/core.js
Normal file
192
src/middleware/core.js
Normal file
|
@ -0,0 +1,192 @@
|
|||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const generateErrorStack = require("../utils/generateErrorStack.js");
|
||||
const serverHTTPErrorDescs = require("../res/httpErrorDescriptions.js");
|
||||
|
||||
module.exports = (req, res, logFacilities, config, next) => {
|
||||
// TODO: proxy
|
||||
req.isProxy = false;
|
||||
|
||||
// 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) && 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.customHeaders, ...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);
|
||||
responseEnd(data.toString().replace(/{errorMessage}/g, errorCode.toString() + " " + http.STATUS_CODES[errorCode].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/ {2}/g, " ")).replace(/{path}/g, req.url.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")).replace(/{server}/g, "" + ((config.exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!config.exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + ((req.headers.host == undefined || req.isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"))).replace(/{contact}/g, config.serverAdministratorEmail.replace(/&/g, "&").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(("<!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, "&").replace(/</g, "<").replace(/>/g, ">")).replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode]).replace(/{stack}/g, stack.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/ {2}/g, " ")).replace(/{path}/g, req.url.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")).replace(/{server}/g, "" + ((config.exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS") + ((!config.exposeModsInErrorPages || extName == undefined) ? "" : " " + extName)).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + ((req.headers.host == undefined || req.isProxy) ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"))).replace(/{contact}/g, config.serverAdministratorEmail.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\./g, "[dot]").replace(/@/g, "[at]")).replace(/{additionalError}/g, additionalError.toString())); // Replace placeholders in error response
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to perform HTTP redirection to a specified destination URL
|
||||
res.redirect = (destination, isTemporary, keepMethod, customHeaders) => {
|
||||
// If keepMethod is a object, then save it to customHeaders
|
||||
if (typeof keepMethod == "object") customHeaders = keepMethod;
|
||||
|
||||
// If isTemporary is a object, then save it to customHeaders
|
||||
if (typeof isTemporary == "object") customHeaders = isTemporary;
|
||||
|
||||
// If customHeaders are not provided, get the default custom headers
|
||||
if (customHeaders === undefined) customHeaders = Object.assign(config.customHeaders);
|
||||
|
||||
// Set the "Location" header to the destination URL
|
||||
customHeaders["Location"] = destination;
|
||||
|
||||
// Determine the status code for redirection based on the isTemporary and keepMethod flags
|
||||
const statusCode = keepMethod ? (isTemporary ? 307 : 308) : (isTemporary ? 302 : 301);
|
||||
|
||||
// Write the response header with the appropriate status code and message
|
||||
res.writeHead(statusCode, http.STATUS_CODES[statusCode], customHeaders);
|
||||
|
||||
// Log the redirection message
|
||||
logFacilities.resmessage("Client redirected to " + destination);
|
||||
|
||||
// End the response
|
||||
res.end();
|
||||
|
||||
// Return from the function
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
req.parsedURL = new URL(req.url, "http" + (req.socket.encrypted ? "s" : "") + "://" + (req.headers.host ? req.headers.host : (domain ? domain : "unknown.invalid")));
|
||||
} catch (err) {
|
||||
res.error(400, err);
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
52
src/res/httpErrorDescriptions.js
Normal file
52
src/res/httpErrorDescriptions.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
// HTTP error descriptions
|
||||
var serverHTTPErrorDescs = {
|
||||
200: "The request succeeded! :)",
|
||||
201: "A new resource has been created.",
|
||||
202: "The request has been accepted for processing, but the processing has not been completed.",
|
||||
400: "The request you made is invalid.",
|
||||
401: "You need to authenticate yourself in order to access the requested file.",
|
||||
402: "You need to pay in order to access the requested file.",
|
||||
403: "You don't have access to the requested file.",
|
||||
404: "The requested file doesn't exist. If you have typed the URL manually, then please check the spelling.",
|
||||
405: "Method used to access the requested file isn't allowed.",
|
||||
406: "The request is capable of generating only unacceptable content.",
|
||||
407: "You need to authenticate yourself in order to use the proxy.",
|
||||
408: "You have timed out.",
|
||||
409: "The request you sent conflicts with the current state of the server.",
|
||||
410: "The requested file is permanently deleted.",
|
||||
411: "Content-Length property is required.",
|
||||
412: "The server doesn't meet the preconditions you put in the request.",
|
||||
413: "The request you sent is too large.",
|
||||
414: "The URL you sent is too long.",
|
||||
415: "The media type of request you sent isn't supported by the server.",
|
||||
416: "The requested content range (Content-Range header) you sent is unsatisfiable.",
|
||||
417: "The expectation specified in the Expect property couldn't be satisfied.",
|
||||
418: "The server (teapot) can't brew any coffee! ;)",
|
||||
421: "The request you made isn't intended for this server.",
|
||||
422: "The server couldn't process content sent by you.",
|
||||
423: "The requested file is locked.",
|
||||
424: "The request depends on another failed request.",
|
||||
425: "The server is unwilling to risk processing a request that might be replayed.",
|
||||
426: "You need to upgrade the protocols you use to request a file.",
|
||||
428: "The request you sent needs to be conditional, but it isn't.",
|
||||
429: "You sent too many requests to the server.",
|
||||
431: "The request you sent contains headers that are too large.",
|
||||
451: "The requested file isn't accessible for legal reasons.",
|
||||
497: "You sent a non-TLS request to the HTTPS server.",
|
||||
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>.",
|
||||
501: "The request requires the use of a function, which isn't currently implemented by the server.",
|
||||
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>.",
|
||||
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>.",
|
||||
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>.",
|
||||
505: "The server doesn't support the HTTP version used in the request.",
|
||||
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>.",
|
||||
507: "The server ran out of disk space necessary to complete the request.",
|
||||
508: "The server detected an infinite loop while processing the request.",
|
||||
509: "The server has its bandwidth limit exceeded.</p><p>You may need to contact the server administrator at <i>{contact}</i>.",
|
||||
510: "The server requires an extended HTTP request. The request you made isn't an extended HTTP request.",
|
||||
511: "You need to authenticate yourself in order to get network access.",
|
||||
598: "The server couldn't get a response in time while it was acting as a proxy.",
|
||||
599: "The server couldn't connect in time while it was acting as a proxy.",
|
||||
};
|
||||
|
||||
module.exports = serverHTTPErrorDescs;
|
230
src/utils/clusterBunShim.js
Normal file
230
src/utils/clusterBunShim.js
Normal file
|
@ -0,0 +1,230 @@
|
|||
const net = require("net");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
|
||||
let cluster = {};
|
||||
try {
|
||||
// Import cluster module
|
||||
cluster = require("cluster");
|
||||
} catch (err) {
|
||||
// Clustering is not supported!
|
||||
}
|
||||
|
||||
// Cluster & IPC shim for Bun
|
||||
|
||||
cluster.bunShim = function () {
|
||||
cluster.isMaster = !process.env.NODE_UNIQUE_ID;
|
||||
cluster.isPrimary = cluster.isMaster;
|
||||
cluster.isWorker = !cluster.isMaster;
|
||||
cluster.__shimmed__ = true;
|
||||
|
||||
if (cluster.isWorker) {
|
||||
// Shim the cluster.worker object for worker processes
|
||||
cluster.worker = {
|
||||
id: parseInt(process.env.NODE_UNIQUE_ID),
|
||||
process: process,
|
||||
isDead: function () {
|
||||
return false;
|
||||
},
|
||||
send: function (message, b, c, d) {
|
||||
process.send(message, b, c, d);
|
||||
},
|
||||
};
|
||||
|
||||
if (!process.send) {
|
||||
// Shim the process.send function for worker processes
|
||||
|
||||
// Create a fake IPC server to receive messages
|
||||
let fakeIPCServer = net.createServer(function (socket) {
|
||||
let receivedData = "";
|
||||
|
||||
socket.on("data", function (data) {
|
||||
receivedData += data.toString();
|
||||
});
|
||||
|
||||
socket.on("end", function () {
|
||||
process.emit("message", receivedData);
|
||||
});
|
||||
});
|
||||
fakeIPCServer.listen(
|
||||
os.platform() === "win32"
|
||||
? path.join(
|
||||
"\\\\?\\pipe",
|
||||
__dirname,
|
||||
"temp/.W" + process.pid + ".ipc",
|
||||
)
|
||||
: __dirname + "/temp/.W" + process.pid + ".ipc",
|
||||
);
|
||||
|
||||
process.send = function (message) {
|
||||
// Create a fake IPC connection to send messages
|
||||
let fakeIPCConnection = net.createConnection(
|
||||
os.platform() === "win32"
|
||||
? path.join(
|
||||
"\\\\?\\pipe",
|
||||
__dirname,
|
||||
"temp/.P" + process.pid + ".ipc",
|
||||
)
|
||||
: __dirname + "/temp/.P" + process.pid + ".ipc",
|
||||
function () {
|
||||
fakeIPCConnection.end(message);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
process.removeFakeIPC = function () {
|
||||
// Close IPC server
|
||||
process.send = function () {};
|
||||
fakeIPCServer.close();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Custom implementation for cluster.fork()
|
||||
cluster._workersCounter = 1;
|
||||
cluster.workers = {};
|
||||
cluster.fork = function (env) {
|
||||
const child_process = require("child_process");
|
||||
let newEnvironment = Object.assign(env ? env : process.env);
|
||||
newEnvironment.NODE_UNIQUE_ID = cluster._workersCounter;
|
||||
let newArguments = Object.assign(process.argv);
|
||||
let command = newArguments.shift();
|
||||
let newWorker = child_process.spawn(command, newArguments, {
|
||||
env: newEnvironment,
|
||||
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
||||
});
|
||||
|
||||
newWorker.process = newWorker;
|
||||
newWorker.isDead = function () {
|
||||
return newWorker.exitCode !== null || newWorker.killed;
|
||||
};
|
||||
newWorker.id = newEnvironment.NODE_UNIQUE_ID;
|
||||
|
||||
function checkSendImplementation(worker) {
|
||||
let sendImplemented = true;
|
||||
|
||||
if (
|
||||
!(
|
||||
process.versions &&
|
||||
process.versions.bun &&
|
||||
process.versions.bun[0] != "0"
|
||||
)
|
||||
) {
|
||||
if (!worker.send) {
|
||||
sendImplemented = false;
|
||||
}
|
||||
|
||||
let oldLog = console.log;
|
||||
console.log = function (a, b, c, d, e, f) {
|
||||
if (
|
||||
a == "ChildProcess.prototype.send() - Sorry! Not implemented yet"
|
||||
) {
|
||||
throw new Error("NOT IMPLEMENTED");
|
||||
} else {
|
||||
oldLog(a, b, c, d, e, f);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
worker.send(undefined);
|
||||
} catch (err) {
|
||||
if (err.message === "NOT IMPLEMENTED") {
|
||||
sendImplemented = false;
|
||||
}
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
console.log = oldLog;
|
||||
}
|
||||
|
||||
return sendImplemented;
|
||||
}
|
||||
|
||||
if (!checkSendImplementation(newWorker)) {
|
||||
// Create a fake IPC server for worker process to receive messages
|
||||
let fakeWorkerIPCServer = net.createServer(function (socket) {
|
||||
let receivedData = "";
|
||||
|
||||
socket.on("data", function (data) {
|
||||
receivedData += data.toString();
|
||||
});
|
||||
|
||||
socket.on("end", function () {
|
||||
newWorker.emit("message", receivedData);
|
||||
});
|
||||
});
|
||||
fakeWorkerIPCServer.listen(
|
||||
os.platform() === "win32"
|
||||
? path.join(
|
||||
"\\\\?\\pipe",
|
||||
__dirname,
|
||||
"temp/.P" + newWorker.process.pid + ".ipc",
|
||||
)
|
||||
: __dirname + "/temp/.P" + newWorker.process.pid + ".ipc",
|
||||
);
|
||||
|
||||
// Cleanup when worker process exits
|
||||
newWorker.on("exit", function () {
|
||||
fakeWorkerIPCServer.close();
|
||||
delete cluster.workers[newWorker.id];
|
||||
});
|
||||
|
||||
newWorker.send = function (
|
||||
message,
|
||||
fakeParam2,
|
||||
fakeParam3,
|
||||
fakeParam4,
|
||||
tries,
|
||||
) {
|
||||
if (!tries) tries = 0;
|
||||
|
||||
try {
|
||||
// Create a fake IPC connection to send messages to worker process
|
||||
let fakeWorkerIPCConnection = net.createConnection(
|
||||
os.platform() === "win32"
|
||||
? path.join(
|
||||
"\\\\?\\pipe",
|
||||
__dirname,
|
||||
"temp/.W" + newWorker.process.pid + ".ipc",
|
||||
)
|
||||
: __dirname + "/temp/.W" + newWorker.process.pid + ".ipc",
|
||||
function () {
|
||||
fakeWorkerIPCConnection.end(message);
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
if (tries > 50) throw err;
|
||||
newWorker.send(
|
||||
message,
|
||||
fakeParam2,
|
||||
fakeParam3,
|
||||
fakeParam4,
|
||||
tries + 1,
|
||||
);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
newWorker.on("exit", function () {
|
||||
delete cluster.workers[newWorker.id];
|
||||
});
|
||||
}
|
||||
|
||||
cluster.workers[newWorker.id] = newWorker;
|
||||
cluster._workersCounter++;
|
||||
return newWorker;
|
||||
};
|
||||
};
|
||||
|
||||
if (
|
||||
process.isBun &&
|
||||
(cluster.isMaster === undefined ||
|
||||
(cluster.isMaster && process.env.NODE_UNIQUE_ID))
|
||||
) {
|
||||
cluster.bunShim();
|
||||
}
|
||||
|
||||
// Shim cluster.isPrimary field
|
||||
if (cluster.isPrimary === undefined && cluster.isMaster !== undefined)
|
||||
cluster.isPrimary = cluster.isMaster;
|
||||
|
||||
module.exports = cluster;
|
50
src/utils/generateErrorStack.js
Normal file
50
src/utils/generateErrorStack.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Generate V8-style error stack from Error object.
|
||||
function generateErrorStack(errorObject) {
|
||||
// Split the error stack by newlines.
|
||||
var errorStack = errorObject.stack ? errorObject.stack.split("\n") : [];
|
||||
|
||||
// If the error stack starts with the error name, return the original stack (it is V8-style then).
|
||||
if (
|
||||
errorStack.some(function (errorStackLine) {
|
||||
return errorStackLine.indexOf(errorObject.name) == 0;
|
||||
})
|
||||
) {
|
||||
return errorObject.stack;
|
||||
}
|
||||
|
||||
// Create a new error stack with the error name and code (if available).
|
||||
var newErrorStack = [
|
||||
errorObject.name +
|
||||
(errorObject.code ? ": " + errorObject.code : "") +
|
||||
(errorObject.message == "" ? "" : ": " + errorObject.message),
|
||||
];
|
||||
|
||||
// Process each line of the original error stack.
|
||||
errorStack.forEach(function (errorStackLine) {
|
||||
if (errorStackLine != "") {
|
||||
// Split the line into function and location parts (if available).
|
||||
var errorFrame = errorStackLine.split("@");
|
||||
var location = "";
|
||||
if (errorFrame.length > 1 && errorFrame[0] == "global code")
|
||||
errorFrame.shift();
|
||||
if (errorFrame.length > 1) location = errorFrame.pop();
|
||||
var func = errorFrame.join("@");
|
||||
|
||||
// Build the new error stack entry with function and location information.
|
||||
newErrorStack.push(
|
||||
" at " +
|
||||
(func == ""
|
||||
? !location || location == ""
|
||||
? "<anonymous>"
|
||||
: location
|
||||
: func +
|
||||
(!location || location == "" ? "" : " (" + location + ")")),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Join the new error stack entries with newlines and return the final stack.
|
||||
return newErrorStack.join("\n");
|
||||
}
|
||||
|
||||
module.exports = generateErrorStack;
|
31
src/utils/getOS.js
Normal file
31
src/utils/getOS.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
const os = require("os");
|
||||
|
||||
function getOS() {
|
||||
var osType = os.type();
|
||||
var platform = os.platform();
|
||||
if (platform == "android") {
|
||||
return "Android";
|
||||
} else if (osType == "Windows_NT" || osType == "WindowsNT") {
|
||||
var arch = os.arch();
|
||||
if (arch == "ia32") {
|
||||
return "Win32";
|
||||
} else if (arch == "x64") {
|
||||
return "Win64";
|
||||
} else {
|
||||
return "Win" + arch.toUpperCase();
|
||||
}
|
||||
} else if (osType.indexOf("CYGWIN") == 0) {
|
||||
return "Cygwin";
|
||||
} else if (osType.indexOf("MINGW") == 0) {
|
||||
return "MinGW";
|
||||
} else if (osType.indexOf("MSYS") == 0) {
|
||||
return "MSYS";
|
||||
} else if (osType.indexOf("UWIN") == 0) {
|
||||
return "UWIN";
|
||||
} else if (osType == "GNU") {
|
||||
return "GNU Hurd";
|
||||
} else {
|
||||
return osType;
|
||||
}
|
||||
}
|
||||
module.exports = getOS;
|
|
@ -1,8 +0,0 @@
|
|||
// src/utils/helper.js
|
||||
function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
add,
|
||||
};
|
277
src/utils/serverconsole.js
Normal file
277
src/utils/serverconsole.js
Normal file
|
@ -0,0 +1,277 @@
|
|||
const fs = require("fs");
|
||||
|
||||
let enableLoggingIntoFile = false;
|
||||
let logFile = undefined;
|
||||
let logSync = false;
|
||||
let cluster = require("./clusterBunShim.js");
|
||||
let reallyExiting = false;
|
||||
let timestamp = new Date().getTime();
|
||||
|
||||
// Logging function
|
||||
function LOG(s) {
|
||||
try {
|
||||
if (enableLoggingIntoFile) {
|
||||
if (logSync) {
|
||||
fs.appendFileSync(
|
||||
__dirname +
|
||||
"/log/" +
|
||||
(cluster.isPrimary
|
||||
? "master"
|
||||
: cluster.isPrimary === undefined
|
||||
? "singlethread"
|
||||
: "worker") +
|
||||
"-" +
|
||||
timestamp +
|
||||
".log",
|
||||
"[" + new Date().toISOString() + "] " + s + "\r\n",
|
||||
);
|
||||
} else {
|
||||
if (!logFile) {
|
||||
logFile = fs.createWriteStream(
|
||||
__dirname +
|
||||
"/log/" +
|
||||
(cluster.isPrimary
|
||||
? "master"
|
||||
: cluster.isPrimary === undefined
|
||||
? "singlethread"
|
||||
: "worker") +
|
||||
"-" +
|
||||
timestamp +
|
||||
".log",
|
||||
{
|
||||
flags: "a",
|
||||
autoClose: false,
|
||||
},
|
||||
);
|
||||
logFile.on("error", function (err) {
|
||||
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,
|
||||
);
|
||||
});
|
||||
}
|
||||
if (logFile.writable) {
|
||||
logFile.write("[" + new Date().toISOString() + "] " + s + "\r\n");
|
||||
} else {
|
||||
throw new Error("Log file stream is closed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Server console function
|
||||
var serverconsole = {
|
||||
climessage: (msg, reqId) => {
|
||||
if (msg.indexOf("\n") != -1) {
|
||||
msg.split("\n").forEach((nmsg) => {
|
||||
serverconsole.climessage(nmsg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"\x1b[1mSERVER CLI MESSAGE\x1b[22m" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg,
|
||||
);
|
||||
LOG(
|
||||
"SERVER CLI MESSAGE" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg,
|
||||
);
|
||||
return;
|
||||
},
|
||||
reqmessage: (msg, reqId) => {
|
||||
if (msg.indexOf("\n") != -1) {
|
||||
msg.split("\n").forEach((nmsg) => {
|
||||
serverconsole.reqmessage(nmsg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"\x1b[34m\x1b[1mSERVER REQUEST MESSAGE\x1b[22m" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg +
|
||||
"\x1b[37m\x1b[0m",
|
||||
);
|
||||
LOG(
|
||||
"SERVER REQUEST MESSAGE" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg,
|
||||
);
|
||||
return;
|
||||
},
|
||||
resmessage: (msg, reqId) => {
|
||||
if (msg.indexOf("\n") != -1) {
|
||||
msg.split("\n").forEach((nmsg) => {
|
||||
serverconsole.resmessage(nmsg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"\x1b[32m\x1b[1mSERVER RESPONSE MESSAGE\x1b[22m" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg +
|
||||
"\x1b[37m\x1b[0m",
|
||||
);
|
||||
LOG(
|
||||
"SERVER RESPONSE MESSAGE" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg,
|
||||
);
|
||||
return;
|
||||
},
|
||||
errmessage: (msg, reqId) => {
|
||||
if (msg.indexOf("\n") != -1) {
|
||||
msg.split("\n").forEach((nmsg) => {
|
||||
serverconsole.errmessage(nmsg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"\x1b[31m\x1b[1mSERVER RESPONSE ERROR MESSAGE\x1b[22m" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg +
|
||||
"\x1b[37m\x1b[0m",
|
||||
);
|
||||
LOG(
|
||||
"SERVER RESPONSE ERROR MESSAGE" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg,
|
||||
);
|
||||
return;
|
||||
},
|
||||
locerrmessage: (msg, reqId) => {
|
||||
if (msg.indexOf("\n") != -1) {
|
||||
msg.split("\n").forEach((nmsg) => {
|
||||
serverconsole.locerrmessage(nmsg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"\x1b[41m\x1b[1mSERVER ERROR MESSAGE\x1b[22m" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg +
|
||||
"\x1b[40m\x1b[0m",
|
||||
);
|
||||
LOG(
|
||||
"SERVER ERROR MESSAGE" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg,
|
||||
);
|
||||
return;
|
||||
},
|
||||
locwarnmessage: (msg, reqId) => {
|
||||
if (msg.indexOf("\n") != -1) {
|
||||
msg.split("\n").forEach((nmsg) => {
|
||||
serverconsole.locwarnmessage(nmsg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"\x1b[43m\x1b[1mSERVER WARNING MESSAGE\x1b[22m" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg +
|
||||
"\x1b[40m\x1b[0m",
|
||||
);
|
||||
LOG(
|
||||
"SERVER WARNING MESSAGE" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg,
|
||||
);
|
||||
return;
|
||||
},
|
||||
locmessage: (msg, reqId) => {
|
||||
if (msg.indexOf("\n") != -1) {
|
||||
msg.split("\n").forEach((nmsg) => {
|
||||
serverconsole.locmessage(nmsg);
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"\x1b[1mSERVER MESSAGE\x1b[22m" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg,
|
||||
);
|
||||
LOG(
|
||||
"SERVER MESSAGE" +
|
||||
(reqId ? " [Request Id: " + reqId + "]" : "") +
|
||||
": " +
|
||||
msg,
|
||||
);
|
||||
return;
|
||||
},
|
||||
setProcessExiting: (state) => {
|
||||
reallyExiting = state;
|
||||
},
|
||||
};
|
||||
|
||||
// Wrap around process.exit, so that log contents can be flushed.
|
||||
process.unsafeExit = process.exit;
|
||||
process.exit = function (code) {
|
||||
if (logFile && logFile.writable && !logFile.pending) {
|
||||
try {
|
||||
logFile.close(function () {
|
||||
logFile = undefined;
|
||||
logSync = true;
|
||||
process.unsafeExit(code);
|
||||
});
|
||||
if (process.isBun) {
|
||||
setInterval(function () {
|
||||
if (!logFile.writable) {
|
||||
logFile = undefined;
|
||||
logSync = true;
|
||||
process.unsafeExit(code);
|
||||
}
|
||||
}, 50); // Interval
|
||||
}
|
||||
setTimeout(function () {
|
||||
logFile = undefined;
|
||||
logSync = true;
|
||||
process.unsafeExit(code);
|
||||
}, 10000); // timeout
|
||||
} catch (err) {
|
||||
logFile = undefined;
|
||||
logSync = true;
|
||||
process.unsafeExit(code);
|
||||
}
|
||||
} else {
|
||||
logSync = true;
|
||||
process.unsafeExit(code);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = (enableLoggingIntoFileParam) => {
|
||||
enableLoggingIntoFile = enableLoggingIntoFileParam;
|
||||
return serverconsole;
|
||||
};
|
21
src/utils/urlMojibakeFixer.js
Normal file
21
src/utils/urlMojibakeFixer.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Node.JS mojibake URL fixing function
|
||||
function fixNodeMojibakeURL(string) {
|
||||
var encoded = "";
|
||||
|
||||
//Encode URLs
|
||||
Buffer.from(string, "latin1").forEach(function (value) {
|
||||
if (value > 127) {
|
||||
encoded +=
|
||||
"%" + (value < 16 ? "0" : "") + value.toString(16).toUpperCase();
|
||||
} else {
|
||||
encoded += String.fromCodePoint(value);
|
||||
}
|
||||
});
|
||||
|
||||
//Upper case the URL encodings
|
||||
return encoded.replace(/%[0-9a-f-A-F]{2}/g, function (match) {
|
||||
return match.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = fixNodeMojibakeURL;
|
90
src/utils/urlParser.js
Normal file
90
src/utils/urlParser.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
const url = require("url");
|
||||
|
||||
// SVR.JS URL parser function
|
||||
function parseURL(uri, prepend) {
|
||||
// Replace newline characters with its respective URL encodings
|
||||
uri = uri.replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
||||
|
||||
// If URL begins with a slash, prepend a string if available
|
||||
if (prepend && uri[0] == "/") uri = prepend.replace(/\/+$/, "") + uri;
|
||||
|
||||
// Determine if URL has slashes
|
||||
let hasSlashes = uri.indexOf("/") != -1;
|
||||
|
||||
// Parse the URL using regular expression
|
||||
let parsedURI = uri.match(
|
||||
/^(?:([^:]+:)(\/\/)?)?(?:([^@\/?#\*]+)@)?([^:\/?#\*]+|\[[^\*]\/]\])?(?::([0-9]+))?(\*|\/[^?#]*)?(\?[^#]*)?(#[\S\s]*)?/,
|
||||
);
|
||||
// Match 1: protocol
|
||||
// Match 2: slashes after protocol
|
||||
// Match 3: authentication credentials
|
||||
// Match 4: host name
|
||||
// Match 5: port
|
||||
// Match 6: path name
|
||||
// Match 7: query string
|
||||
// Match 8: hash
|
||||
|
||||
// If regular expression didn't match the entire URL, throw an error
|
||||
if (parsedURI[0].length != uri.length) throw new Error("Invalid URL: " + uri);
|
||||
|
||||
// If match 1 is not empty, set the slash variable based on state of match 2
|
||||
if (parsedURI[1]) hasSlashes = parsedURI[2] == "//";
|
||||
|
||||
// If match 6 is empty and URL has slashes, set it to a slash.
|
||||
if (hasSlashes && !parsedURI[6]) parsedURI[6] = "/";
|
||||
|
||||
// If match 4 contains Unicode characters, convert it to Punycode. If the result is an empty string, throw an error
|
||||
if (parsedURI[4] && !parsedURI[4].match(/^[a-zA-Z0-9\.\-]+$/)) {
|
||||
parsedURI[4] = url.domainToASCII(parsedURI[4]);
|
||||
if (!parsedURI[4]) throw new Error("Invalid URL: " + uri);
|
||||
}
|
||||
|
||||
// Create a new URL object
|
||||
let uobject = new url.Url();
|
||||
|
||||
// Populate a URL object
|
||||
if (hasSlashes) uobject.slashes = true;
|
||||
if (parsedURI[1]) uobject.protocol = parsedURI[1];
|
||||
if (parsedURI[3]) uobject.auth = parsedURI[3];
|
||||
if (parsedURI[4]) {
|
||||
uobject.host = parsedURI[4] + (parsedURI[5] ? ":" + parsedURI[5] : "");
|
||||
if (parsedURI[4][0] == "[")
|
||||
uobject.hostname = parsedURI[4].substring(1, parsedURI[4].length - 1);
|
||||
else uobject.hostname = parsedURI[4];
|
||||
}
|
||||
if (parsedURI[5]) uobject.port = parsedURI[5];
|
||||
if (parsedURI[6]) uobject.pathname = parsedURI[6];
|
||||
if (parsedURI[7]) {
|
||||
uobject.search = parsedURI[7];
|
||||
// Parse query strings
|
||||
let qobject = Object.create(null);
|
||||
const parsedQuery = parsedURI[7]
|
||||
.substring(1)
|
||||
.match(/([^&=]*)(?:=([^&]*))?/g);
|
||||
parsedQuery.forEach(function (qp) {
|
||||
if (qp.length > 0) {
|
||||
let parsedQP = qp.match(/([^&=]*)(?:=([^&]*))?/);
|
||||
if (parsedQP) {
|
||||
qobject[parsedQP[1]] = parsedQP[2] ? parsedQP[2] : "";
|
||||
}
|
||||
}
|
||||
});
|
||||
uobject.query = qobject;
|
||||
} else {
|
||||
uobject.query = Object.create(null);
|
||||
}
|
||||
if (parsedURI[8]) uobject.hash = parsedURI[8];
|
||||
if (uobject.pathname)
|
||||
uobject.path = uobject.pathname + (uobject.search ? uobject.search : "");
|
||||
uobject.href =
|
||||
(uobject.protocol ? uobject.protocol + (uobject.slashes ? "//" : "") : "") +
|
||||
(uobject.auth ? uobject.auth + "@" : "") +
|
||||
(uobject.hostname ? uobject.hostname : "") +
|
||||
(uobject.port ? ":" + uobject.port : "") +
|
||||
(uobject.path ? uobject.path : "") +
|
||||
(uobject.hash ? uobject.hash : "");
|
||||
|
||||
return uobject;
|
||||
}
|
||||
|
||||
module.exports = parseURL;
|
|
@ -1,3 +1,4 @@
|
|||
// SVR.JS path sanitizer function
|
||||
function sanitizeURL(resource, allowDoubleSlashes) {
|
||||
if (resource == "*" || resource == "") return resource;
|
||||
// Remove null characters
|
||||
|
@ -7,17 +8,17 @@ function sanitizeURL(resource, allowDoubleSlashes) {
|
|||
throw new URIError("URI malformed");
|
||||
// Decode URL-encoded characters while preserving certain characters
|
||||
resource = resource.replace(/%([0-9a-f]{2})/gi, (match, hex) => {
|
||||
var decodedChar = String.fromCharCode(parseInt(hex, 16));
|
||||
const decodedChar = String.fromCharCode(parseInt(hex, 16));
|
||||
return /(?!["<>^`{|}?#%])[!-~]/.test(decodedChar) ? decodedChar : "%" + hex;
|
||||
});
|
||||
// Encode certain characters
|
||||
resource = resource.replace(/[<>^`{|}]]/g, (character) => {
|
||||
var charCode = character.charCodeAt(0);
|
||||
const charCode = character.charCodeAt(0);
|
||||
return (
|
||||
"%" + (charCode < 16 ? "0" : "") + charCode.toString(16).toUpperCase()
|
||||
);
|
||||
});
|
||||
var sanitizedResource = resource;
|
||||
let sanitizedResource = resource;
|
||||
// Ensure the resource starts with a slash
|
||||
if (resource[0] != "/") sanitizedResource = "/" + sanitizedResource;
|
||||
// Convert backslashes to slashes and handle duplicate slashes
|
||||
|
|
44
tests/utils/generateErrorStack.test.js
Normal file
44
tests/utils/generateErrorStack.test.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
const generateErrorStack = require("../../src/utils/generateErrorStack");
|
||||
|
||||
describe("generateErrorStack", () => {
|
||||
test("should return the original stack if it is V8-style", () => {
|
||||
const error = new Error("Test error");
|
||||
error.stack = `Error: Test error
|
||||
at Object.<anonymous> (/path/to/file.js:10:15)
|
||||
at Module._compile (internal/modules/cjs/loader.js:1063:30)
|
||||
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)`;
|
||||
|
||||
const result = generateErrorStack(error);
|
||||
expect(result).toBe(error.stack);
|
||||
});
|
||||
|
||||
test("should generate a new stack if the original stack is SpiderMonkey-style", () => {
|
||||
const error = new Error("Test error");
|
||||
error.stack = `baz@filename.js:10:15
|
||||
bar@filename.js:6:3
|
||||
foo@filename.js:2:3
|
||||
@filename.js:13:1`;
|
||||
|
||||
const result = generateErrorStack(error);
|
||||
expect(result).toContain("Error: Test error");
|
||||
expect(result).toContain(" at baz (filename.js:10:15)");
|
||||
expect(result).toContain(" at bar (filename.js:6:3)");
|
||||
expect(result).toContain(" at foo (filename.js:2:3)");
|
||||
expect(result).toContain(" at filename.js:13:1");
|
||||
});
|
||||
|
||||
test("should generate a new stack if the original stack is JavaScriptCore-style", () => {
|
||||
const error = new Error("Test error");
|
||||
error.stack = `baz@filename.js:10:15
|
||||
bar@filename.js:6:3
|
||||
foo@filename.js:2:3
|
||||
global code@filename.js:13:1`;
|
||||
|
||||
const result = generateErrorStack(error);
|
||||
expect(result).toContain("Error: Test error");
|
||||
expect(result).toContain(" at baz (filename.js:10:15)");
|
||||
expect(result).toContain(" at bar (filename.js:6:3)");
|
||||
expect(result).toContain(" at foo (filename.js:2:3)");
|
||||
expect(result).toContain(" at filename.js:13:1");
|
||||
});
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
const { add } = require("../../src/utils/helper");
|
||||
|
||||
test("adds 1 + 2 to equal 3", () => {
|
||||
expect(add(1, 2)).toBe(3);
|
||||
});
|
42
tests/utils/urlMojibakeFixer.test.js
Normal file
42
tests/utils/urlMojibakeFixer.test.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
const fixNodeMojibakeURL = require("../../src/utils/urlMojibakeFixer.js");
|
||||
|
||||
describe("URL mojibake fixer", () => {
|
||||
test("should return the same string for ASCII characters", () => {
|
||||
expect(fixNodeMojibakeURL("hello world")).toBe("hello world");
|
||||
});
|
||||
|
||||
test("should encode characters with values greater than 127", () => {
|
||||
expect(fixNodeMojibakeURL("Ă©")).toBe("%E9");
|
||||
expect(fixNodeMojibakeURL("ñ")).toBe("%F1");
|
||||
});
|
||||
|
||||
test("should uppercase the URL encodings", () => {
|
||||
expect(fixNodeMojibakeURL("a%e9b")).toBe("a%E9b");
|
||||
});
|
||||
|
||||
test("should handle mixed ASCII and non-ASCII characters", () => {
|
||||
expect(fixNodeMojibakeURL("hello é world ñ")).toBe("hello %E9 world %F1");
|
||||
});
|
||||
|
||||
test("should handle empty string", () => {
|
||||
expect(fixNodeMojibakeURL("")).toBe("");
|
||||
});
|
||||
|
||||
test("should handle strings with special characters", () => {
|
||||
expect(fixNodeMojibakeURL("!@#$%^&*()")).toBe("!@#$%^&*()");
|
||||
});
|
||||
|
||||
test("should handle strings with spaces", () => {
|
||||
expect(fixNodeMojibakeURL("hello world")).toBe("hello world");
|
||||
});
|
||||
|
||||
test("should handle strings with numbers", () => {
|
||||
expect(fixNodeMojibakeURL("12345")).toBe("12345");
|
||||
});
|
||||
|
||||
test("should handle strings with mixed characters", () => {
|
||||
expect(fixNodeMojibakeURL("hello123 é world ñ!@#$%^&*()")).toBe(
|
||||
"hello123 %E9 world %F1!@#$%^&*()",
|
||||
);
|
||||
});
|
||||
});
|
80
tests/utils/urlParser.test.js
Normal file
80
tests/utils/urlParser.test.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
const parseURL = require("../../src/utils/urlParser.js");
|
||||
|
||||
describe("URL parser", () => {
|
||||
test("should parse a simple URL", () => {
|
||||
const parsedUrl = parseURL("http://example.com");
|
||||
expect(parsedUrl.protocol).toBe("http:");
|
||||
expect(parsedUrl.hostname).toBe("example.com");
|
||||
expect(parsedUrl.pathname).toBe("/");
|
||||
expect(parsedUrl.path).toBe("/");
|
||||
expect(parsedUrl.href).toBe("http://example.com/");
|
||||
});
|
||||
|
||||
test("should parse a URL with a path", () => {
|
||||
const parsedUrl = parseURL("http://example.com/path/to/resource");
|
||||
expect(parsedUrl.protocol).toBe("http:");
|
||||
expect(parsedUrl.hostname).toBe("example.com");
|
||||
expect(parsedUrl.pathname).toBe("/path/to/resource");
|
||||
expect(parsedUrl.path).toBe("/path/to/resource");
|
||||
expect(parsedUrl.href).toBe("http://example.com/path/to/resource");
|
||||
});
|
||||
|
||||
test("should parse a URL with a query string", () => {
|
||||
const parsedUrl = parseURL("http://example.com/path?query=string");
|
||||
expect(parsedUrl.protocol).toBe("http:");
|
||||
expect(parsedUrl.hostname).toBe("example.com");
|
||||
expect(parsedUrl.pathname).toBe("/path");
|
||||
expect(parsedUrl.search).toBe("?query=string");
|
||||
expect(parsedUrl.query.query).toBe("string");
|
||||
expect(parsedUrl.path).toBe("/path?query=string");
|
||||
expect(parsedUrl.href).toBe("http://example.com/path?query=string");
|
||||
});
|
||||
|
||||
test("should parse a URL with a port", () => {
|
||||
const parsedUrl = parseURL("http://example.com:8080");
|
||||
expect(parsedUrl.protocol).toBe("http:");
|
||||
expect(parsedUrl.hostname).toBe("example.com");
|
||||
expect(parsedUrl.port).toBe("8080");
|
||||
expect(parsedUrl.pathname).toBe("/");
|
||||
expect(parsedUrl.path).toBe("/");
|
||||
expect(parsedUrl.href).toBe("http://example.com:8080/");
|
||||
});
|
||||
|
||||
test("should parse a URL with a username and password", () => {
|
||||
const parsedUrl = parseURL("http://user:pass@example.com");
|
||||
expect(parsedUrl.protocol).toBe("http:");
|
||||
expect(parsedUrl.auth).toBe("user:pass");
|
||||
expect(parsedUrl.hostname).toBe("example.com");
|
||||
expect(parsedUrl.pathname).toBe("/");
|
||||
expect(parsedUrl.path).toBe("/");
|
||||
expect(parsedUrl.href).toBe("http://user:pass@example.com/");
|
||||
});
|
||||
|
||||
test("should parse a URL with a fragment", () => {
|
||||
const parsedUrl = parseURL("http://example.com/path#fragment");
|
||||
expect(parsedUrl.protocol).toBe("http:");
|
||||
expect(parsedUrl.hostname).toBe("example.com");
|
||||
expect(parsedUrl.pathname).toBe("/path");
|
||||
expect(parsedUrl.hash).toBe("#fragment");
|
||||
expect(parsedUrl.path).toBe("/path");
|
||||
expect(parsedUrl.href).toBe("http://example.com/path#fragment");
|
||||
});
|
||||
|
||||
test("should parse a URL with all components", () => {
|
||||
const parsedUrl = parseURL(
|
||||
"http://user:pass@example.com:8080/path/to/resource?query=string#fragment",
|
||||
);
|
||||
expect(parsedUrl.protocol).toBe("http:");
|
||||
expect(parsedUrl.auth).toBe("user:pass");
|
||||
expect(parsedUrl.hostname).toBe("example.com");
|
||||
expect(parsedUrl.port).toBe("8080");
|
||||
expect(parsedUrl.pathname).toBe("/path/to/resource");
|
||||
expect(parsedUrl.search).toBe("?query=string");
|
||||
expect(parsedUrl.query.query).toBe("string");
|
||||
expect(parsedUrl.hash).toBe("#fragment");
|
||||
expect(parsedUrl.path).toBe("/path/to/resource?query=string");
|
||||
expect(parsedUrl.href).toBe(
|
||||
"http://user:pass@example.com:8080/path/to/resource?query=string#fragment",
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue