1
0
Fork 0
forked from svrjs/svrjs

Update to SVR.JS 4.0.0-beta3

This commit is contained in:
Dorian Niemiec 2024-08-30 08:09:02 +02:00
parent 920d942016
commit c5af172d1e
26 changed files with 201 additions and 184 deletions

View file

@ -217,17 +217,13 @@ esbuild
archive.pipe(output); archive.pipe(output);
// Add everything in the "dist" directory except for "svr.js" and "svr.compressed" // Add everything in the "dist" directory except for "svr.js" and "svr.compressed"
const distFilesAndDirectories = fs.existsSync(__dirname + "/dist") archive.glob("**/*", {
? fs.readdirSync(__dirname + "/dist") cwd: __dirname + "/dist",
: []; ignore: [
distFilesAndDirectories.forEach((entry) => { "svr.js",
if (entry == "svr.js" || entry == "svr.compressed") return; "svr.compressed"
const stats = fs.statSync(__dirname + "/dist/" + entry); ],
if (stats.isDirectory()) { dot: true
archive.directory(__dirname + "/dist/" + entry, entry);
} else if (stats.isFile()) {
archive.file(__dirname + "/dist/" + entry, { name: entry });
}
}); });
// Create a stream for the "svr.compressed" file // Create a stream for the "svr.compressed" file

View file

@ -44,7 +44,7 @@ function clientErrorHandler(err, socket) {
if (err.code === "ECONNRESET" || !socket.writable) { if (err.code === "ECONNRESET" || !socket.writable) {
return; return;
} }
socket.end(x, function () { socket.end(x, () => {
try { try {
socket.destroy(); socket.destroy();
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
@ -60,9 +60,9 @@ function clientErrorHandler(err, socket) {
headers = Object.assign({}, headers); headers = Object.assign({}, headers);
headers["Date"] = new Date().toGMTString(); headers["Date"] = new Date().toGMTString();
headers["Connection"] = "close"; headers["Connection"] = "close";
Object.keys(headers).forEach(function (headername) { Object.keys(headers).forEach((headername) => {
if (headername.toLowerCase() == "set-cookie") { if (headername.toLowerCase() == "set-cookie") {
headers[headername].forEach(function (headerValueS) { headers[headername].forEach((headerValueS) => {
if ( if (
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex
headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) || headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) ||
@ -105,7 +105,7 @@ function clientErrorHandler(err, socket) {
locmessage: (msg) => serverconsole.locmessage(msg, reqId), locmessage: (msg) => serverconsole.locmessage(msg, reqId),
}; };
socket.on("close", function (hasError) { socket.on("close", (hasError) => {
if ( if (
!hasError || !hasError ||
err.code == "ERR_SSL_HTTP_REQUEST" || err.code == "ERR_SSL_HTTP_REQUEST" ||
@ -114,7 +114,7 @@ function clientErrorHandler(err, socket) {
logFacilities.locmessage("Client disconnected."); logFacilities.locmessage("Client disconnected.");
else logFacilities.locmessage("Client disconnected due to error."); else logFacilities.locmessage("Client disconnected due to error.");
}); });
socket.on("error", function () {}); socket.on("error", () => {});
// Header and footer placeholders // Header and footer placeholders
let head = ""; let head = "";
@ -178,7 +178,7 @@ function clientErrorHandler(err, socket) {
if (p) callback(p); if (p) callback(p);
else { else {
if (errorCode == 404) { if (errorCode == 404) {
fs.access(config.page404, fs.constants.F_OK, function (err) { fs.access(config.page404, fs.constants.F_OK, (err) => {
if (err) { if (err) {
fs.access( fs.access(
"." + errorCode.toString(), "." + errorCode.toString(),
@ -229,7 +229,7 @@ function clientErrorHandler(err, socket) {
getErrorFileName(list, callback, _i + 1); getErrorFileName(list, callback, _i + 1);
return; return;
} else { } else {
fs.access(list[_i].path, fs.constants.F_OK, function (err) { fs.access(list[_i].path, fs.constants.F_OK, (err) => {
if (err) { if (err) {
getErrorFileName(list, callback, _i + 1); getErrorFileName(list, callback, _i + 1);
} else { } else {
@ -239,7 +239,7 @@ function clientErrorHandler(err, socket) {
} }
}; };
getErrorFileName(config.errorPages, function (errorFile) { getErrorFileName(config.errorPages, (errorFile) => {
if (Object.prototype.toString.call(stack) === "[object Error]") if (Object.prototype.toString.call(stack) === "[object Error]")
stack = generateErrorStack(stack); stack = generateErrorStack(stack);
if (stack === undefined) if (stack === undefined)
@ -313,7 +313,7 @@ function clientErrorHandler(err, socket) {
); );
res.end(); res.end();
} else { } else {
fs.readFile(errorFile, function (err, data) { fs.readFile(errorFile, (err, data) => {
try { try {
if (err) throw err; if (err) throw err;
res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders); res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);

View file

@ -29,8 +29,8 @@ function noproxyHandler(req, socket, head) {
// SVR.JS configuration object (modified) // SVR.JS configuration object (modified)
const config = deepClone(process.serverConfig); const config = deepClone(process.serverConfig);
var reqip = socket.remoteAddress; const reqip = socket.remoteAddress;
var reqport = socket.remotePort; const reqport = socket.remotePort;
process.reqcounter++; process.reqcounter++;
logFacilities.locmessage( logFacilities.locmessage(
`Somebody connected to ${ `Somebody connected to ${

View file

@ -32,12 +32,11 @@ function proxyHandler(req, socket, head) {
// SVR.JS configuration object (modified) // SVR.JS configuration object (modified)
const config = deepClone(process.serverConfig); const config = deepClone(process.serverConfig);
config.generateServerString = () => { config.generateServerString = () =>
return generateServerString(config.exposeServerVersion); generateServerString(config.exposeServerVersion);
};
var reqip = socket.remoteAddress; const reqip = socket.remoteAddress;
var reqport = socket.remotePort; const reqport = socket.remotePort;
process.reqcounter++; process.reqcounter++;
logFacilities.locmessage( logFacilities.locmessage(
`Somebody connected to ${ `Somebody connected to ${

View file

@ -34,9 +34,8 @@ function requestHandler(req, res) {
// SVR.JS configuration object (modified) // SVR.JS configuration object (modified)
const config = deepClone(process.serverConfig); const config = deepClone(process.serverConfig);
config.generateServerString = () => { config.generateServerString = () =>
return generateServerString(config.exposeServerVersion); generateServerString(config.exposeServerVersion);
};
// getCustomHeaders() in SVR.JS 3.x // getCustomHeaders() in SVR.JS 3.x
config.getCustomHeaders = () => { config.getCustomHeaders = () => {
@ -79,7 +78,7 @@ function requestHandler(req, res) {
if (table == undefined) table = this.tHeaders; if (table == undefined) table = this.tHeaders;
if (table == undefined) table = {}; if (table == undefined) table = {};
table = Object.assign({}, table); table = Object.assign({}, table);
Object.keys(table).forEach(function (key) { Object.keys(table).forEach((key) => {
const al = key.toLowerCase(); const al = key.toLowerCase();
if ( if (
al == "transfer-encoding" || al == "transfer-encoding" ||
@ -343,7 +342,7 @@ function requestHandler(req, res) {
if (p) callback(p); if (p) callback(p);
else { else {
if (errorCode == 404) { if (errorCode == 404) {
fs.access(config.page404, fs.constants.F_OK, function (err) { fs.access(config.page404, fs.constants.F_OK, (err) => {
if (err) { if (err) {
fs.access( fs.access(
"." + errorCode.toString(), "." + errorCode.toString(),
@ -400,7 +399,7 @@ function requestHandler(req, res) {
getErrorFileName(list, callback, _i + 1); getErrorFileName(list, callback, _i + 1);
return; return;
} else { } else {
fs.access(list[_i].path, fs.constants.F_OK, function (err) { fs.access(list[_i].path, fs.constants.F_OK, (err) => {
if (err) { if (err) {
getErrorFileName(list, callback, _i + 1); getErrorFileName(list, callback, _i + 1);
} else { } else {
@ -410,7 +409,7 @@ function requestHandler(req, res) {
} }
}; };
getErrorFileName(config.errorPages, function (errorFile) { getErrorFileName(config.errorPages, (errorFile) => {
// Generate error stack if not provided // Generate error stack if not provided
if (Object.prototype.toString.call(stack) === "[object Error]") if (Object.prototype.toString.call(stack) === "[object Error]")
stack = generateErrorStack(stack); stack = generateErrorStack(stack);
@ -442,7 +441,7 @@ function requestHandler(req, res) {
cheaders["Allow"] = "GET, POST, HEAD, OPTIONS"; cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
// Read the error file and replace placeholders with error information // Read the error file and replace placeholders with error information
fs.readFile(errorFile, function (err, data) { fs.readFile(errorFile, (err, data) => {
try { try {
if (err) throw err; if (err) throw err;
res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders); res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders);

View file

@ -38,7 +38,7 @@ function serverErrorHandler(err, isRedirect, server, start) {
} catch (err) { } catch (err) {
// Probably main process exited // Probably main process exited
} }
setTimeout(function () { setTimeout(() => {
const errno = os.constants.errno[err.code]; const errno = os.constants.errno[err.code];
process.exit(errno !== undefined ? errno : 1); process.exit(errno !== undefined ? errno : 1);
}, 50); }, 50);

View file

@ -134,16 +134,14 @@ for (
args[i] == "/h" || args[i] == "/h" ||
args[i] == "/?" args[i] == "/?"
) { ) {
console.log(name + " usage:"); console.log(`${name} usage:`);
console.log( console.log(
"node svr.js [-h] [--help] [-?] [/h] [/?] [--secure] [--reset] [--clean] [--disable-mods] [--single-threaded] [-v] [--version]", "node svr.js [-h] [--help] [-?] [/h] [/?] [--secure] [--reset] [--clean] [--disable-mods] [--single-threaded] [-v] [--version]",
); );
console.log("-h -? /h /? --help -- Displays help"); console.log("-h -? /h /? --help -- Displays help");
console.log("--clean -- Cleans up files created by " + name); console.log("--clean -- Cleans up files created by " + name);
console.log( console.log(
"--reset -- Resets " + `--reset -- Resets ${name} to default settings (WARNING: DANGEROUS)`,
name +
" to default settings (WARNING: DANGEROUS)",
); );
console.log("--secure -- Runs HTTPS server"); console.log("--secure -- Runs HTTPS server");
console.log("--disable-mods -- Disables mods (safe mode)"); console.log("--disable-mods -- Disables mods (safe mode)");
@ -180,17 +178,15 @@ for (
} else if (args[i] == "--single-threaded") { } else if (args[i] == "--single-threaded") {
process.singleThreaded = true; process.singleThreaded = true;
} else { } else {
console.log("Unrecognized argument: " + args[i]); console.log(`Unrecognized argument: ${args[i]}`);
console.log(name + " usage:"); console.log(`${name} usage:`);
console.log( console.log(
"node svr.js [-h] [--help] [-?] [/h] [/?] [--secure] [--reset] [--clean] [--disable-mods] [--single-threaded] [-v] [--version]", "node svr.js [-h] [--help] [-?] [/h] [/?] [--secure] [--reset] [--clean] [--disable-mods] [--single-threaded] [-v] [--version]",
); );
console.log("-h -? /h /? --help -- Displays help"); console.log("-h -? /h /? --help -- Displays help");
console.log("--clean -- Cleans up files created by " + name); console.log("--clean -- Cleans up files created by " + name);
console.log( console.log(
"--reset -- Resets " + `--reset -- Resets ${name} to default settings (WARNING: DANGEROUS)`,
name +
" to default settings (WARNING: DANGEROUS)",
); );
console.log("--secure -- Runs HTTPS server"); console.log("--secure -- Runs HTTPS server");
console.log("--disable-mods -- Disables mods (safe mode)"); console.log("--disable-mods -- Disables mods (safe mode)");
@ -422,7 +418,7 @@ try {
} catch (err) { } catch (err) {
ifaceEx = err; ifaceEx = err;
} }
var ips = []; let ips = [];
const brdIPs = ["255.255.255.255", "127.255.255.255", "0.255.255.255"]; const brdIPs = ["255.255.255.255", "127.255.255.255", "0.255.255.255"];
const netIPs = ["127.0.0.0"]; const netIPs = ["127.0.0.0"];
@ -461,7 +457,7 @@ if (ips.length == 0) {
} }
// Server IP address // Server IP address
var host = ips[ips.length - 1]; let host = ips[ips.length - 1];
if (!host) host = "[offline]"; if (!host) host = "[offline]";
// Public IP address-related // Public IP address-related
@ -821,7 +817,7 @@ if (!disableMods) {
crypto.__disabled__ === undefined crypto.__disabled__ === undefined
? "var crypto = require('crypto');\r\nvar https = require('https');\r\n" ? "var crypto = require('crypto');\r\nvar https = require('https');\r\n"
: "" : ""
}var stream = require('stream');\r\nvar customvar1;\r\nvar customvar2;\r\nvar customvar3;\r\nvar customvar4;\r\n\r\nfunction Mod() {}\r\nMod.prototype.callback = function callback(req, res, serverconsole, responseEnd, href, ext, uobject, search, defaultpage, users, page404, head, foot, fd, elseCallback, configJSON, callServerError, getCustomHeaders, origHref, redirect, parsePostData, authUser) {\r\nreturn () => {\r\nvar disableEndElseCallbackExecute = false;\r\nfunction filterHeaders(e){var r={};return Object.keys(e).forEach(((t) => {null!==e[t]&&void 0!==e[t]&&("object"==typeof e[t]?r[t]=JSON.parse(JSON.stringify(e[t])):r[t]=e[t])})),r}\r\nfunction checkHostname(e){if(void 0===e||"*"==e)return!0;if(req.headers.host&&0==e.indexOf("*.")&&"*."!=e){var r=e.substring(2);if(req.headers.host==r||req.headers.host.indexOf("."+r)==req.headers.host.length-r.length-1)return!0}else if(req.headers.host&&req.headers.host==e)return!0;return!1}\r\nfunction checkHref(e){return href==e||"win32"==os.platform()&&href.toLowerCase()==e.toLowerCase()}\r\n`; }var stream = require('stream');\r\nvar customvar1;\r\nvar customvar2;\r\nvar customvar3;\r\nvar customvar4;\r\n\r\nfunction Mod() {}\r\nMod.prototype.callback = function callback(req, res, serverconsole, responseEnd, href, ext, uobject, search, defaultpage, users, page404, head, foot, fd, elseCallback, configJSON, callServerError, getCustomHeaders, origHref, redirect, parsePostData, authUser) {\r\nreturn function() {\r\nvar disableEndElseCallbackExecute = false;\r\nfunction filterHeaders(e){var r={};return Object.keys(e).forEach((function(t){null!==e[t]&&void 0!==e[t]&&("object"==typeof e[t]?r[t]=JSON.parse(JSON.stringify(e[t])):r[t]=e[t])})),r}\r\nfunction checkHostname(e){if(void 0===e||"*"==e)return!0;if(req.headers.host&&0==e.indexOf("*.")&&"*."!=e){var r=e.substring(2);if(req.headers.host==r||req.headers.host.indexOf("."+r)==req.headers.host.length-r.length-1)return!0}else if(req.headers.host&&req.headers.host==e)return!0;return!1}\r\nfunction checkHref(e){return href==e||"win32"==os.platform()&&href.toLowerCase()==e.toLowerCase()}\r\n`;
const modfoot = const modfoot =
"\r\nif(!disableEndElseCallbackExecute) {\r\ntry{\r\nelseCallback();\r\n} catch(err) {\r\n}\r\n}\r\n}\r\n}\r\nmodule.exports = Mod;"; "\r\nif(!disableEndElseCallbackExecute) {\r\ntry{\r\nelseCallback();\r\n} catch(err) {\r\n}\r\n}\r\n}\r\n}\r\nmodule.exports = Mod;";
// Write the modified server side script to the temp folder // Write the modified server side script to the temp folder
@ -1132,7 +1128,7 @@ if (process.serverConfig.secure) {
key: sniCredentialsSingle.key, key: sniCredentialsSingle.key,
}); });
try { try {
var snMatches = sniCredentialsSingle.name.match( let snMatches = sniCredentialsSingle.name.match(
/^([^:[]*|\[[^]]*\]?)((?::.*)?)$/, /^([^:[]*|\[[^]]*\]?)((?::.*)?)$/,
); );
if (!snMatches[1][0].match(/^\.+$/)) if (!snMatches[1][0].match(/^\.+$/))
@ -1414,7 +1410,7 @@ function SVRJSFork() {
"Starting next thread, because previous one hung up/crashed...", "Starting next thread, because previous one hung up/crashed...",
); );
// Fork new worker // Fork new worker
var newWorker = {}; let newWorker = {};
try { try {
if ( if (
!threadLimitWarned && !threadLimitWarned &&
@ -1518,7 +1514,7 @@ function getWorkerCountToFork() {
} }
function forkWorkers(workersToFork, callback) { function forkWorkers(workersToFork, callback) {
for (var i = 0; i < workersToFork; i++) { for (let i = 0; i < workersToFork; i++) {
if (i == 0) { if (i == 0) {
SVRJSFork(); SVRJSFork();
} else { } else {
@ -1582,9 +1578,9 @@ function msgListener(message) {
// Save configuration file // Save configuration file
function saveConfig() { function saveConfig() {
for (var i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
try { try {
var configJSONobj = {}; let configJSONobj = {};
if (fs.existsSync(process.dirname + "/config.json")) if (fs.existsSync(process.dirname + "/config.json"))
configJSONobj = JSON.parse( configJSONobj = JSON.parse(
fs.readFileSync(process.dirname + "/config.json").toString(), fs.readFileSync(process.dirname + "/config.json").toString(),
@ -1674,12 +1670,14 @@ function saveConfig() {
if (configJSONobj.optOutOfStatisticsServer === undefined) if (configJSONobj.optOutOfStatisticsServer === undefined)
configJSONobj.optOutOfStatisticsServer = false; configJSONobj.optOutOfStatisticsServer = false;
var configString = JSON.stringify(configJSONobj, null, 2) + "\n"; fs.writeFileSync(
fs.writeFileSync(__dirname + "/config.json", configString); process.dirname + "/config.json",
JSON.stringify(configJSONobj, null, 2) + "\n",
);
break; break;
} catch (err) { } catch (err) {
if (i >= 2) throw err; if (i >= 2) throw err;
var now = Date.now(); const now = Date.now();
while (Date.now() - now < 2); while (Date.now() - now < 2);
} }
} }
@ -1717,9 +1715,7 @@ function start(init) {
/^(?:0\.|1\.0\.|1\.1\.[0-9](?![0-9])|1\.1\.1[0-2](?![0-9]))/, /^(?:0\.|1\.0\.|1\.1\.[0-9](?![0-9])|1\.1\.1[0-2](?![0-9]))/,
) )
) && ) &&
process.serverConfig.users.some((entry) => { process.serverConfig.users.some((entry) => entry.pbkdf2)
return entry.pbkdf2;
})
) )
serverconsole.locwarnmessage( serverconsole.locwarnmessage(
"PBKDF2 password hashing function in Bun versions older than v1.1.13 blocks the event loop, which may result in denial of service.", "PBKDF2 password hashing function in Bun versions older than v1.1.13 blocks the event loop, which may result in denial of service.",
@ -1967,8 +1963,8 @@ function start(init) {
}, 300000); }, 300000);
} else if (cluster.isPrimary) { } else if (cluster.isPrimary) {
setInterval(() => { setInterval(() => {
var allWorkers = Object.keys(cluster.workers); let allWorkers = Object.keys(cluster.workers);
var goodWorkers = []; let goodWorkers = [];
const checkWorker = (callback, _id) => { const checkWorker = (callback, _id) => {
if (typeof _id === "undefined") _id = 0; if (typeof _id === "undefined") _id = 0;
@ -2058,8 +2054,8 @@ function start(init) {
commands[line.split(" ")[0]] !== undefined && commands[line.split(" ")[0]] !== undefined &&
commands[line.split(" ")[0]] !== null commands[line.split(" ")[0]] !== null
) { ) {
var argss = line.split(" "); let argss = line.split(" ");
var command = argss.shift(); const command = argss.shift();
commands[command](argss, (msg) => process.send(msg)); commands[command](argss, (msg) => process.send(msg));
process.send("\x12END"); process.send("\x12END");
} else { } else {
@ -2087,15 +2083,15 @@ function start(init) {
const command = argss.shift(); const command = argss.shift();
if (line != "") { if (line != "") {
if (cluster.isPrimary !== undefined) { if (cluster.isPrimary !== undefined) {
var allWorkers = Object.keys(cluster.workers); let allWorkers = Object.keys(cluster.workers);
if (command == "block") if (command == "block")
commands.block(argss, serverconsole.climessage); commands.block(argss, serverconsole.climessage);
if (command == "unblock") if (command == "unblock")
commands.unblock(argss, serverconsole.climessage); commands.unblock(argss, serverconsole.climessage);
if (command == "restart") { if (command == "restart") {
var stopError = false; let stopError = false;
exiting = true; exiting = true;
for (var i = 0; i < allWorkers.length; i++) { for (let i = 0; i < allWorkers.length; i++) {
try { try {
if (cluster.workers[allWorkers[i]]) { if (cluster.workers[allWorkers[i]]) {
cluster.workers[allWorkers[i]].kill(); cluster.workers[allWorkers[i]].kill();
@ -2150,9 +2146,7 @@ function start(init) {
commands[command](argss, serverconsole.climessage); commands[command](argss, serverconsole.climessage);
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
} catch (err) { } catch (err) {
serverconsole.climessage( serverconsole.climessage(`Unrecognized command "${command}".`);
'Unrecognized command "' + command + '".',
);
} }
} }
} }
@ -2224,7 +2218,7 @@ function start(init) {
!process.serverConfig.secure !process.serverConfig.secure
) { ) {
// It doesn't support through Unix sockets or Windows named pipes // It doesn't support through Unix sockets or Windows named pipes
var address = ( let address = (
typeof process.serverConfig.port == "number" && listenAddress typeof process.serverConfig.port == "number" && listenAddress
? listenAddress ? listenAddress
: "localhost" : "localhost"
@ -2232,7 +2226,7 @@ function start(init) {
if (address.indexOf(":") > -1) { if (address.indexOf(":") > -1) {
address = "[" + address + "]"; address = "[" + address + "]";
} }
var connection = http2.connect( const connection = http2.connect(
"http://" + "http://" +
address + address +
":" + ":" +

View file

@ -23,14 +23,14 @@ module.exports.commands = {
if (ip == undefined || JSON.stringify(ip) == "[]") { if (ip == undefined || JSON.stringify(ip) == "[]") {
if (!cluster.isPrimary === false) log("Cannot block non-existent IP."); if (!cluster.isPrimary === false) log("Cannot block non-existent IP.");
} else { } else {
for (var i = 0; i < ip.length; i++) { ip.forEach((ipAddress) => {
if (ip[i] != "localhost" && ip[i].indexOf(":") == -1) { if (ipAddress !== "localhost" && ipAddress.indexOf(":") == -1) {
ip[i] = "::ffff:" + ip[i]; ipAddress = "::ffff:" + ipAddress;
} }
if (!blocklist.check(ip[i])) { if (!blocklist.check(ipAddress)) {
blocklist.add(ip[i]); blocklist.add(ipAddress);
} }
} });
process.serverConfig.blacklist = blocklist.raw; process.serverConfig.blacklist = blocklist.raw;
if (!cluster.isPrimary === false) log("IPs successfully blocked."); if (!cluster.isPrimary === false) log("IPs successfully blocked.");
passCommand(ip, log); passCommand(ip, log);
@ -40,12 +40,12 @@ module.exports.commands = {
if (ip == undefined || JSON.stringify(ip) == "[]") { if (ip == undefined || JSON.stringify(ip) == "[]") {
if (!cluster.isPrimary === false) log("Cannot unblock non-existent IP."); if (!cluster.isPrimary === false) log("Cannot unblock non-existent IP.");
} else { } else {
for (var i = 0; i < ip.length; i++) { ip.forEach((ipAddress) => {
if (ip[i].indexOf(":") == -1) { if (ipAddress !== "localhost" && ipAddress.indexOf(":") == -1) {
ip[i] = "::ffff:" + ip[i]; ipAddress = "::ffff:" + ipAddress;
} }
blocklist.remove(ip[i]); blocklist.remove(ipAddress);
} });
process.serverConfig.blacklist = blocklist.raw; process.serverConfig.blacklist = blocklist.raw;
if (!cluster.isPrimary === false) log("IPs successfully unblocked."); if (!cluster.isPrimary === false) log("IPs successfully unblocked.");
passCommand(ip, log); passCommand(ip, log);

View file

@ -29,7 +29,7 @@ let passwordHashCacheIntervalId = -1;
// Non-standard code object // Non-standard code object
let nonStandardCodes = []; let nonStandardCodes = [];
process.serverConfig.nonStandardCodes.forEach((nonStandardCodeRaw) => { process.serverConfig.nonStandardCodes.forEach((nonStandardCodeRaw) => {
var newObject = {}; let newObject = {};
Object.keys(nonStandardCodeRaw).forEach((nsKey) => { Object.keys(nonStandardCodeRaw).forEach((nsKey) => {
if (nsKey != "users") { if (nsKey != "users") {
newObject[nsKey] = nonStandardCodeRaw[nsKey]; newObject[nsKey] = nonStandardCodeRaw[nsKey];
@ -42,12 +42,12 @@ process.serverConfig.nonStandardCodes.forEach((nonStandardCodeRaw) => {
if (!cluster.isPrimary) { if (!cluster.isPrimary) {
passwordHashCacheIntervalId = setInterval(() => { passwordHashCacheIntervalId = setInterval(() => {
pbkdf2Cache = pbkdf2Cache.filter((entry) => { pbkdf2Cache = pbkdf2Cache.filter(
return entry.addDate > new Date() - 3600000; (entry) => entry.addDate > new Date() - 3600000,
}); );
scryptCache = scryptCache.filter((entry) => { scryptCache = scryptCache.filter(
return entry.addDate > new Date() - 3600000; (entry) => entry.addDate > new Date() - 3600000,
}); );
}, 1800000); }, 1800000);
} }
@ -77,7 +77,7 @@ module.exports = (req, res, logFacilities, config, next) => {
); );
if (nonStandardCodes[i].regex) { if (nonStandardCodes[i].regex) {
// Regex match // Regex match
var createdRegex = createRegex(nonStandardCodes[i].regex, true); const createdRegex = createRegex(nonStandardCodes[i].regex, true);
isMatch = isMatch =
req.url.match(createdRegex) || req.url.match(createdRegex) ||
hrefWithoutDuplicateSlashes.match(createdRegex); hrefWithoutDuplicateSlashes.match(createdRegex);
@ -192,11 +192,10 @@ module.exports = (req, res, logFacilities, config, next) => {
); );
return; return;
} else { } else {
cacheEntry = scryptCache.find((entry) => { cacheEntry = scryptCache.find(
return ( (entry) =>
entry.password == hashedPassword && entry.salt == list[_i].salt entry.password == hashedPassword && entry.salt == list[_i].salt,
); );
});
if (cacheEntry) { if (cacheEntry) {
cb(cacheEntry.hash); cb(cacheEntry.hash);
} else { } else {
@ -226,11 +225,10 @@ module.exports = (req, res, logFacilities, config, next) => {
); );
return; return;
} else { } else {
cacheEntry = pbkdf2Cache.find((entry) => { cacheEntry = pbkdf2Cache.find(
return ( (entry) =>
entry.password == hashedPassword && entry.salt == list[_i].salt entry.password == hashedPassword && entry.salt == list[_i].salt,
); );
});
if (cacheEntry) { if (cacheEntry) {
cb(cacheEntry.hash); cb(cacheEntry.hash);
} else { } else {

View file

@ -9,7 +9,7 @@ module.exports = (req, res, logFacilities, config, next) => {
!config.disableNonEncryptedServer && !config.disableNonEncryptedServer &&
!config.disableToHTTPSRedirect !config.disableToHTTPSRedirect
) { ) {
var hostx = req.headers.host; const hostx = req.headers.host;
if (hostx === undefined) { if (hostx === undefined) {
logFacilities.errmessage("Host header is missing."); logFacilities.errmessage("Host header is missing.");
res.error(400); res.error(400);
@ -22,7 +22,7 @@ module.exports = (req, res, logFacilities, config, next) => {
return; return;
} }
var isPublicServer = !( const isPublicServer = !(
req.socket.realRemoteAddress req.socket.realRemoteAddress
? req.socket.realRemoteAddress ? req.socket.realRemoteAddress
: req.socket.remoteAddress : req.socket.remoteAddress
@ -30,11 +30,11 @@ module.exports = (req, res, logFacilities, config, next) => {
/^(?:localhost$|::1$|f[c-d][0-9a-f]{2}:|(?:::ffff:)?(?:(?:127|10)\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|192\.168\.[0-9]{1,3}\.[0-9]{1,3}|172\.(?:1[6-9]|2[0-9]|3[0-1])\.[0-9]{1,3}\.[0-9]{1,3})$)/i, /^(?:localhost$|::1$|f[c-d][0-9a-f]{2}:|(?:::ffff:)?(?:(?:127|10)\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|192\.168\.[0-9]{1,3}\.[0-9]{1,3}|172\.(?:1[6-9]|2[0-9]|3[0-1])\.[0-9]{1,3}\.[0-9]{1,3})$)/i,
); );
var destinationPort = 0; let destinationPort = 0;
var parsedHostx = hostx.match(/(\[[^\]]*\]|[^:]*)(?::([0-9]+))?/); const parsedHostx = hostx.match(/(\[[^\]]*\]|[^:]*)(?::([0-9]+))?/);
var hostname = parsedHostx[1]; let hostname = parsedHostx[1];
var hostPort = parsedHostx[2] ? parseInt(parsedHostx[2]) : 80; let hostPort = parsedHostx[2] ? parseInt(parsedHostx[2]) : 80;
if (isNaN(hostPort)) hostPort = 80; if (isNaN(hostPort)) hostPort = 80;
if ( if (

View file

@ -1,6 +1,6 @@
module.exports = (req, res, logFacilities, config, next) => { module.exports = (req, res, logFacilities, config, next) => {
if (!req.isProxy) { if (!req.isProxy) {
var hkh = config.getCustomHeaders(); const hkh = config.getCustomHeaders();
Object.keys(hkh).forEach((hkS) => { Object.keys(hkh).forEach((hkS) => {
try { try {
res.setHeader(hkS, hkh[hkS]); res.setHeader(hkS, hkh[hkS]);

View file

@ -30,7 +30,7 @@ module.exports = (req, res, logFacilities, config, next) => {
fs.stat( fs.stat(
"." + decodeURIComponent(req.parsedURL.pathname), "." + decodeURIComponent(req.parsedURL.pathname),
(err, stats) => { (err, stats) => {
var _fileState = 3; let _fileState = 3;
if (err) { if (err) {
_fileState = 3; _fileState = 3;
} else if (stats.isDirectory()) { } else if (stats.isDirectory()) {
@ -126,7 +126,7 @@ module.exports = (req, res, logFacilities, config, next) => {
logFacilities.errmessage("Content blocked."); logFacilities.errmessage("Content blocked.");
return; return;
} else if (sHref != req.parsedURL.pathname) { } else if (sHref != req.parsedURL.pathname) {
var rewrittenAgainURL = const rewrittenAgainURL =
sHref + sHref +
(req.parsedURL.search ? req.parsedURL.search : "") + (req.parsedURL.search ? req.parsedURL.search : "") +
(req.parsedURL.hash ? req.parsedURL.hash : ""); (req.parsedURL.hash ? req.parsedURL.hash : "");

View file

@ -34,7 +34,7 @@ module.exports = (req, res, logFacilities, config, next) => {
let levelDownCount = 0; let levelDownCount = 0;
// Loop through the path components // Loop through the path components
for (var i = 0; i < pathComponents.length; i++) { for (let i = 0; i < pathComponents.length; i++) {
// If the component is "..", decrement the levelUpCount // If the component is "..", decrement the levelUpCount
if (".." === pathComponents[i]) { if (".." === pathComponents[i]) {
levelUpCount--; levelUpCount--;
@ -313,7 +313,7 @@ module.exports = (req, res, logFacilities, config, next) => {
// Helper function to check if compression is allowed for the file // Helper function to check if compression is allowed for the file
const canCompress = (path, list) => { const canCompress = (path, list) => {
let canCompress = true; let canCompress = true;
for (var i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
if (createRegex(list[i], true).test(path)) { if (createRegex(list[i], true).test(path)) {
canCompress = false; canCompress = false;
break; break;
@ -437,7 +437,7 @@ module.exports = (req, res, logFacilities, config, next) => {
}) })
.on("open", () => { .on("open", () => {
try { try {
var resStream = {}; let resStream = {};
if (useBrotli && isCompressable) { if (useBrotli && isCompressable) {
resStream = zlib.createBrotliCompress(); resStream = zlib.createBrotliCompress();
resStream.pipe(res); resStream.pipe(res);
@ -738,7 +738,7 @@ module.exports = (req, res, logFacilities, config, next) => {
// Get stats for all files in the directory and generate the listing // Get stats for all files in the directory and generate the listing
getStatsForAllFiles(list, readFrom, (filelist) => { getStatsForAllFiles(list, readFrom, (filelist) => {
let directoryListingRows = []; let directoryListingRows = [];
for (var i = 0; i < filelist.length; i++) { for (let i = 0; i < filelist.length; i++) {
if (filelist[i].name[0] !== ".") { if (filelist[i].name[0] !== ".") {
const estats = filelist[i].stats; const estats = filelist[i].stats;
const ename = filelist[i].name; const ename = filelist[i].name;
@ -773,7 +773,7 @@ module.exports = (req, res, logFacilities, config, next) => {
.replace(/>/g, "&gt;")}</a></td><td>${ .replace(/>/g, "&gt;")}</a></td><td>${
estats.isDirectory() estats.isDirectory()
? "-" ? "-"
: sizify(estats.size.toString()) : sizify(estats.size)
}</td><td>${estats.mtime.toDateString()}</td></tr>\r\n`; }</td><td>${estats.mtime.toDateString()}</td></tr>\r\n`;
// Determine the file type and set the appropriate image and alt text // Determine the file type and set the appropriate image and alt text

View file

@ -26,9 +26,7 @@ if (!process.singleThreaded) {
cluster.worker = { cluster.worker = {
id: parseInt(process.env.NODE_UNIQUE_ID), id: parseInt(process.env.NODE_UNIQUE_ID),
process: process, process: process,
isDead: () => { isDead: () => false,
return false;
},
send: (message, ...params) => { send: (message, ...params) => {
process.send(message, ...params); process.send(message, ...params);
}, },
@ -98,9 +96,7 @@ if (!process.singleThreaded) {
}); });
newWorker.process = newWorker; newWorker.process = newWorker;
newWorker.isDead = () => { newWorker.isDead = () => newWorker.exitCode !== null || newWorker.killed;
return newWorker.exitCode !== null || newWorker.killed;
};
newWorker.id = newEnvironment.NODE_UNIQUE_ID; newWorker.id = newEnvironment.NODE_UNIQUE_ID;
function checkSendImplementation(worker) { function checkSendImplementation(worker) {

View file

@ -54,16 +54,23 @@ function isIndexOfForbiddenPath(decodedHref, match) {
if (typeof forbiddenPath === "string") { if (typeof forbiddenPath === "string") {
const forbiddenPathLower = isWin32 ? forbiddenPath.toLowerCase() : null; const forbiddenPathLower = isWin32 ? forbiddenPath.toLowerCase() : null;
return isWin32 return isWin32
? decodedHrefLower.indexOf(forbiddenPathLower) == 0 ? decodedHrefLower === forbiddenPathLower ||
: decodedHref.indexOf(forbiddenPath) == 0; decodedHrefLower.indexOf(forbiddenPathLower + "/") == 0
: decodedHref === forbiddenPath ||
decodedHref.indexOf(forbiddenPath + "/") == 0;
} }
if (typeof forbiddenPath === "object") { if (typeof forbiddenPath === "object") {
return isWin32 return isWin32
? forbiddenPath.some( ? forbiddenPath.some(
(path) => decodedHrefLower.indexOf(path.toLowerCase()) == 0, (path) =>
decodedHrefLower === path.toLowerCase() ||
decodedHrefLower.indexOf(path.toLowerCase() + "/") == 0,
) )
: forbiddenPath.some((path) => decodedHref.indexOf(path) == 0); : forbiddenPath.some(
(path) =>
decodedHref === path || decodedHref.indexOf(path + "/") == 0,
);
} }
return false; return false;

View file

@ -1,19 +1,19 @@
// Generate V8-style error stack from Error object. // Generate V8-style error stack from Error object.
function generateErrorStack(errorObject) { function generateErrorStack(errorObject) {
// Split the error stack by newlines. // Split the error stack by newlines.
var errorStack = errorObject.stack ? errorObject.stack.split("\n") : []; const 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 the error stack starts with the error name, return the original stack (it is V8-style then).
if ( if (
errorStack.some((errorStackLine) => { errorStack.some(
return errorStackLine.indexOf(errorObject.name) == 0; (errorStackLine) => errorStackLine.indexOf(errorObject.name) == 0,
}) )
) { ) {
return errorObject.stack; return errorObject.stack;
} }
// Create a new error stack with the error name and code (if available). // Create a new error stack with the error name and code (if available).
var newErrorStack = [ let newErrorStack = [
errorObject.name + errorObject.name +
(errorObject.code ? ": " + errorObject.code : "") + (errorObject.code ? ": " + errorObject.code : "") +
(errorObject.message == "" ? "" : ": " + errorObject.message), (errorObject.message == "" ? "" : ": " + errorObject.message),
@ -23,12 +23,12 @@ function generateErrorStack(errorObject) {
errorStack.forEach((errorStackLine) => { errorStack.forEach((errorStackLine) => {
if (errorStackLine != "") { if (errorStackLine != "") {
// Split the line into function and location parts (if available). // Split the line into function and location parts (if available).
var errorFrame = errorStackLine.split("@"); let errorFrame = errorStackLine.split("@");
var location = ""; let location = "";
if (errorFrame.length > 1 && errorFrame[0] == "global code") if (errorFrame.length > 1 && errorFrame[0] == "global code")
errorFrame.shift(); errorFrame.shift();
if (errorFrame.length > 1) location = errorFrame.pop(); if (errorFrame.length > 1) location = errorFrame.pop();
var func = errorFrame.join("@"); const func = errorFrame.join("@");
// Build the new error stack entry with function and location information. // Build the new error stack entry with function and location information.
newErrorStack.push( newErrorStack.push(

View file

@ -1,12 +1,12 @@
const os = require("os"); const os = require("os");
function getOS() { function getOS() {
var osType = os.type(); const osType = os.type();
var platform = os.platform(); const platform = os.platform();
if (platform == "android") { if (platform == "android") {
return "Android"; return "Android";
} else if (osType == "Windows_NT" || osType == "WindowsNT") { } else if (osType == "Windows_NT" || osType == "WindowsNT") {
var arch = os.arch(); const arch = os.arch();
if (arch == "ia32") { if (arch == "ia32") {
return "Win32"; return "Win32";
} else if (arch == "x64") { } else if (arch == "x64") {

View file

@ -2,7 +2,7 @@
function ipBlockList(rawBlockList) { function ipBlockList(rawBlockList) {
// Initialize the instance with empty arrays // Initialize the instance with empty arrays
if (rawBlockList === undefined) rawBlockList = []; if (rawBlockList === undefined) rawBlockList = [];
var instance = { const instance = {
raw: [], raw: [],
rawtoPreparedMap: [], rawtoPreparedMap: [],
prepared: [], prepared: [],
@ -10,9 +10,8 @@ function ipBlockList(rawBlockList) {
}; };
// Function to normalize IPv4 address (remove leading zeros) // Function to normalize IPv4 address (remove leading zeros)
const normalizeIPv4Address = (address) => { const normalizeIPv4Address = (address) =>
return address.replace(/(^|\.)(?:0(?!\.|$))+/g, "$1"); address.replace(/(^|\.)(?:0(?!\.|$))+/g, "$1");
};
// Function to expand IPv6 address to full format // Function to expand IPv6 address to full format
const expandIPv6Address = (address) => { const expandIPv6Address = (address) => {
@ -236,9 +235,7 @@ function ipBlockList(rawBlockList) {
? checkIfIPv4CIDRMatches ? checkIfIPv4CIDRMatches
: checkIfIPv6CIDRMatches; : checkIfIPv6CIDRMatches;
return instance.cidrs.some((iCidr) => { return instance.cidrs.some((iCidr) => checkMethod(ipParsedObject, iCidr));
return checkMethod(ipParsedObject, iCidr);
});
}; };
// Add initial raw block list values to the instance // Add initial raw block list values to the instance

View file

@ -4,9 +4,8 @@ function ipMatch(IP1, IP2) {
if (!IP2) return false; if (!IP2) return false;
// Function to normalize IPv4 address (remove leading zeros) // Function to normalize IPv4 address (remove leading zeros)
const normalizeIPv4Address = (address) => { const normalizeIPv4Address = (address) =>
return address.replace(/(^|\.)(?:0(?!\.|$))+/g, "$1"); address.replace(/(^|\.)(?:0(?!\.|$))+/g, "$1");
};
// Function to expand IPv6 address to full format // Function to expand IPv6 address to full format
const expandIPv6Address = (address) => { const expandIPv6Address = (address) => {

View file

@ -14,7 +14,7 @@ function calculateBroadcastIPv4FromCidr(ipWithCidr) {
.split(".") .split(".")
.map((num, index) => { .map((num, index) => {
// Calculate resulting 8-bit // Calculate resulting 8-bit
var power = Math.max(Math.min(mask - index * 8, 8), 0); const power = Math.max(Math.min(mask - index * 8, 8), 0);
return ( return (
(parseInt(num) & ((Math.pow(2, power) - 1) << (8 - power))) | (parseInt(num) & ((Math.pow(2, power) - 1) << (8 - power))) |
(Math.pow(2, 8 - power) - 1) (Math.pow(2, 8 - power) - 1)
@ -39,7 +39,7 @@ function calculateNetworkIPv4FromCidr(ipWithCidr) {
.split(".") .split(".")
.map((num, index) => { .map((num, index) => {
// Calculate resulting 8-bit // Calculate resulting 8-bit
var power = Math.max(Math.min(mask - index * 8, 8), 0); const power = Math.max(Math.min(mask - index * 8, 8), 0);
return ( return (
parseInt(num) & parseInt(num) &
((Math.pow(2, power) - 1) << (8 - power)) ((Math.pow(2, power) - 1) << (8 - power))

View file

@ -78,7 +78,7 @@ function LOG(s) {
} }
// Server console function // Server console function
var serverconsole = { const serverconsole = {
climessage: (msg, reqId) => { climessage: (msg, reqId) => {
if (msg.indexOf("\n") != -1) { if (msg.indexOf("\n") != -1) {
msg.split("\n").forEach((nmsg) => { msg.split("\n").forEach((nmsg) => {

View file

@ -22,37 +22,14 @@ function sha256(s) {
return (msw << 16) | (lsw & 0xffff); return (msw << 16) | (lsw & 0xffff);
}; };
const S = (X, n) => { const S = (X, n) => (X >>> n) | (X << (32 - n));
return (X >>> n) | (X << (32 - n)); const R = (X, n) => X >>> n;
}; const Ch = (x, y, z) => (x & y) ^ (~x & z);
const Maj = (x, y, z) => (x & y) ^ (x & z) ^ (y & z);
const R = (X, n) => { const Sigma0256 = (x) => S(x, 2) ^ S(x, 13) ^ S(x, 22);
return X >>> n; const Sigma1256 = (x) => S(x, 6) ^ S(x, 11) ^ S(x, 25);
}; const Gamma0256 = (x) => S(x, 7) ^ S(x, 18) ^ R(x, 3);
const Gamma1256 = (x) => S(x, 17) ^ S(x, 19) ^ R(x, 10);
const Ch = (x, y, z) => {
return (x & y) ^ (~x & z);
};
const Maj = (x, y, z) => {
return (x & y) ^ (x & z) ^ (y & z);
};
const Sigma0256 = (x) => {
return S(x, 2) ^ S(x, 13) ^ S(x, 22);
};
const Sigma1256 = (x) => {
return S(x, 6) ^ S(x, 11) ^ S(x, 25);
};
const Gamma0256 = (x) => {
return S(x, 7) ^ S(x, 18) ^ R(x, 3);
};
const Gamma1256 = (x) => {
return S(x, 17) ^ S(x, 19) ^ R(x, 10);
};
function coreSha256(m, l) { function coreSha256(m, l) {
const K = new Array( const K = new Array(

View file

@ -1,5 +1,5 @@
function sizify(bytes, addI) { function sizify(bytes, addI) {
if (bytes === 0) return "0"; if (bytes == 0) return "0";
if (bytes < 0) bytes = -bytes; if (bytes < 0) bytes = -bytes;
const prefixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"]; const prefixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"];

View file

@ -1,11 +1,11 @@
{ {
"version": "4.0.0-beta2", "version": "4.0.0-beta3",
"name": "SVR.JS", "name": "SVR.JS",
"documentationURL": "https://svrjs.org/docs/tentative", "documentationURL": "https://svrjs.org/docs/tentative",
"statisticsServerCollectEndpoint": "https://statistics.svrjs.org/collect.svr", "statisticsServerCollectEndpoint": "https://statistics.svrjs.org/collect.svr",
"changes": [ "changes": [
"Fixed the bug with \"ext\" variable for .tar.gz mods.", "Fixed the bug related to forbidden path checking.",
"Fixed the regular expression in the URL parser.", "Fixed \"NaN\" file sizes in directory listings.",
"Optimized many functions" "SVR.JS zip archives now include empty directories."
] ]
} }

View file

@ -117,6 +117,13 @@ describe("Forbidden paths handling", () => {
expect( expect(
isIndexOfForbiddenPath("/notforbidden/", "serverSideScriptDirectories"), isIndexOfForbiddenPath("/notforbidden/", "serverSideScriptDirectories"),
).toBe(false); ).toBe(false);
expect(isIndexOfForbiddenPath("/config.json.fake", "config")).toBe(false);
expect(
isIndexOfForbiddenPath(
"/node_modules_fake/",
"serverSideScriptDirectories",
),
).toBe(false);
}); });
test("should handle case insensitivity on Windows", () => { test("should handle case insensitivity on Windows", () => {

View file

@ -51,4 +51,52 @@ describe("URL sanitizer", () => {
test('should return "/" for empty sanitized resource', () => { test('should return "/" for empty sanitized resource', () => {
expect(sanitizeURL("/../..")).toBe("/"); expect(sanitizeURL("/../..")).toBe("/");
}); });
test("should encode special characters", () => {
expect(sanitizeURL("/test<path>")).toBe("/test%3Cpath%3E");
expect(sanitizeURL("/test^path")).toBe("/test%5Epath");
expect(sanitizeURL("/test`path")).toBe("/test%60path");
expect(sanitizeURL("/test{path}")).toBe("/test%7Bpath%7D");
expect(sanitizeURL("/test|path")).toBe("/test%7Cpath");
});
test("should preserve certain characters", () => {
expect(sanitizeURL("/test!path")).toBe("/test!path");
expect(sanitizeURL("/test$path")).toBe("/test$path");
expect(sanitizeURL("/test&path")).toBe("/test&path");
expect(sanitizeURL("/test-path")).toBe("/test-path");
expect(sanitizeURL("/test=path")).toBe("/test=path");
expect(sanitizeURL("/test@path")).toBe("/test@path");
expect(sanitizeURL("/test_path")).toBe("/test_path");
expect(sanitizeURL("/test~path")).toBe("/test~path");
});
test("should decode URL-encoded characters while preserving certain characters", () => {
expect(sanitizeURL("/test%20path")).toBe("/test%20path");
expect(sanitizeURL("/test%21path")).toBe("/test!path");
expect(sanitizeURL("/test%22path")).toBe("/test%22path");
expect(sanitizeURL("/test%24path")).toBe("/test$path");
expect(sanitizeURL("/test%25path")).toBe("/test%25path");
expect(sanitizeURL("/test%26path")).toBe("/test&path");
expect(sanitizeURL("/test%2Dpath")).toBe("/test-path");
expect(sanitizeURL("/test%3Cpath")).toBe("/test%3Cpath");
expect(sanitizeURL("/test%3Dpath")).toBe("/test=path");
expect(sanitizeURL("/test%3Epath")).toBe("/test%3Epath");
expect(sanitizeURL("/test%40path")).toBe("/test@path");
expect(sanitizeURL("/test%5Fpath")).toBe("/test_path");
expect(sanitizeURL("/test%7Dpath")).toBe("/test%7Dpath");
expect(sanitizeURL("/test%7Epath")).toBe("/test~path");
});
test("should decode URL-encoded alphanumeric characters while preserving certain characters", () => {
expect(sanitizeURL("/conf%69g.json")).toBe("/config.json");
expect(sanitizeURL("/CONF%49G.JSON")).toBe("/CONFIG.JSON");
expect(sanitizeURL("/svr%32.js")).toBe("/svr2.js");
expect(sanitizeURL("/%73%76%72%32%2E%6A%73")).toBe("/svr2.js");
});
test("should decode URL-encoded characters regardless of the letter case of the URL encoding", () => {
expect(sanitizeURL("/%5f")).toBe("/_");
expect(sanitizeURL("/%5F")).toBe("/_");
});
}); });