From c5af172d1e71705278913e374486040ccee0949b Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Fri, 30 Aug 2024 08:09:02 +0200 Subject: [PATCH] Update to SVR.JS 4.0.0-beta3 --- esbuild.config.js | 18 ++---- src/handlers/clientErrorHandler.js | 18 +++--- src/handlers/noproxyHandler.js | 4 +- src/handlers/proxyHandler.js | 9 ++- src/handlers/requestHandler.js | 15 ++--- src/handlers/serverErrorHandler.js | 2 +- src/index.js | 64 +++++++++---------- src/middleware/blocklist.js | 22 +++---- .../nonStandardCodesAndHttpAuthentication.js | 34 +++++----- src/middleware/redirects.js | 12 ++-- src/middleware/responseHeaders.js | 2 +- src/middleware/rewriteURL.js | 4 +- .../staticFileServingAndDirectoryListings.js | 10 +-- src/utils/clusterBunShim.js | 8 +-- src/utils/forbiddenPaths.js | 15 +++-- src/utils/generateErrorStack.js | 16 ++--- src/utils/getOS.js | 6 +- src/utils/ipBlockList.js | 11 ++-- src/utils/ipMatch.js | 5 +- src/utils/ipSubnetUtils.js | 4 +- src/utils/serverconsole.js | 2 +- src/utils/sha256.js | 39 +++-------- src/utils/sizify.js | 2 +- svrjs.json | 8 +-- tests/utils/forbiddenPaths.test.js | 7 ++ tests/utils/urlSanitizer.test.js | 48 ++++++++++++++ 26 files changed, 201 insertions(+), 184 deletions(-) diff --git a/esbuild.config.js b/esbuild.config.js index 52795e7..35a8fc4 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -217,17 +217,13 @@ esbuild archive.pipe(output); // Add everything in the "dist" directory except for "svr.js" and "svr.compressed" - const distFilesAndDirectories = fs.existsSync(__dirname + "/dist") - ? fs.readdirSync(__dirname + "/dist") - : []; - distFilesAndDirectories.forEach((entry) => { - if (entry == "svr.js" || entry == "svr.compressed") return; - const stats = fs.statSync(__dirname + "/dist/" + entry); - if (stats.isDirectory()) { - archive.directory(__dirname + "/dist/" + entry, entry); - } else if (stats.isFile()) { - archive.file(__dirname + "/dist/" + entry, { name: entry }); - } + archive.glob("**/*", { + cwd: __dirname + "/dist", + ignore: [ + "svr.js", + "svr.compressed" + ], + dot: true }); // Create a stream for the "svr.compressed" file diff --git a/src/handlers/clientErrorHandler.js b/src/handlers/clientErrorHandler.js index a3d8f90..280431c 100644 --- a/src/handlers/clientErrorHandler.js +++ b/src/handlers/clientErrorHandler.js @@ -44,7 +44,7 @@ function clientErrorHandler(err, socket) { if (err.code === "ECONNRESET" || !socket.writable) { return; } - socket.end(x, function () { + socket.end(x, () => { try { socket.destroy(); // eslint-disable-next-line no-unused-vars @@ -60,9 +60,9 @@ function clientErrorHandler(err, socket) { headers = Object.assign({}, headers); headers["Date"] = new Date().toGMTString(); headers["Connection"] = "close"; - Object.keys(headers).forEach(function (headername) { + Object.keys(headers).forEach((headername) => { if (headername.toLowerCase() == "set-cookie") { - headers[headername].forEach(function (headerValueS) { + headers[headername].forEach((headerValueS) => { if ( // eslint-disable-next-line no-control-regex headername.match(/[^\x09\x20-\x7e\x80-\xff]|.:/) || @@ -105,7 +105,7 @@ function clientErrorHandler(err, socket) { locmessage: (msg) => serverconsole.locmessage(msg, reqId), }; - socket.on("close", function (hasError) { + socket.on("close", (hasError) => { if ( !hasError || err.code == "ERR_SSL_HTTP_REQUEST" || @@ -114,7 +114,7 @@ function clientErrorHandler(err, socket) { logFacilities.locmessage("Client disconnected."); else logFacilities.locmessage("Client disconnected due to error."); }); - socket.on("error", function () {}); + socket.on("error", () => {}); // Header and footer placeholders let head = ""; @@ -178,7 +178,7 @@ function clientErrorHandler(err, socket) { if (p) callback(p); else { if (errorCode == 404) { - fs.access(config.page404, fs.constants.F_OK, function (err) { + fs.access(config.page404, fs.constants.F_OK, (err) => { if (err) { fs.access( "." + errorCode.toString(), @@ -229,7 +229,7 @@ function clientErrorHandler(err, socket) { getErrorFileName(list, callback, _i + 1); return; } else { - fs.access(list[_i].path, fs.constants.F_OK, function (err) { + fs.access(list[_i].path, fs.constants.F_OK, (err) => { if (err) { getErrorFileName(list, callback, _i + 1); } 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]") stack = generateErrorStack(stack); if (stack === undefined) @@ -313,7 +313,7 @@ function clientErrorHandler(err, socket) { ); res.end(); } else { - fs.readFile(errorFile, function (err, data) { + fs.readFile(errorFile, (err, data) => { try { if (err) throw err; res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders); diff --git a/src/handlers/noproxyHandler.js b/src/handlers/noproxyHandler.js index 205b45f..1f5f126 100644 --- a/src/handlers/noproxyHandler.js +++ b/src/handlers/noproxyHandler.js @@ -29,8 +29,8 @@ function noproxyHandler(req, socket, head) { // SVR.JS configuration object (modified) const config = deepClone(process.serverConfig); - var reqip = socket.remoteAddress; - var reqport = socket.remotePort; + const reqip = socket.remoteAddress; + const reqport = socket.remotePort; process.reqcounter++; logFacilities.locmessage( `Somebody connected to ${ diff --git a/src/handlers/proxyHandler.js b/src/handlers/proxyHandler.js index 4bf8f9d..87b5f45 100644 --- a/src/handlers/proxyHandler.js +++ b/src/handlers/proxyHandler.js @@ -32,12 +32,11 @@ function proxyHandler(req, socket, head) { // SVR.JS configuration object (modified) const config = deepClone(process.serverConfig); - config.generateServerString = () => { - return generateServerString(config.exposeServerVersion); - }; + config.generateServerString = () => + generateServerString(config.exposeServerVersion); - var reqip = socket.remoteAddress; - var reqport = socket.remotePort; + const reqip = socket.remoteAddress; + const reqport = socket.remotePort; process.reqcounter++; logFacilities.locmessage( `Somebody connected to ${ diff --git a/src/handlers/requestHandler.js b/src/handlers/requestHandler.js index 4dca787..be2fc14 100644 --- a/src/handlers/requestHandler.js +++ b/src/handlers/requestHandler.js @@ -34,9 +34,8 @@ function requestHandler(req, res) { // SVR.JS configuration object (modified) const config = deepClone(process.serverConfig); - config.generateServerString = () => { - return generateServerString(config.exposeServerVersion); - }; + config.generateServerString = () => + generateServerString(config.exposeServerVersion); // getCustomHeaders() in SVR.JS 3.x config.getCustomHeaders = () => { @@ -79,7 +78,7 @@ function requestHandler(req, res) { if (table == undefined) table = this.tHeaders; if (table == undefined) table = {}; table = Object.assign({}, table); - Object.keys(table).forEach(function (key) { + Object.keys(table).forEach((key) => { const al = key.toLowerCase(); if ( al == "transfer-encoding" || @@ -343,7 +342,7 @@ function requestHandler(req, res) { if (p) callback(p); else { if (errorCode == 404) { - fs.access(config.page404, fs.constants.F_OK, function (err) { + fs.access(config.page404, fs.constants.F_OK, (err) => { if (err) { fs.access( "." + errorCode.toString(), @@ -400,7 +399,7 @@ function requestHandler(req, res) { getErrorFileName(list, callback, _i + 1); return; } else { - fs.access(list[_i].path, fs.constants.F_OK, function (err) { + fs.access(list[_i].path, fs.constants.F_OK, (err) => { if (err) { getErrorFileName(list, callback, _i + 1); } else { @@ -410,7 +409,7 @@ function requestHandler(req, res) { } }; - getErrorFileName(config.errorPages, function (errorFile) { + getErrorFileName(config.errorPages, (errorFile) => { // Generate error stack if not provided if (Object.prototype.toString.call(stack) === "[object Error]") stack = generateErrorStack(stack); @@ -442,7 +441,7 @@ function requestHandler(req, res) { cheaders["Allow"] = "GET, POST, HEAD, OPTIONS"; // Read the error file and replace placeholders with error information - fs.readFile(errorFile, function (err, data) { + fs.readFile(errorFile, (err, data) => { try { if (err) throw err; res.writeHead(errorCode, http.STATUS_CODES[errorCode], cheaders); diff --git a/src/handlers/serverErrorHandler.js b/src/handlers/serverErrorHandler.js index 4ecb8d2..c71ecbc 100644 --- a/src/handlers/serverErrorHandler.js +++ b/src/handlers/serverErrorHandler.js @@ -38,7 +38,7 @@ function serverErrorHandler(err, isRedirect, server, start) { } catch (err) { // Probably main process exited } - setTimeout(function () { + setTimeout(() => { const errno = os.constants.errno[err.code]; process.exit(errno !== undefined ? errno : 1); }, 50); diff --git a/src/index.js b/src/index.js index 41b8452..b834036 100644 --- a/src/index.js +++ b/src/index.js @@ -134,16 +134,14 @@ for ( args[i] == "/h" || args[i] == "/?" ) { - console.log(name + " usage:"); + console.log(`${name} usage:`); console.log( "node svr.js [-h] [--help] [-?] [/h] [/?] [--secure] [--reset] [--clean] [--disable-mods] [--single-threaded] [-v] [--version]", ); console.log("-h -? /h /? --help -- Displays help"); console.log("--clean -- Cleans up files created by " + name); console.log( - "--reset -- Resets " + - name + - " to default settings (WARNING: DANGEROUS)", + `--reset -- Resets ${name} to default settings (WARNING: DANGEROUS)`, ); console.log("--secure -- Runs HTTPS server"); console.log("--disable-mods -- Disables mods (safe mode)"); @@ -180,17 +178,15 @@ for ( } else if (args[i] == "--single-threaded") { process.singleThreaded = true; } else { - console.log("Unrecognized argument: " + args[i]); - console.log(name + " usage:"); + console.log(`Unrecognized argument: ${args[i]}`); + console.log(`${name} usage:`); console.log( "node svr.js [-h] [--help] [-?] [/h] [/?] [--secure] [--reset] [--clean] [--disable-mods] [--single-threaded] [-v] [--version]", ); console.log("-h -? /h /? --help -- Displays help"); console.log("--clean -- Cleans up files created by " + name); console.log( - "--reset -- Resets " + - name + - " to default settings (WARNING: DANGEROUS)", + `--reset -- Resets ${name} to default settings (WARNING: DANGEROUS)`, ); console.log("--secure -- Runs HTTPS server"); console.log("--disable-mods -- Disables mods (safe mode)"); @@ -422,7 +418,7 @@ try { } catch (err) { ifaceEx = err; } -var ips = []; +let ips = []; const brdIPs = ["255.255.255.255", "127.255.255.255", "0.255.255.255"]; const netIPs = ["127.0.0.0"]; @@ -461,7 +457,7 @@ if (ips.length == 0) { } // Server IP address -var host = ips[ips.length - 1]; +let host = ips[ips.length - 1]; if (!host) host = "[offline]"; // Public IP address-related @@ -821,7 +817,7 @@ if (!disableMods) { crypto.__disabled__ === undefined ? "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 = "\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 @@ -1132,7 +1128,7 @@ if (process.serverConfig.secure) { key: sniCredentialsSingle.key, }); try { - var snMatches = sniCredentialsSingle.name.match( + let snMatches = sniCredentialsSingle.name.match( /^([^:[]*|\[[^]]*\]?)((?::.*)?)$/, ); if (!snMatches[1][0].match(/^\.+$/)) @@ -1414,7 +1410,7 @@ function SVRJSFork() { "Starting next thread, because previous one hung up/crashed...", ); // Fork new worker - var newWorker = {}; + let newWorker = {}; try { if ( !threadLimitWarned && @@ -1518,7 +1514,7 @@ function getWorkerCountToFork() { } function forkWorkers(workersToFork, callback) { - for (var i = 0; i < workersToFork; i++) { + for (let i = 0; i < workersToFork; i++) { if (i == 0) { SVRJSFork(); } else { @@ -1582,9 +1578,9 @@ function msgListener(message) { // Save configuration file function saveConfig() { - for (var i = 0; i < 3; i++) { + for (let i = 0; i < 3; i++) { try { - var configJSONobj = {}; + let configJSONobj = {}; if (fs.existsSync(process.dirname + "/config.json")) configJSONobj = JSON.parse( fs.readFileSync(process.dirname + "/config.json").toString(), @@ -1674,12 +1670,14 @@ function saveConfig() { if (configJSONobj.optOutOfStatisticsServer === undefined) configJSONobj.optOutOfStatisticsServer = false; - var configString = JSON.stringify(configJSONobj, null, 2) + "\n"; - fs.writeFileSync(__dirname + "/config.json", configString); + fs.writeFileSync( + process.dirname + "/config.json", + JSON.stringify(configJSONobj, null, 2) + "\n", + ); break; } catch (err) { if (i >= 2) throw err; - var now = Date.now(); + const now = Date.now(); 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]))/, ) ) && - process.serverConfig.users.some((entry) => { - return entry.pbkdf2; - }) + process.serverConfig.users.some((entry) => entry.pbkdf2) ) 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.", @@ -1967,8 +1963,8 @@ function start(init) { }, 300000); } else if (cluster.isPrimary) { setInterval(() => { - var allWorkers = Object.keys(cluster.workers); - var goodWorkers = []; + let allWorkers = Object.keys(cluster.workers); + let goodWorkers = []; const checkWorker = (callback, _id) => { if (typeof _id === "undefined") _id = 0; @@ -2058,8 +2054,8 @@ function start(init) { commands[line.split(" ")[0]] !== undefined && commands[line.split(" ")[0]] !== null ) { - var argss = line.split(" "); - var command = argss.shift(); + let argss = line.split(" "); + const command = argss.shift(); commands[command](argss, (msg) => process.send(msg)); process.send("\x12END"); } else { @@ -2087,15 +2083,15 @@ function start(init) { const command = argss.shift(); if (line != "") { if (cluster.isPrimary !== undefined) { - var allWorkers = Object.keys(cluster.workers); + let allWorkers = Object.keys(cluster.workers); if (command == "block") commands.block(argss, serverconsole.climessage); if (command == "unblock") commands.unblock(argss, serverconsole.climessage); if (command == "restart") { - var stopError = false; + let stopError = false; exiting = true; - for (var i = 0; i < allWorkers.length; i++) { + for (let i = 0; i < allWorkers.length; i++) { try { if (cluster.workers[allWorkers[i]]) { cluster.workers[allWorkers[i]].kill(); @@ -2150,9 +2146,7 @@ function start(init) { commands[command](argss, serverconsole.climessage); // eslint-disable-next-line no-unused-vars } catch (err) { - serverconsole.climessage( - 'Unrecognized command "' + command + '".', - ); + serverconsole.climessage(`Unrecognized command "${command}".`); } } } @@ -2224,7 +2218,7 @@ function start(init) { !process.serverConfig.secure ) { // It doesn't support through Unix sockets or Windows named pipes - var address = ( + let address = ( typeof process.serverConfig.port == "number" && listenAddress ? listenAddress : "localhost" @@ -2232,7 +2226,7 @@ function start(init) { if (address.indexOf(":") > -1) { address = "[" + address + "]"; } - var connection = http2.connect( + const connection = http2.connect( "http://" + address + ":" + diff --git a/src/middleware/blocklist.js b/src/middleware/blocklist.js index 020166f..fa7d294 100644 --- a/src/middleware/blocklist.js +++ b/src/middleware/blocklist.js @@ -23,14 +23,14 @@ module.exports.commands = { if (ip == undefined || JSON.stringify(ip) == "[]") { if (!cluster.isPrimary === false) log("Cannot block non-existent IP."); } else { - for (var i = 0; i < ip.length; i++) { - if (ip[i] != "localhost" && ip[i].indexOf(":") == -1) { - ip[i] = "::ffff:" + ip[i]; + ip.forEach((ipAddress) => { + if (ipAddress !== "localhost" && ipAddress.indexOf(":") == -1) { + ipAddress = "::ffff:" + ipAddress; } - if (!blocklist.check(ip[i])) { - blocklist.add(ip[i]); + if (!blocklist.check(ipAddress)) { + blocklist.add(ipAddress); } - } + }); process.serverConfig.blacklist = blocklist.raw; if (!cluster.isPrimary === false) log("IPs successfully blocked."); passCommand(ip, log); @@ -40,12 +40,12 @@ module.exports.commands = { if (ip == undefined || JSON.stringify(ip) == "[]") { if (!cluster.isPrimary === false) log("Cannot unblock non-existent IP."); } else { - for (var i = 0; i < ip.length; i++) { - if (ip[i].indexOf(":") == -1) { - ip[i] = "::ffff:" + ip[i]; + ip.forEach((ipAddress) => { + if (ipAddress !== "localhost" && ipAddress.indexOf(":") == -1) { + ipAddress = "::ffff:" + ipAddress; } - blocklist.remove(ip[i]); - } + blocklist.remove(ipAddress); + }); process.serverConfig.blacklist = blocklist.raw; if (!cluster.isPrimary === false) log("IPs successfully unblocked."); passCommand(ip, log); diff --git a/src/middleware/nonStandardCodesAndHttpAuthentication.js b/src/middleware/nonStandardCodesAndHttpAuthentication.js index 4f5487a..7c419e7 100644 --- a/src/middleware/nonStandardCodesAndHttpAuthentication.js +++ b/src/middleware/nonStandardCodesAndHttpAuthentication.js @@ -29,7 +29,7 @@ let passwordHashCacheIntervalId = -1; // Non-standard code object let nonStandardCodes = []; process.serverConfig.nonStandardCodes.forEach((nonStandardCodeRaw) => { - var newObject = {}; + let newObject = {}; Object.keys(nonStandardCodeRaw).forEach((nsKey) => { if (nsKey != "users") { newObject[nsKey] = nonStandardCodeRaw[nsKey]; @@ -42,12 +42,12 @@ process.serverConfig.nonStandardCodes.forEach((nonStandardCodeRaw) => { if (!cluster.isPrimary) { passwordHashCacheIntervalId = setInterval(() => { - pbkdf2Cache = pbkdf2Cache.filter((entry) => { - return entry.addDate > new Date() - 3600000; - }); - scryptCache = scryptCache.filter((entry) => { - return entry.addDate > new Date() - 3600000; - }); + pbkdf2Cache = pbkdf2Cache.filter( + (entry) => entry.addDate > new Date() - 3600000, + ); + scryptCache = scryptCache.filter( + (entry) => entry.addDate > new Date() - 3600000, + ); }, 1800000); } @@ -77,7 +77,7 @@ module.exports = (req, res, logFacilities, config, next) => { ); if (nonStandardCodes[i].regex) { // Regex match - var createdRegex = createRegex(nonStandardCodes[i].regex, true); + const createdRegex = createRegex(nonStandardCodes[i].regex, true); isMatch = req.url.match(createdRegex) || hrefWithoutDuplicateSlashes.match(createdRegex); @@ -192,11 +192,10 @@ module.exports = (req, res, logFacilities, config, next) => { ); return; } else { - cacheEntry = scryptCache.find((entry) => { - return ( - entry.password == hashedPassword && entry.salt == list[_i].salt - ); - }); + cacheEntry = scryptCache.find( + (entry) => + entry.password == hashedPassword && entry.salt == list[_i].salt, + ); if (cacheEntry) { cb(cacheEntry.hash); } else { @@ -226,11 +225,10 @@ module.exports = (req, res, logFacilities, config, next) => { ); return; } else { - cacheEntry = pbkdf2Cache.find((entry) => { - return ( - entry.password == hashedPassword && entry.salt == list[_i].salt - ); - }); + cacheEntry = pbkdf2Cache.find( + (entry) => + entry.password == hashedPassword && entry.salt == list[_i].salt, + ); if (cacheEntry) { cb(cacheEntry.hash); } else { diff --git a/src/middleware/redirects.js b/src/middleware/redirects.js index d594f73..5e02caf 100644 --- a/src/middleware/redirects.js +++ b/src/middleware/redirects.js @@ -9,7 +9,7 @@ module.exports = (req, res, logFacilities, config, next) => { !config.disableNonEncryptedServer && !config.disableToHTTPSRedirect ) { - var hostx = req.headers.host; + const hostx = req.headers.host; if (hostx === undefined) { logFacilities.errmessage("Host header is missing."); res.error(400); @@ -22,7 +22,7 @@ module.exports = (req, res, logFacilities, config, next) => { return; } - var isPublicServer = !( + const isPublicServer = !( req.socket.realRemoteAddress ? req.socket.realRemoteAddress : 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, ); - var destinationPort = 0; + let destinationPort = 0; - var parsedHostx = hostx.match(/(\[[^\]]*\]|[^:]*)(?::([0-9]+))?/); - var hostname = parsedHostx[1]; - var hostPort = parsedHostx[2] ? parseInt(parsedHostx[2]) : 80; + const parsedHostx = hostx.match(/(\[[^\]]*\]|[^:]*)(?::([0-9]+))?/); + let hostname = parsedHostx[1]; + let hostPort = parsedHostx[2] ? parseInt(parsedHostx[2]) : 80; if (isNaN(hostPort)) hostPort = 80; if ( diff --git a/src/middleware/responseHeaders.js b/src/middleware/responseHeaders.js index 393c42a..db746eb 100644 --- a/src/middleware/responseHeaders.js +++ b/src/middleware/responseHeaders.js @@ -1,6 +1,6 @@ module.exports = (req, res, logFacilities, config, next) => { if (!req.isProxy) { - var hkh = config.getCustomHeaders(); + const hkh = config.getCustomHeaders(); Object.keys(hkh).forEach((hkS) => { try { res.setHeader(hkS, hkh[hkS]); diff --git a/src/middleware/rewriteURL.js b/src/middleware/rewriteURL.js index c4cce0f..a11ab2d 100644 --- a/src/middleware/rewriteURL.js +++ b/src/middleware/rewriteURL.js @@ -30,7 +30,7 @@ module.exports = (req, res, logFacilities, config, next) => { fs.stat( "." + decodeURIComponent(req.parsedURL.pathname), (err, stats) => { - var _fileState = 3; + let _fileState = 3; if (err) { _fileState = 3; } else if (stats.isDirectory()) { @@ -126,7 +126,7 @@ module.exports = (req, res, logFacilities, config, next) => { logFacilities.errmessage("Content blocked."); return; } else if (sHref != req.parsedURL.pathname) { - var rewrittenAgainURL = + const rewrittenAgainURL = sHref + (req.parsedURL.search ? req.parsedURL.search : "") + (req.parsedURL.hash ? req.parsedURL.hash : ""); diff --git a/src/middleware/staticFileServingAndDirectoryListings.js b/src/middleware/staticFileServingAndDirectoryListings.js index bea1530..8080307 100644 --- a/src/middleware/staticFileServingAndDirectoryListings.js +++ b/src/middleware/staticFileServingAndDirectoryListings.js @@ -34,7 +34,7 @@ module.exports = (req, res, logFacilities, config, next) => { let levelDownCount = 0; // 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 (".." === pathComponents[i]) { levelUpCount--; @@ -313,7 +313,7 @@ module.exports = (req, res, logFacilities, config, next) => { // Helper function to check if compression is allowed for the file const canCompress = (path, list) => { 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)) { canCompress = false; break; @@ -437,7 +437,7 @@ module.exports = (req, res, logFacilities, config, next) => { }) .on("open", () => { try { - var resStream = {}; + let resStream = {}; if (useBrotli && isCompressable) { resStream = zlib.createBrotliCompress(); 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 getStatsForAllFiles(list, readFrom, (filelist) => { let directoryListingRows = []; - for (var i = 0; i < filelist.length; i++) { + for (let i = 0; i < filelist.length; i++) { if (filelist[i].name[0] !== ".") { const estats = filelist[i].stats; const ename = filelist[i].name; @@ -773,7 +773,7 @@ module.exports = (req, res, logFacilities, config, next) => { .replace(/>/g, ">")}${ estats.isDirectory() ? "-" - : sizify(estats.size.toString()) + : sizify(estats.size) }${estats.mtime.toDateString()}\r\n`; // Determine the file type and set the appropriate image and alt text diff --git a/src/utils/clusterBunShim.js b/src/utils/clusterBunShim.js index 642d94e..5c7ced8 100644 --- a/src/utils/clusterBunShim.js +++ b/src/utils/clusterBunShim.js @@ -26,9 +26,7 @@ if (!process.singleThreaded) { cluster.worker = { id: parseInt(process.env.NODE_UNIQUE_ID), process: process, - isDead: () => { - return false; - }, + isDead: () => false, send: (message, ...params) => { process.send(message, ...params); }, @@ -98,9 +96,7 @@ if (!process.singleThreaded) { }); newWorker.process = newWorker; - newWorker.isDead = () => { - return newWorker.exitCode !== null || newWorker.killed; - }; + newWorker.isDead = () => newWorker.exitCode !== null || newWorker.killed; newWorker.id = newEnvironment.NODE_UNIQUE_ID; function checkSendImplementation(worker) { diff --git a/src/utils/forbiddenPaths.js b/src/utils/forbiddenPaths.js index d2d4007..e836e25 100644 --- a/src/utils/forbiddenPaths.js +++ b/src/utils/forbiddenPaths.js @@ -54,16 +54,23 @@ function isIndexOfForbiddenPath(decodedHref, match) { if (typeof forbiddenPath === "string") { const forbiddenPathLower = isWin32 ? forbiddenPath.toLowerCase() : null; return isWin32 - ? decodedHrefLower.indexOf(forbiddenPathLower) == 0 - : decodedHref.indexOf(forbiddenPath) == 0; + ? decodedHrefLower === forbiddenPathLower || + decodedHrefLower.indexOf(forbiddenPathLower + "/") == 0 + : decodedHref === forbiddenPath || + decodedHref.indexOf(forbiddenPath + "/") == 0; } if (typeof forbiddenPath === "object") { return isWin32 ? 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; diff --git a/src/utils/generateErrorStack.js b/src/utils/generateErrorStack.js index 8451a11..fe58772 100644 --- a/src/utils/generateErrorStack.js +++ b/src/utils/generateErrorStack.js @@ -1,19 +1,19 @@ // 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") : []; + 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 ( - errorStack.some((errorStackLine) => { - return errorStackLine.indexOf(errorObject.name) == 0; - }) + errorStack.some( + (errorStackLine) => errorStackLine.indexOf(errorObject.name) == 0, + ) ) { return errorObject.stack; } // Create a new error stack with the error name and code (if available). - var newErrorStack = [ + let newErrorStack = [ errorObject.name + (errorObject.code ? ": " + errorObject.code : "") + (errorObject.message == "" ? "" : ": " + errorObject.message), @@ -23,12 +23,12 @@ function generateErrorStack(errorObject) { errorStack.forEach((errorStackLine) => { if (errorStackLine != "") { // Split the line into function and location parts (if available). - var errorFrame = errorStackLine.split("@"); - var location = ""; + let errorFrame = errorStackLine.split("@"); + let location = ""; if (errorFrame.length > 1 && errorFrame[0] == "global code") errorFrame.shift(); 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. newErrorStack.push( diff --git a/src/utils/getOS.js b/src/utils/getOS.js index 7f181eb..b64c7a5 100644 --- a/src/utils/getOS.js +++ b/src/utils/getOS.js @@ -1,12 +1,12 @@ const os = require("os"); function getOS() { - var osType = os.type(); - var platform = os.platform(); + const osType = os.type(); + const platform = os.platform(); if (platform == "android") { return "Android"; } else if (osType == "Windows_NT" || osType == "WindowsNT") { - var arch = os.arch(); + const arch = os.arch(); if (arch == "ia32") { return "Win32"; } else if (arch == "x64") { diff --git a/src/utils/ipBlockList.js b/src/utils/ipBlockList.js index 2d4fda7..d5f7d00 100644 --- a/src/utils/ipBlockList.js +++ b/src/utils/ipBlockList.js @@ -2,7 +2,7 @@ function ipBlockList(rawBlockList) { // Initialize the instance with empty arrays if (rawBlockList === undefined) rawBlockList = []; - var instance = { + const instance = { raw: [], rawtoPreparedMap: [], prepared: [], @@ -10,9 +10,8 @@ function ipBlockList(rawBlockList) { }; // Function to normalize IPv4 address (remove leading zeros) - const normalizeIPv4Address = (address) => { - return address.replace(/(^|\.)(?:0(?!\.|$))+/g, "$1"); - }; + const normalizeIPv4Address = (address) => + address.replace(/(^|\.)(?:0(?!\.|$))+/g, "$1"); // Function to expand IPv6 address to full format const expandIPv6Address = (address) => { @@ -236,9 +235,7 @@ function ipBlockList(rawBlockList) { ? checkIfIPv4CIDRMatches : checkIfIPv6CIDRMatches; - return instance.cidrs.some((iCidr) => { - return checkMethod(ipParsedObject, iCidr); - }); + return instance.cidrs.some((iCidr) => checkMethod(ipParsedObject, iCidr)); }; // Add initial raw block list values to the instance diff --git a/src/utils/ipMatch.js b/src/utils/ipMatch.js index 1370ff3..5ef0e6f 100644 --- a/src/utils/ipMatch.js +++ b/src/utils/ipMatch.js @@ -4,9 +4,8 @@ function ipMatch(IP1, IP2) { if (!IP2) return false; // Function to normalize IPv4 address (remove leading zeros) - const normalizeIPv4Address = (address) => { - return address.replace(/(^|\.)(?:0(?!\.|$))+/g, "$1"); - }; + const normalizeIPv4Address = (address) => + address.replace(/(^|\.)(?:0(?!\.|$))+/g, "$1"); // Function to expand IPv6 address to full format const expandIPv6Address = (address) => { diff --git a/src/utils/ipSubnetUtils.js b/src/utils/ipSubnetUtils.js index e2aeeeb..8a6cfcb 100644 --- a/src/utils/ipSubnetUtils.js +++ b/src/utils/ipSubnetUtils.js @@ -14,7 +14,7 @@ function calculateBroadcastIPv4FromCidr(ipWithCidr) { .split(".") .map((num, index) => { // 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 ( (parseInt(num) & ((Math.pow(2, power) - 1) << (8 - power))) | (Math.pow(2, 8 - power) - 1) @@ -39,7 +39,7 @@ function calculateNetworkIPv4FromCidr(ipWithCidr) { .split(".") .map((num, index) => { // 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 ( parseInt(num) & ((Math.pow(2, power) - 1) << (8 - power)) diff --git a/src/utils/serverconsole.js b/src/utils/serverconsole.js index 096ba0e..60c613e 100644 --- a/src/utils/serverconsole.js +++ b/src/utils/serverconsole.js @@ -78,7 +78,7 @@ function LOG(s) { } // Server console function -var serverconsole = { +const serverconsole = { climessage: (msg, reqId) => { if (msg.indexOf("\n") != -1) { msg.split("\n").forEach((nmsg) => { diff --git a/src/utils/sha256.js b/src/utils/sha256.js index 58043de..4ea6abe 100644 --- a/src/utils/sha256.js +++ b/src/utils/sha256.js @@ -22,37 +22,14 @@ function sha256(s) { return (msw << 16) | (lsw & 0xffff); }; - const S = (X, n) => { - return (X >>> n) | (X << (32 - n)); - }; - - const R = (X, n) => { - return X >>> n; - }; - - 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); - }; + const S = (X, n) => (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 Sigma0256 = (x) => S(x, 2) ^ S(x, 13) ^ S(x, 22); + 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); function coreSha256(m, l) { const K = new Array( diff --git a/src/utils/sizify.js b/src/utils/sizify.js index 688ed54..b8780f1 100644 --- a/src/utils/sizify.js +++ b/src/utils/sizify.js @@ -1,5 +1,5 @@ function sizify(bytes, addI) { - if (bytes === 0) return "0"; + if (bytes == 0) return "0"; if (bytes < 0) bytes = -bytes; const prefixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"]; diff --git a/svrjs.json b/svrjs.json index 139dafd..1ed2c4a 100644 --- a/svrjs.json +++ b/svrjs.json @@ -1,11 +1,11 @@ { - "version": "4.0.0-beta2", + "version": "4.0.0-beta3", "name": "SVR.JS", "documentationURL": "https://svrjs.org/docs/tentative", "statisticsServerCollectEndpoint": "https://statistics.svrjs.org/collect.svr", "changes": [ - "Fixed the bug with \"ext\" variable for .tar.gz mods.", - "Fixed the regular expression in the URL parser.", - "Optimized many functions" + "Fixed the bug related to forbidden path checking.", + "Fixed \"NaN\" file sizes in directory listings.", + "SVR.JS zip archives now include empty directories." ] } diff --git a/tests/utils/forbiddenPaths.test.js b/tests/utils/forbiddenPaths.test.js index 3261428..1badecb 100644 --- a/tests/utils/forbiddenPaths.test.js +++ b/tests/utils/forbiddenPaths.test.js @@ -117,6 +117,13 @@ describe("Forbidden paths handling", () => { expect( isIndexOfForbiddenPath("/notforbidden/", "serverSideScriptDirectories"), ).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", () => { diff --git a/tests/utils/urlSanitizer.test.js b/tests/utils/urlSanitizer.test.js index 79f5bd6..1206517 100644 --- a/tests/utils/urlSanitizer.test.js +++ b/tests/utils/urlSanitizer.test.js @@ -51,4 +51,52 @@ describe("URL sanitizer", () => { test('should return "/" for empty sanitized resource', () => { expect(sanitizeURL("/../..")).toBe("/"); }); + + test("should encode special characters", () => { + expect(sanitizeURL("/test")).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("/_"); + }); });