-- Test suite for path validation functions -- Tests sanitizePath, isPathSafe, validatePath, getFileExtension, hasAllowedExtension package.path = package.path .. ";./?.lua;./modules/?.lua" -- Load love stub before anything else require("testing.loveStub") local luaunit = require("testing.luaunit") local utils = require("modules.utils") -- Test suite for sanitizePath TestSanitizePath = {} function TestSanitizePath:testSanitizePath_NilInput() local result = utils.sanitizePath(nil) luaunit.assertEquals(result, "") end function TestSanitizePath:testSanitizePath_EmptyString() local result = utils.sanitizePath("") luaunit.assertEquals(result, "") end function TestSanitizePath:testSanitizePath_Whitespace() local result = utils.sanitizePath(" /path/to/file ") luaunit.assertEquals(result, "/path/to/file") end function TestSanitizePath:testSanitizePath_Backslashes() local result = utils.sanitizePath("C:\\path\\to\\file") luaunit.assertEquals(result, "C:/path/to/file") end function TestSanitizePath:testSanitizePath_DuplicateSlashes() local result = utils.sanitizePath("/path//to///file") luaunit.assertEquals(result, "/path/to/file") end function TestSanitizePath:testSanitizePath_TrailingSlash() local result = utils.sanitizePath("/path/to/dir/") luaunit.assertEquals(result, "/path/to/dir") -- Root should keep trailing slash result = utils.sanitizePath("/") luaunit.assertEquals(result, "/") end function TestSanitizePath:testSanitizePath_MixedIssues() local result = utils.sanitizePath(" C:\\path\\\\to///file.txt ") luaunit.assertEquals(result, "C:/path/to/file.txt") end -- Test suite for isPathSafe TestIsPathSafe = {} function TestIsPathSafe:testIsPathSafe_EmptyPath() local safe, reason = utils.isPathSafe("") luaunit.assertFalse(safe) luaunit.assertStrContains(reason, "empty") end function TestIsPathSafe:testIsPathSafe_NilPath() local safe, reason = utils.isPathSafe(nil) luaunit.assertFalse(safe) luaunit.assertNotNil(reason) end function TestIsPathSafe:testIsPathSafe_ParentDirectory() local safe, reason = utils.isPathSafe("../etc/passwd") luaunit.assertFalse(safe) luaunit.assertStrContains(reason, "..") end function TestIsPathSafe:testIsPathSafe_MultipleParentDirectories() local safe, reason = utils.isPathSafe("../../../../../../etc/passwd") luaunit.assertFalse(safe) luaunit.assertStrContains(reason, "..") end function TestIsPathSafe:testIsPathSafe_HiddenParentDirectory() local safe, reason = utils.isPathSafe("/path/to/../../../etc/passwd") luaunit.assertFalse(safe) luaunit.assertStrContains(reason, "..") end function TestIsPathSafe:testIsPathSafe_NullBytes() local safe, reason = utils.isPathSafe("/path/to/file\0.txt") luaunit.assertFalse(safe) luaunit.assertStrContains(reason, "null") end function TestIsPathSafe:testIsPathSafe_EncodedTraversal() local safe, reason = utils.isPathSafe("/path/%2e%2e/file") luaunit.assertFalse(safe) luaunit.assertStrContains(reason, "encoded") end function TestIsPathSafe:testIsPathSafe_LegitimatePathNoBaseDir() local safe, reason = utils.isPathSafe("/themes/default.lua") luaunit.assertTrue(safe) luaunit.assertNil(reason) end function TestIsPathSafe:testIsPathSafe_LegitimatePathWithBaseDir() local safe, reason = utils.isPathSafe("/allowed/themes/default.lua", "/allowed") luaunit.assertTrue(safe) luaunit.assertNil(reason) end function TestIsPathSafe:testIsPathSafe_RelativePathWithBaseDir() local safe, reason = utils.isPathSafe("themes/default.lua", "/allowed") luaunit.assertTrue(safe) luaunit.assertNil(reason) end function TestIsPathSafe:testIsPathSafe_OutsideBaseDir() local safe, reason = utils.isPathSafe("/other/themes/default.lua", "/allowed") luaunit.assertFalse(safe) luaunit.assertStrContains(reason, "outside") end -- Test suite for validatePath TestValidatePath = {} function TestValidatePath:testValidatePath_EmptyPath() local valid, err = utils.validatePath("") luaunit.assertFalse(valid) luaunit.assertStrContains(err, "empty") end function TestValidatePath:testValidatePath_NilPath() local valid, err = utils.validatePath(nil) luaunit.assertFalse(valid) luaunit.assertStrContains(err, "empty") end function TestValidatePath:testValidatePath_TooLong() local longPath = string.rep("a", 5000) local valid, err = utils.validatePath(longPath, { maxLength = 100 }) luaunit.assertFalse(valid) luaunit.assertStrContains(err, "maximum length") end function TestValidatePath:testValidatePath_TraversalAttack() local valid, err = utils.validatePath("../../../etc/passwd") luaunit.assertFalse(valid) luaunit.assertNotNil(err) end function TestValidatePath:testValidatePath_AllowedExtension() local valid, err = utils.validatePath("theme.lua", { allowedExtensions = { "lua", "txt" } }) luaunit.assertTrue(valid) luaunit.assertNil(err) end function TestValidatePath:testValidatePath_DisallowedExtension() local valid, err = utils.validatePath("script.exe", { allowedExtensions = { "lua", "txt" } }) luaunit.assertFalse(valid) luaunit.assertStrContains(err, "not allowed") end function TestValidatePath:testValidatePath_NoExtension() local valid, err = utils.validatePath("README", { allowedExtensions = { "lua", "txt" } }) luaunit.assertFalse(valid) luaunit.assertStrContains(err, "no file extension") end function TestValidatePath:testValidatePath_CaseInsensitiveExtension() local valid, err = utils.validatePath("Theme.LUA", { allowedExtensions = { "lua" } }) luaunit.assertTrue(valid) luaunit.assertNil(err) end function TestValidatePath:testValidatePath_WithBaseDir() local valid, err = utils.validatePath("themes/default.lua", { baseDir = "/allowed" }) luaunit.assertTrue(valid) luaunit.assertNil(err) end function TestValidatePath:testValidatePath_OutsideBaseDir() local valid, err = utils.validatePath("/other/theme.lua", { baseDir = "/allowed" }) luaunit.assertFalse(valid) luaunit.assertStrContains(err, "outside") end -- Test suite for getFileExtension TestGetFileExtension = {} function TestGetFileExtension:testGetFileExtension_SimpleExtension() local ext = utils.getFileExtension("file.txt") luaunit.assertEquals(ext, "txt") end function TestGetFileExtension:testGetFileExtension_MultipleDotsInPath() local ext = utils.getFileExtension("/path/to/file.name.txt") luaunit.assertEquals(ext, "txt") end function TestGetFileExtension:testGetFileExtension_NoExtension() local ext = utils.getFileExtension("README") luaunit.assertNil(ext) end function TestGetFileExtension:testGetFileExtension_NilPath() local ext = utils.getFileExtension(nil) luaunit.assertNil(ext) end function TestGetFileExtension:testGetFileExtension_CaseSensitive() local ext = utils.getFileExtension("File.TXT") luaunit.assertEquals(ext, "txt") -- Should be lowercase end function TestGetFileExtension:testGetFileExtension_LongExtension() local ext = utils.getFileExtension("archive.tar.gz") luaunit.assertEquals(ext, "gz") end -- Test suite for hasAllowedExtension TestHasAllowedExtension = {} function TestHasAllowedExtension:testHasAllowedExtension_Allowed() local allowed = utils.hasAllowedExtension("file.txt", { "txt", "lua" }) luaunit.assertTrue(allowed) end function TestHasAllowedExtension:testHasAllowedExtension_NotAllowed() local allowed = utils.hasAllowedExtension("file.exe", { "txt", "lua" }) luaunit.assertFalse(allowed) end function TestHasAllowedExtension:testHasAllowedExtension_CaseInsensitive() local allowed = utils.hasAllowedExtension("File.TXT", { "txt", "lua" }) luaunit.assertTrue(allowed) end function TestHasAllowedExtension:testHasAllowedExtension_NoExtension() local allowed = utils.hasAllowedExtension("README", { "txt", "lua" }) luaunit.assertFalse(allowed) end function TestHasAllowedExtension:testHasAllowedExtension_EmptyArray() local allowed = utils.hasAllowedExtension("file.txt", {}) luaunit.assertFalse(allowed) end -- Test suite for security scenarios TestPathSecurity = {} function TestPathSecurity:testPathSecurity_WindowsTraversal() local safe = utils.isPathSafe("..\\..\\..\\windows\\system32") luaunit.assertFalse(safe) end function TestPathSecurity:testPathSecurity_MixedSeparators() local safe = utils.isPathSafe("../path\\to/../file") luaunit.assertFalse(safe) end function TestPathSecurity:testPathSecurity_DoubleEncodedTraversal() local safe = utils.isPathSafe("%252e%252e%252f") luaunit.assertFalse(safe) end function TestPathSecurity:testPathSecurity_LegitimateFileWithDots() -- Files with dots in name should be OK (not ..) local safe = utils.isPathSafe("/path/to/file.backup.txt") luaunit.assertTrue(safe) end function TestPathSecurity:testPathSecurity_HiddenFiles() -- Hidden files (starting with .) should be OK local safe = utils.isPathSafe("/path/to/.hidden") luaunit.assertTrue(safe) end -- Run tests if this file is executed directly if not _G.RUNNING_ALL_TESTS then os.exit(luaunit.LuaUnit.run()) end