282 lines
8.8 KiB
Lua
282 lines
8.8 KiB
Lua
-- 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
|