mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
184 lines
5.4 KiB
Lua
184 lines
5.4 KiB
Lua
local modulePath = (...):match("(.-)[^%.]+$")
|
|
local function req(name)
|
|
return require(modulePath .. name)
|
|
end
|
|
|
|
-- Text sanitization, escaping, and input validation utilities.
|
|
|
|
-- ErrorHandler is injected via init() for truncation warnings.
|
|
local ErrorHandler = nil
|
|
|
|
--- Initialize dependencies
|
|
---@param deps table Dependencies: { ErrorHandler = ErrorHandler }
|
|
local function init(deps)
|
|
if type(deps) == "table" then
|
|
ErrorHandler = deps.ErrorHandler
|
|
end
|
|
end
|
|
|
|
--- Sanitize text to prevent security vulnerabilities
|
|
--- @param text string? Text to sanitize
|
|
--- @param options table? Sanitization options
|
|
--- @return string Sanitized text
|
|
local function sanitizeText(text, options)
|
|
local utf8 = require("utf8")
|
|
-- Handle nil or non-string inputs
|
|
if text == nil then
|
|
return ""
|
|
end
|
|
if type(text) ~= "string" then
|
|
text = tostring(text)
|
|
end
|
|
|
|
-- Default options
|
|
options = options or {}
|
|
local maxLength = options.maxLength or 10000
|
|
local allowNewlines = options.allowNewlines ~= false -- default true
|
|
local allowTabs = options.allowTabs ~= false -- default true
|
|
local stripControls = options.stripControls ~= false -- default true
|
|
local trimWhitespace = options.trimWhitespace ~= false -- default true
|
|
|
|
-- Remove null bytes (critical security risk)
|
|
text = text:gsub("%z", "")
|
|
|
|
-- Strip control characters except allowed ones
|
|
if stripControls then
|
|
local pattern = "[\1-\31\127]" -- All control characters
|
|
if allowNewlines and allowTabs then
|
|
pattern = "[\1-\8\11\12\14-\31\127]" -- Exclude \t (9), \n (10), \r (13)
|
|
elseif allowNewlines then
|
|
pattern = "[\1-\9\11\12\14-\31\127]" -- Exclude \n (10), \r (13)
|
|
elseif allowTabs then
|
|
pattern = "[\1-\8\10\12-\31\127]" -- Exclude \t (9)
|
|
end
|
|
text = text:gsub(pattern, "")
|
|
end
|
|
|
|
-- Trim leading/trailing whitespace
|
|
if trimWhitespace then
|
|
text = text:match("^%s*(.-)%s*$") or ""
|
|
end
|
|
|
|
-- Limit string length (use UTF-8 character count, not byte count)
|
|
local charCount = utf8.len(text)
|
|
if charCount and charCount > maxLength then
|
|
if ErrorHandler then
|
|
ErrorHandler:warn("utils", "UTIL_001", {
|
|
original = charCount,
|
|
truncated = maxLength,
|
|
})
|
|
end
|
|
-- Truncate to maxLength UTF-8 characters
|
|
local bytePos = utf8.offset(text, maxLength + 1)
|
|
if bytePos then
|
|
text = text:sub(1, bytePos - 1)
|
|
end
|
|
if ErrorHandler then
|
|
ErrorHandler:warn("utils", string.format("Text truncated from %d to %d characters", charCount, maxLength))
|
|
end
|
|
end
|
|
|
|
return text
|
|
end
|
|
|
|
--- Validate text input against rules
|
|
--- @param text string Text to validate
|
|
--- @param rules table Validation rules
|
|
--- @return boolean, string? Returns true if valid, or false with error message
|
|
local function validateTextInput(text, rules)
|
|
rules = rules or {}
|
|
|
|
-- Check minimum length
|
|
if rules.minLength and #text < rules.minLength then
|
|
return false, string.format("Text must be at least %d characters", rules.minLength)
|
|
end
|
|
|
|
-- Check maximum length
|
|
if rules.maxLength and #text > rules.maxLength then
|
|
return false, string.format("Text must be at most %d characters", rules.maxLength)
|
|
end
|
|
|
|
-- Check pattern match
|
|
if rules.pattern and not text:match(rules.pattern) then
|
|
return false, rules.patternError or "Text does not match required pattern"
|
|
end
|
|
|
|
-- Check character whitelist
|
|
if rules.allowedChars then
|
|
local pattern = "[^" .. rules.allowedChars .. "]"
|
|
if text:match(pattern) then
|
|
return false, "Text contains invalid characters"
|
|
end
|
|
end
|
|
|
|
-- Check character blacklist
|
|
if rules.forbiddenChars then
|
|
local pattern = "[" .. rules.forbiddenChars .. "]"
|
|
if text:match(pattern) then
|
|
return false, "Text contains forbidden characters"
|
|
end
|
|
end
|
|
|
|
return true, nil
|
|
end
|
|
|
|
--- Validate text against range/length rules (alias of validateTextInput)
|
|
--- @param text string Text to validate
|
|
--- @param rules table Validation rules (minLength, maxLength, pattern, etc.)
|
|
--- @return boolean, string? Returns true if valid, or false with error message
|
|
local function validateTextRange(text, rules)
|
|
return validateTextInput(text, rules)
|
|
end
|
|
|
|
--- Escape HTML special characters
|
|
--- @param text string Text to escape
|
|
--- @return string Escaped text
|
|
local function escapeHtml(text)
|
|
if text == nil then
|
|
return ""
|
|
end
|
|
text = tostring(text)
|
|
text = text:gsub("&", "&")
|
|
text = text:gsub("<", "<")
|
|
text = text:gsub(">", ">")
|
|
text = text:gsub('"', """)
|
|
text = text:gsub("'", "'")
|
|
return text
|
|
end
|
|
|
|
--- Escape Lua pattern special characters
|
|
--- @param text string Text to escape
|
|
--- @return string Escaped text
|
|
local function escapeLuaPattern(text)
|
|
if text == nil then
|
|
return ""
|
|
end
|
|
text = tostring(text)
|
|
-- Escape all Lua pattern special characters
|
|
text = text:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
|
|
return text
|
|
end
|
|
|
|
--- Strip all non-printable characters from text
|
|
--- @param text string Text to clean
|
|
--- @return string Cleaned text
|
|
local function stripNonPrintable(text)
|
|
if text == nil then
|
|
return ""
|
|
end
|
|
text = tostring(text)
|
|
-- Keep printable ASCII (32-126), newline (10), tab (9), and carriage return (13)
|
|
text = text:gsub("[^\9\10\13\32-\126]", "")
|
|
return text
|
|
end
|
|
|
|
return {
|
|
init = init,
|
|
sanitizeText = sanitizeText,
|
|
validateTextInput = validateTextInput,
|
|
validateTextRange = validateTextRange,
|
|
escapeHtml = escapeHtml,
|
|
escapeLuaPattern = escapeLuaPattern,
|
|
stripNonPrintable = stripNonPrintable,
|
|
}
|