diff --git a/src/index.js b/src/index.js index aeac172..15c9d7d 100644 --- a/src/index.js +++ b/src/index.js @@ -13,6 +13,7 @@ try { } process.dirname = __dirname; +process.filename = __filename; // TODO: after implementing clustering in new SVR.JS //process.singleThreaded = false; diff --git a/src/middleware/checkForbiddenPaths.js b/src/middleware/checkForbiddenPaths.js index 97d07ee..0923a86 100644 --- a/src/middleware/checkForbiddenPaths.js +++ b/src/middleware/checkForbiddenPaths.js @@ -1,81 +1,11 @@ -const os = require("os"); -const path = require("path"); - -// Function to get URL path for use in forbidden path adding. -function getInitializePath(to) { - const cwd = process.cwd(); - if (os.platform() == "win32") { - to = to.replace(/\//g, "\\"); - if (to[0] == "\\") to = cwd.split("\\")[0] + to; - } - const absoluteTo = path.isAbsolute(to) - ? to - : process.dirname + (os.platform() == "win32" ? "\\" : "/") + to; - if (os.platform() == "win32" && cwd[0] != absoluteTo[0]) return ""; - const relative = path.relative(cwd, absoluteTo); - if (os.platform() == "win32") { - return "/" + relative.replace(/\\/g, "/"); - } else { - return "/" + relative; - } -} - -// Function to check if URL path name is a forbidden path. -function isForbiddenPath(decodedHref, match) { - const forbiddenPath = forbiddenPaths[match]; - if (!forbiddenPath) return false; - if (typeof forbiddenPath === "string") { - return ( - decodedHref === forbiddenPath || - (os.platform() === "win32" && - decodedHref.toLowerCase() === forbiddenPath.toLowerCase()) - ); - } - if (typeof forbiddenPath === "object") { - return forbiddenPath.some(function (forbiddenPathSingle) { - return ( - decodedHref === forbiddenPathSingle || - (os.platform() === "win32" && - decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase()) - ); - }); - } - return false; -} - -// Function to check if URL path name is index of one of defined forbidden paths. -function isIndexOfForbiddenPath(decodedHref, match) { - const forbiddenPath = forbiddenPaths[match]; - if (!forbiddenPath) return false; - if (typeof forbiddenPath === "string") { - return ( - decodedHref === forbiddenPath || - decodedHref.indexOf(forbiddenPath + "/") === 0 || - (os.platform() === "win32" && - (decodedHref.toLowerCase() === forbiddenPath.toLowerCase() || - decodedHref - .toLowerCase() - .indexOf(forbiddenPath.toLowerCase() + "/") === 0)) - ); - } - if (typeof forbiddenPath === "object") { - return forbiddenPath.some(function (forbiddenPathSingle) { - return ( - decodedHref === forbiddenPathSingle || - decodedHref.indexOf(forbiddenPathSingle + "/") === 0 || - (os.platform() === "win32" && - (decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase() || - decodedHref - .toLowerCase() - .indexOf(forbiddenPathSingle.toLowerCase() + "/") === 0)) - ); - }); - } - return false; -} - -// Set up forbidden paths -var forbiddenPaths = {}; +// WARNING: This middleware is optimized for production SVR.JS, and may not work correctly for development SVR.JS. +// Use "npm run dev" to test SVR.JS web server itself. +const { + getInitializePath, + isForbiddenPath, + isIndexOfForbiddenPath, + forbiddenPaths, +} = require("../utils/forbiddenPaths.js"); forbiddenPaths.config = getInitializePath("./config.json"); forbiddenPaths.certificates = []; @@ -96,8 +26,8 @@ if (process.serverConfig.secure) { forbiddenPaths.svrjs = getInitializePath( "./" + (process.dirname[process.dirname.length - 1] != "/" - ? __filename.replace(process.dirname + "/", "") - : __filename.replace(process.dirname, "")), + ? process.filename.replace(process.dirname + "/", "") + : process.filename.replace(process.dirname, "")), ); forbiddenPaths.serverSideScripts = []; if (process.serverConfig.useWebRootServerSideScript) { diff --git a/src/utils/forbiddenPaths.js b/src/utils/forbiddenPaths.js new file mode 100644 index 0000000..be76508 --- /dev/null +++ b/src/utils/forbiddenPaths.js @@ -0,0 +1,87 @@ +const os = require("os"); +const path = require("path"); + +// Function to get URL path for use in forbidden path adding. +function getInitializePath(to) { + const isWin32 = os.platform() == "win32"; + const pathModS = isWin32 ? path.win32 : path.posix; // pathModS needed just for the test suite + const cwd = process.cwd(); + if (isWin32) { + to = to.replace(/\//g, "\\"); + if (to[0] == "\\") to = cwd.split("\\")[0] + to; + } + const absoluteTo = pathModS.isAbsolute(to) + ? to + : process.dirname + (isWin32 ? "\\" : "/") + to; + if (isWin32 && cwd[0] != absoluteTo[0]) return ""; + const relative = pathModS.relative(cwd, absoluteTo); + if (isWin32) { + return "/" + relative.replace(/\\/g, "/"); + } else { + return "/" + relative; + } +} + +// Function to check if URL path name is a forbidden path. +function isForbiddenPath(decodedHref, match) { + const forbiddenPath = forbiddenPaths[match]; + if (!forbiddenPath) return false; + if (typeof forbiddenPath === "string") { + return ( + decodedHref === forbiddenPath || + (os.platform() === "win32" && + decodedHref.toLowerCase() === forbiddenPath.toLowerCase()) + ); + } + if (typeof forbiddenPath === "object") { + return forbiddenPath.some(function (forbiddenPathSingle) { + return ( + decodedHref === forbiddenPathSingle || + (os.platform() === "win32" && + decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase()) + ); + }); + } + return false; +} + +// Function to check if URL path name is index of one of defined forbidden paths. +function isIndexOfForbiddenPath(decodedHref, match) { + const forbiddenPath = forbiddenPaths[match]; + if (!forbiddenPath) return false; + if (typeof forbiddenPath === "string") { + return ( + decodedHref === forbiddenPath || + decodedHref.indexOf(forbiddenPath + "/") === 0 || + (os.platform() === "win32" && + (decodedHref.toLowerCase() === forbiddenPath.toLowerCase() || + decodedHref + .toLowerCase() + .indexOf(forbiddenPath.toLowerCase() + "/") === 0)) + ); + } + if (typeof forbiddenPath === "object") { + return forbiddenPath.some(function (forbiddenPathSingle) { + return ( + decodedHref === forbiddenPathSingle || + decodedHref.indexOf(forbiddenPathSingle + "/") === 0 || + (os.platform() === "win32" && + (decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase() || + decodedHref + .toLowerCase() + .indexOf(forbiddenPathSingle.toLowerCase() + "/") === 0)) + ); + }); + } + return false; +} + +// Set up forbidden paths +let forbiddenPaths = {}; + +module.exports = { + getInitializePath: getInitializePath, + isForbiddenPath: isForbiddenPath, + isIndexOfForbiddenPath: isIndexOfForbiddenPath, + forbiddenPaths: forbiddenPaths, +}; diff --git a/tests/utils/forbiddenPaths.test.js b/tests/utils/forbiddenPaths.test.js new file mode 100644 index 0000000..1ae2775 --- /dev/null +++ b/tests/utils/forbiddenPaths.test.js @@ -0,0 +1,147 @@ +const { + getInitializePath, + isForbiddenPath, + isIndexOfForbiddenPath, + forbiddenPaths, +} = require("../../src/utils/forbiddenPaths"); +const os = require("os"); +const path = require("path"); + +jest.mock("os", () => ({ + platform: jest.fn(), +})); + +describe("Forbidden paths handling", () => { + beforeEach(() => { + os.platform.mockReset(); + forbiddenPaths.config = getInitializePath("./config.json"); + forbiddenPaths.serverSideScriptDirectories = []; + forbiddenPaths.serverSideScriptDirectories.push( + getInitializePath("./node_modules"), + ); + forbiddenPaths.serverSideScriptDirectories.push( + getInitializePath("./mods"), + ); + process.cwd = () => "/usr/lib/mocksvrjs"; + process.dirname = "/usr/lib/mocksvrjs"; + process.filename = "/usr/lib/mocksvrjs/svr.js"; + }); + + describe("getInitializePath", () => { + test("should return the correct path on Unix", () => { + os.platform.mockReturnValue("linux"); + expect(getInitializePath("./config.json")).toBe("/config.json"); + }); + + test("should return the correct path on Windows", () => { + process.cwd = () => "C:\\mocksvrjs"; + process.dirname = "C:\\mocksvrjs"; + process.filename = "C:\\mocksvrjs\\svr.js"; + os.platform.mockReturnValue("win32"); + expect(getInitializePath("./config.json")).toBe("/config.json"); + }); + + test("should handle absolute paths on Unix", () => { + os.platform.mockReturnValue("linux"); + expect(getInitializePath("/absolute/path")).toBe( + "/../../../absolute/path", + ); + }); + + test("should handle absolute paths on Windows", () => { + process.cwd = () => "C:\\mocksvrjs"; + process.dirname = "C:\\mocksvrjs"; + process.filename = "C:\\mocksvrjs\\svr.js"; + os.platform.mockReturnValue("win32"); + expect(getInitializePath("C:\\absolute\\path")).toBe("/../absolute/path"); + }); + + test("should handle relative paths on Unix", () => { + os.platform.mockReturnValue("linux"); + expect(getInitializePath("./relative/path")).toBe("/relative/path"); + }); + + test("should handle relative paths on Windows", () => { + process.cwd = () => "C:\\mocksvrjs"; + process.dirname = "C:\\mocksvrjs"; + process.filename = "C:\\mocksvrjs\\svr.js"; + os.platform.mockReturnValue("win32"); + expect(getInitializePath("./relative\\path")).toBe("/relative/path"); + }); + }); + + describe("isForbiddenPath", () => { + test("should return true if the path is forbidden", () => { + os.platform.mockReturnValue("linux"); + expect(isForbiddenPath("/config.json", "config")).toBe(true); + }); + + test("should return false if the path is not forbidden", () => { + os.platform.mockReturnValue("linux"); + expect(isForbiddenPath("/notconfig.json", "config")).toBe(false); + }); + + test("should handle case insensitivity on Windows", () => { + process.cwd = () => "C:\\mocksvrjs"; + process.dirname = "C:\\mocksvrjs"; + process.filename = "C:\\mocksvrjs\\svr.js"; + os.platform.mockReturnValue("win32"); + expect(isForbiddenPath("/CONFIG.JSON", "config")).toBe(true); + }); + + test("should handle array of forbidden paths", () => { + os.platform.mockReturnValue("linux"); + expect( + isForbiddenPath("/node_modules", "serverSideScriptDirectories"), + ).toBe(true); + expect(isForbiddenPath("/mods", "serverSideScriptDirectories")).toBe( + true, + ); + expect( + isForbiddenPath("/notforbidden", "serverSideScriptDirectories"), + ).toBe(false); + }); + }); + + describe("isIndexOfForbiddenPath", () => { + test("should return true if the path is an index of a forbidden path", () => { + os.platform.mockReturnValue("linux"); + expect(isIndexOfForbiddenPath("/config.json", "config")).toBe(true); + expect( + isIndexOfForbiddenPath("/node_modules/", "serverSideScriptDirectories"), + ).toBe(true); + }); + + test("should return false if the path is not an index of a forbidden path", () => { + os.platform.mockReturnValue("linux"); + expect(isIndexOfForbiddenPath("/notconfig.json", "config")).toBe(false); + expect( + isIndexOfForbiddenPath("/notforbidden/", "serverSideScriptDirectories"), + ).toBe(false); + }); + + test("should handle case insensitivity on Windows", () => { + process.cwd = () => "C:\\mocksvrjs"; + process.dirname = "C:\\mocksvrjs"; + process.filename = "C:\\mocksvrjs\\svr.js"; + os.platform.mockReturnValue("win32"); + expect(isIndexOfForbiddenPath("/CONFIG.JSON", "config")).toBe(true); + expect( + isIndexOfForbiddenPath("/NODE_MODULES/", "serverSideScriptDirectories"), + ).toBe(true); + }); + + test("should handle array of forbidden paths", () => { + os.platform.mockReturnValue("linux"); + expect( + isIndexOfForbiddenPath("/node_modules/", "serverSideScriptDirectories"), + ).toBe(true); + expect( + isIndexOfForbiddenPath("/mods/", "serverSideScriptDirectories"), + ).toBe(true); + expect( + isIndexOfForbiddenPath("/notforbidden/", "serverSideScriptDirectories"), + ).toBe(false); + }); + }); +});