util library and string lib extension

This commit is contained in:
Sky Johnson 2025-05-05 09:40:15 -05:00
parent 30a126909b
commit 861ab73d83
6 changed files with 730 additions and 224 deletions

View File

@ -22,119 +22,69 @@ var sqliteLuaCode string
//go:embed fs.lua //go:embed fs.lua
var fsLuaCode string var fsLuaCode string
// Global bytecode cache to improve performance //go:embed util.lua
var utilLuaCode string
//go:embed string.lua
var stringLuaCode string
// ModuleInfo holds information about an embeddable Lua module
type ModuleInfo struct {
Name string // Module name
Code string // Module source code
Bytecode atomic.Pointer[[]byte] // Cached bytecode
Once sync.Once // For one-time compilation
}
var ( var (
sandboxBytecode atomic.Pointer[[]byte] sandbox = ModuleInfo{Name: "sandbox", Code: sandboxLuaCode}
jsonBytecode atomic.Pointer[[]byte] modules = []ModuleInfo{
sqliteBytecode atomic.Pointer[[]byte] {Name: "json", Code: jsonLuaCode},
fsBytecode atomic.Pointer[[]byte] {Name: "sqlite", Code: sqliteLuaCode},
bytecodeOnce sync.Once {Name: "fs", Code: fsLuaCode},
jsonBytecodeOnce sync.Once {Name: "util", Code: utilLuaCode},
sqliteBytecodeOnce sync.Once {Name: "string", Code: stringLuaCode},
fsBytecodeOnce sync.Once }
) )
// precompileSandboxCode compiles the sandbox.lua code to bytecode once // precompileModule compiles a module's code to bytecode once
func precompileSandboxCode() { func precompileModule(m *ModuleInfo) {
tempState := luajit.New() m.Once.Do(func() {
if tempState == nil { tempState := luajit.New()
logger.Fatal("Failed to create temp Lua state for bytecode compilation") if tempState == nil {
} logger.Fatal("Failed to create temp Lua state for %s module compilation", m.Name)
defer tempState.Close() return
defer tempState.Cleanup() }
defer tempState.Close()
defer tempState.Cleanup()
code, err := tempState.CompileBytecode(sandboxLuaCode, "sandbox.lua") code, err := tempState.CompileBytecode(m.Code, m.Name+".lua")
if err != nil { if err != nil {
logger.Error("Failed to compile sandbox code: %v", err) logger.Error("Failed to compile %s module: %v", m.Name, err)
return return
}
bytecode := make([]byte, len(code))
copy(bytecode, code)
sandboxBytecode.Store(&bytecode)
logger.Debug("Successfully precompiled sandbox.lua to bytecode (%d bytes)", len(code))
}
// precompileJsonModule compiles the json.lua code to bytecode once
func precompileJsonModule() {
tempState := luajit.New()
if tempState == nil {
logger.Fatal("Failed to create temp Lua state for JSON module compilation")
}
defer tempState.Close()
defer tempState.Cleanup()
code, err := tempState.CompileBytecode(jsonLuaCode, "json.lua")
if err != nil {
logger.Error("Failed to compile JSON module: %v", err)
return
}
bytecode := make([]byte, len(code))
copy(bytecode, code)
jsonBytecode.Store(&bytecode)
logger.Debug("Successfully precompiled json.lua to bytecode (%d bytes)", len(code))
}
// precompileSqliteModule compiles the sqlite.lua code to bytecode once
func precompileSqliteModule() {
tempState := luajit.New()
if tempState == nil {
logger.Fatal("Failed to create temp Lua state for SQLite module compilation")
}
defer tempState.Close()
defer tempState.Cleanup()
code, err := tempState.CompileBytecode(sqliteLuaCode, "sqlite.lua")
if err != nil {
logger.Error("Failed to compile SQLite module: %v", err)
return
}
bytecode := make([]byte, len(code))
copy(bytecode, code)
sqliteBytecode.Store(&bytecode)
logger.Debug("Successfully precompiled sqlite.lua to bytecode (%d bytes)", len(code))
}
func precompileFsModule() {
tempState := luajit.New()
if tempState == nil {
logger.Fatal("Failed to create temp Lua state for FS module compilation")
}
defer tempState.Close()
defer tempState.Cleanup()
code, err := tempState.CompileBytecode(fsLuaCode, "fs.lua")
if err != nil {
logger.Error("Failed to compile FS module: %v", err)
return
}
bytecode := make([]byte, len(code))
copy(bytecode, code)
fsBytecode.Store(&bytecode)
logger.Debug("Successfully precompiled fs.lua to bytecode (%d bytes)", len(code))
}
// loadSandboxIntoState loads the sandbox code into a Lua state
func loadSandboxIntoState(state *luajit.State, verbose bool) error {
bytecodeOnce.Do(precompileSandboxCode)
jsonBytecodeOnce.Do(precompileJsonModule)
sqliteBytecodeOnce.Do(precompileSqliteModule)
// First load and execute the JSON module
jsBytecode := jsonBytecode.Load()
if jsBytecode != nil && len(*jsBytecode) > 0 {
if verbose {
logger.Debug("Loading json.lua from precompiled bytecode")
} }
if err := state.LoadBytecode(*jsBytecode, "json.lua"); err != nil { bytecode := make([]byte, len(code))
copy(bytecode, code)
m.Bytecode.Store(&bytecode)
logger.Debug("Successfully precompiled %s.lua to bytecode (%d bytes)", m.Name, len(code))
})
}
// loadModule loads a module into a Lua state
func loadModule(state *luajit.State, m *ModuleInfo, verbose bool) error {
// Ensure bytecode is compiled
precompileModule(m)
// Attempt to load from bytecode
bytecode := m.Bytecode.Load()
if bytecode != nil && len(*bytecode) > 0 {
if verbose {
logger.Debug("Loading %s.lua from precompiled bytecode", m.Name)
}
if err := state.LoadBytecode(*bytecode, m.Name+".lua"); err != nil {
return err return err
} }
@ -142,75 +92,38 @@ func loadSandboxIntoState(state *luajit.State, verbose bool) error {
return err return err
} }
state.SetGlobal("json") state.SetGlobal(m.Name)
} else { } else {
// Fallback to interpreting the source
if verbose { if verbose {
logger.Warning("Using non-precompiled json.lua") logger.Warning("Using non-precompiled %s.lua", m.Name)
} }
if err := state.DoString(jsonLuaCode); err != nil { if err := state.DoString(m.Code); err != nil {
return err return err
} }
} }
// Initialize active connections tracking return nil
}
// loadSandboxIntoState loads all modules and sandbox into a Lua state
func loadSandboxIntoState(state *luajit.State, verbose bool) error {
// Load all modules first
for i := range modules {
if err := loadModule(state, &modules[i], verbose); err != nil {
return err
}
}
// Initialize active connections tracking (specific to SQLite)
if err := state.DoString(`__active_sqlite_connections = {}`); err != nil { if err := state.DoString(`__active_sqlite_connections = {}`); err != nil {
return err return err
} }
// Load SQLite module // Load the sandbox last
sqlBytecode := sqliteBytecode.Load() precompileModule(&sandbox)
if sqlBytecode != nil && len(*sqlBytecode) > 0 { bytecode := sandbox.Bytecode.Load()
if verbose {
logger.Debug("Loading sqlite.lua from precompiled bytecode")
}
if err := state.LoadBytecode(*sqlBytecode, "sqlite.lua"); err != nil {
return err
}
if err := state.RunBytecodeWithResults(1); err != nil {
return err
}
state.SetGlobal("sqlite")
} else {
if verbose {
logger.Warning("Using non-precompiled sqlite.lua")
}
if err := state.DoString(sqliteLuaCode); err != nil {
return err
}
}
fsBytecodeOnce.Do(precompileFsModule)
fsBytecode := fsBytecode.Load()
if fsBytecode != nil && len(*fsBytecode) > 0 {
if verbose {
logger.Debug("Loading fs.lua from precompiled bytecode")
}
if err := state.LoadBytecode(*fsBytecode, "fs.lua"); err != nil {
return err
}
if err := state.RunBytecodeWithResults(1); err != nil {
return err
}
state.SetGlobal("fs")
} else {
if verbose {
logger.Warning("Using non-precompiled fs.lua")
}
if err := state.DoString(fsLuaCode); err != nil {
return err
}
}
bytecode := sandboxBytecode.Load()
if bytecode != nil && len(*bytecode) > 0 { if bytecode != nil && len(*bytecode) > 0 {
if verbose { if verbose {
logger.Debug("Loading sandbox.lua from precompiled bytecode") logger.Debug("Loading sandbox.lua from precompiled bytecode")

View File

@ -113,6 +113,10 @@ func (s *Sandbox) registerCoreFunctions(state *luajit.State) error {
return err return err
} }
if err := RegisterUtilFunctions(state); err != nil {
return err
}
return nil return nil
} }

View File

@ -1,9 +1,5 @@
--[[ --[[
Moonshark Lua Sandbox Environment sandbox.lua
This file contains all the Lua code needed for the sandbox environment,
including core modules and utilities. It's designed to be embedded in the
Go binary at build time.
]]-- ]]--
__http_response = {} __http_response = {}
@ -421,60 +417,6 @@ local csrf = {
end end
} }
-- ======================================================================
-- UTIL MODULE
-- ======================================================================
-- Utility module implementation
local util = {
generate_token = function(length)
return __generate_token(length or 32)
end,
-- Deep copy of tables
deep_copy = function(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do res[k] = util.deep_copy(v) end
return res
end,
-- Merge tables
merge_tables = function(t1, t2)
if type(t1) ~= 'table' or type(t2) ~= 'table' then
error("Both arguments must be tables", 2)
end
local result = util.deep_copy(t1)
for k, v in pairs(t2) do
if type(v) == 'table' and type(result[k]) == 'table' then
result[k] = util.merge_tables(result[k], v)
else
result[k] = v
end
end
return result
end,
-- String utilities
string = {
-- Trim whitespace
trim = function(s)
return (s:gsub("^%s*(.-)%s*$", "%1"))
end,
-- Split string
split = function(s, delimiter)
delimiter = delimiter or ","
local result = {}
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match)
end
return result
end
}
}
-- ====================================================================== -- ======================================================================
-- TEMPLATE RENDER FUNCTION -- TEMPLATE RENDER FUNCTION
-- ====================================================================== -- ======================================================================
@ -619,5 +561,4 @@ _G.http = http
_G.session = session _G.session = session
_G.csrf = csrf _G.csrf = csrf
_G.cookie = cookie _G.cookie = cookie
_G.util = util
_G.password = password _G.password = password

197
core/runner/string.lua Normal file
View File

@ -0,0 +1,197 @@
--[[
string.lua - Extended string library functions
]]--
local string_ext = {}
-- ======================================================================
-- STRING UTILITY FUNCTIONS
-- ======================================================================
-- Trim whitespace from both ends
function string_ext.trim(s)
if type(s) ~= "string" then return s end
return s:match("^%s*(.-)%s*$")
end
-- Split string by delimiter
function string_ext.split(s, delimiter)
if type(s) ~= "string" then return {} end
delimiter = delimiter or ","
local result = {}
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match)
end
return result
end
-- Check if string starts with prefix
function string_ext.starts_with(s, prefix)
if type(s) ~= "string" or type(prefix) ~= "string" then return false end
return s:sub(1, #prefix) == prefix
end
-- Check if string ends with suffix
function string_ext.ends_with(s, suffix)
if type(s) ~= "string" or type(suffix) ~= "string" then return false end
return suffix == "" or s:sub(-#suffix) == suffix
end
-- Left pad a string
function string_ext.pad_left(s, len, char)
if type(s) ~= "string" or type(len) ~= "number" then return s end
char = char or " "
if #s >= len then return s end
return string.rep(char:sub(1,1), len - #s) .. s
end
-- Right pad a string
function string_ext.pad_right(s, len, char)
if type(s) ~= "string" or type(len) ~= "number" then return s end
char = char or " "
if #s >= len then return s end
return s .. string.rep(char:sub(1,1), len - #s)
end
-- Center a string
function string_ext.center(s, width, char)
if type(s) ~= "string" or width <= #s then return s end
char = char or " "
local pad_len = width - #s
local left_pad = math.floor(pad_len / 2)
local right_pad = pad_len - left_pad
return string.rep(char:sub(1,1), left_pad) .. s .. string.rep(char:sub(1,1), right_pad)
end
-- Count occurrences of substring
function string_ext.count(s, substr)
if type(s) ~= "string" or type(substr) ~= "string" or #substr == 0 then return 0 end
local count, pos = 0, 1
while true do
pos = s:find(substr, pos, true)
if not pos then break end
count = count + 1
pos = pos + 1
end
return count
end
-- Capitalize first letter
function string_ext.capitalize(s)
if type(s) ~= "string" or #s == 0 then return s end
return s:sub(1,1):upper() .. s:sub(2)
end
-- Capitalize all words
function string_ext.title(s)
if type(s) ~= "string" then return s end
return s:gsub("(%w)([%w]*)", function(first, rest)
return first:upper() .. rest:lower()
end)
end
-- Insert string at position
function string_ext.insert(s, pos, insert_str)
if type(s) ~= "string" or type(insert_str) ~= "string" then return s end
pos = math.max(1, math.min(pos, #s + 1))
return s:sub(1, pos - 1) .. insert_str .. s:sub(pos)
end
-- Remove substring
function string_ext.remove(s, start, length)
if type(s) ~= "string" then return s end
length = length or 1
if start < 1 or start > #s then return s end
return s:sub(1, start - 1) .. s:sub(start + length)
end
-- Replace substring once
function string_ext.replace(s, old, new, n)
if type(s) ~= "string" or type(old) ~= "string" or #old == 0 then return s end
new = new or ""
n = n or 1
return s:gsub(old:gsub("[%-%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%1"), new, n)
end
-- Check if string contains substring
function string_ext.contains(s, substr)
if type(s) ~= "string" or type(substr) ~= "string" then return false end
return s:find(substr, 1, true) ~= nil
end
-- Escape pattern magic characters
function string_ext.escape_pattern(s)
if type(s) ~= "string" then return s end
return s:gsub("[%-%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%1")
end
-- Wrap text at specified width
function string_ext.wrap(s, width, indent_first, indent_rest)
if type(s) ~= "string" or type(width) ~= "number" then return s end
width = math.max(1, width)
indent_first = indent_first or ""
indent_rest = indent_rest or indent_first
local result = {}
local line_prefix = indent_first
local pos = 1
while pos <= #s do
local line_width = width - #line_prefix
local end_pos = math.min(pos + line_width - 1, #s)
if end_pos < #s then
local last_space = s:sub(pos, end_pos):match(".*%s()")
if last_space then
end_pos = pos + last_space - 2
end
end
table.insert(result, line_prefix .. s:sub(pos, end_pos))
pos = end_pos + 1
-- Skip leading spaces on next line
while s:sub(pos, pos) == " " do
pos = pos + 1
end
line_prefix = indent_rest
end
return table.concat(result, "\n")
end
-- Limit string length with ellipsis
function string_ext.truncate(s, length, ellipsis)
if type(s) ~= "string" then return s end
ellipsis = ellipsis or "..."
if #s <= length then return s end
return s:sub(1, length - #ellipsis) .. ellipsis
end
-- ======================================================================
-- INSTALL EXTENSIONS INTO STRING LIBRARY
-- ======================================================================
for name, func in pairs(string_ext) do
string[name] = func
end
return string_ext

116
core/runner/util.go Normal file
View File

@ -0,0 +1,116 @@
package runner
import (
"encoding/base64"
"html"
"strings"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
)
// RegisterUtilFunctions registers utility functions with the Lua state
func RegisterUtilFunctions(state *luajit.State) error {
// HTML special chars
if err := state.RegisterGoFunction("__html_special_chars", htmlSpecialChars); err != nil {
return err
}
// HTML entities
if err := state.RegisterGoFunction("__html_entities", htmlEntities); err != nil {
return err
}
// Base64 encode
if err := state.RegisterGoFunction("__base64_encode", base64Encode); err != nil {
return err
}
// Base64 decode
if err := state.RegisterGoFunction("__base64_decode", base64Decode); err != nil {
return err
}
return nil
}
// htmlSpecialChars converts special characters to HTML entities
func htmlSpecialChars(state *luajit.State) int {
if !state.IsString(1) {
state.PushNil()
return 1
}
input := state.ToString(1)
result := html.EscapeString(input)
state.PushString(result)
return 1
}
// htmlEntities is a more comprehensive version of htmlSpecialChars
func htmlEntities(state *luajit.State) int {
if !state.IsString(1) {
state.PushNil()
return 1
}
input := state.ToString(1)
// First use HTML escape for standard entities
result := html.EscapeString(input)
// Additional entities beyond what html.EscapeString handles
replacements := map[string]string{
"©": "&copy;",
"®": "&reg;",
"™": "&trade;",
"€": "&euro;",
"£": "&pound;",
"¥": "&yen;",
"—": "&mdash;",
"": "&ndash;",
"…": "&hellip;",
"•": "&bull;",
"°": "&deg;",
"±": "&plusmn;",
"¼": "&frac14;",
"½": "&frac12;",
"¾": "&frac34;",
}
for char, entity := range replacements {
result = strings.ReplaceAll(result, char, entity)
}
state.PushString(result)
return 1
}
// base64Encode encodes a string to base64
func base64Encode(state *luajit.State) int {
if !state.IsString(1) {
state.PushNil()
return 1
}
input := state.ToString(1)
result := base64.StdEncoding.EncodeToString([]byte(input))
state.PushString(result)
return 1
}
// base64Decode decodes a base64 string
func base64Decode(state *luajit.State) int {
if !state.IsString(1) {
state.PushNil()
return 1
}
input := state.ToString(1)
result, err := base64.StdEncoding.DecodeString(input)
if err != nil {
state.PushNil()
return 1
}
state.PushString(string(result))
return 1
}

335
core/runner/util.lua Normal file
View File

@ -0,0 +1,335 @@
--[[
util.lua - Utility functions for the Lua sandbox
Enhanced with web development utilities
]]--
local util = {}
-- ======================================================================
-- CORE UTILITY FUNCTIONS
-- ======================================================================
-- Generate a random token
function util.generate_token(length)
return __generate_token(length or 32)
end
-- Deep copy of tables
function util.deep_copy(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do res[k] = util.deep_copy(v) end
return res
end
-- Merge tables
function util.merge_tables(t1, t2)
if type(t1) ~= 'table' or type(t2) ~= 'table' then
error("Both arguments must be tables", 2)
end
local result = util.deep_copy(t1)
for k, v in pairs(t2) do
if type(v) == 'table' and type(result[k]) == 'table' then
result[k] = util.merge_tables(result[k], v)
else
result[k] = v
end
end
return result
end
-- ======================================================================
-- HTML ENTITY FUNCTIONS
-- ======================================================================
-- HTML entity mapping for common characters
local html_entities = {
["&"] = "&amp;",
["<"] = "&lt;",
[">"] = "&gt;",
['"'] = "&quot;",
["'"] = "&#39;",
["/"] = "&#x2F;",
["`"] = "&#x60;",
["="] = "&#x3D;"
}
-- Convert special characters to HTML entities (like htmlspecialchars)
function util.html_special_chars(str)
if type(str) ~= "string" then
return str
end
return __html_special_chars(str)
end
-- Convert all applicable characters to HTML entities (like htmlentities)
function util.html_entities(str)
if type(str) ~= "string" then
return str
end
return __html_entities(str)
end
-- Convert HTML entities back to characters (simple version)
function util.html_entity_decode(str)
if type(str) ~= "string" then
return str
end
str = str:gsub("&lt;", "<")
str = str:gsub("&gt;", ">")
str = str:gsub("&quot;", '"')
str = str:gsub("&#39;", "'")
str = str:gsub("&amp;", "&")
return str
end
-- Convert newlines to <br> tags
function util.nl2br(str)
if type(str) ~= "string" then
return str
end
return str:gsub("\r\n", "<br>"):gsub("\n", "<br>"):gsub("\r", "<br>")
end
-- ======================================================================
-- URL FUNCTIONS
-- ======================================================================
-- URL encode a string
function util.url_encode(str)
if type(str) ~= "string" then
return str
end
str = str:gsub("\n", "\r\n")
str = str:gsub("([^%w %-%_%.%~])", function(c)
return string.format("%%%02X", string.byte(c))
end)
str = str:gsub(" ", "+")
return str
end
-- URL decode a string
function util.url_decode(str)
if type(str) ~= "string" then
return str
end
str = str:gsub("+", " ")
str = str:gsub("%%(%x%x)", function(h)
return string.char(tonumber(h, 16))
end)
return str
end
-- ======================================================================
-- VALIDATION FUNCTIONS
-- ======================================================================
-- Email validation
function util.is_email(str)
if type(str) ~= "string" then
return false
end
-- Simple email validation pattern
local pattern = "^[%w%.%%%+%-]+@[%w%.%%%+%-]+%.%w%w%w?%w?$"
return str:match(pattern) ~= nil
end
-- URL validation
function util.is_url(str)
if type(str) ~= "string" then
return false
end
-- Simple URL validation
local pattern = "^https?://[%w-_%.%?%.:/%+=&%%]+$"
return str:match(pattern) ~= nil
end
-- IP address validation (IPv4)
function util.is_ipv4(str)
if type(str) ~= "string" then
return false
end
local pattern = "^(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)$"
local a, b, c, d = str:match(pattern)
if not (a and b and c and d) then
return false
end
a, b, c, d = tonumber(a), tonumber(b), tonumber(c), tonumber(d)
return a <= 255 and b <= 255 and c <= 255 and d <= 255
end
-- Integer validation
function util.is_int(str)
if type(str) == "number" then
return math.floor(str) == str
elseif type(str) ~= "string" then
return false
end
return str:match("^-?%d+$") ~= nil
end
-- Float validation
function util.is_float(str)
if type(str) == "number" then
return true
elseif type(str) ~= "string" then
return false
end
return str:match("^-?%d+%.?%d*$") ~= nil
end
-- Boolean validation
function util.is_bool(value)
if type(value) == "boolean" then
return true
elseif type(value) ~= "string" and type(value) ~= "number" then
return false
end
local v = type(value) == "string" and value:lower() or value
return v == "1" or v == "true" or v == "on" or v == "yes" or
v == "0" or v == "false" or v == "off" or v == "no" or
v == 1 or v == 0
end
-- Convert to boolean
function util.to_bool(value)
if type(value) == "boolean" then
return value
elseif type(value) ~= "string" and type(value) ~= "number" then
return false
end
local v = type(value) == "string" and value:lower() or value
return v == "1" or v == "true" or v == "on" or v == "yes" or v == 1
end
-- Sanitize string (simple version)
function util.sanitize_string(str)
if type(str) ~= "string" then
return ""
end
return util.html_special_chars(str)
end
-- Sanitize to integer
function util.sanitize_int(value)
if type(value) ~= "string" and type(value) ~= "number" then
return 0
end
value = tostring(value)
local result = value:match("^-?%d+")
return result and tonumber(result) or 0
end
-- Sanitize to float
function util.sanitize_float(value)
if type(value) ~= "string" and type(value) ~= "number" then
return 0
end
value = tostring(value)
local result = value:match("^-?%d+%.?%d*")
return result and tonumber(result) or 0
end
-- Sanitize URL
function util.sanitize_url(str)
if type(str) ~= "string" then
return ""
end
-- Basic sanitization by removing control characters
str = str:gsub("[\000-\031]", "")
-- Make sure it's a valid URL
if util.is_url(str) then
return str
end
-- Try to prepend http:// if it's missing
if not str:match("^https?://") and util.is_url("http://" .. str) then
return "http://" .. str
end
return ""
end
-- Sanitize email
function util.sanitize_email(str)
if type(str) ~= "string" then
return ""
end
-- Remove all characters except common email characters
str = str:gsub("[^%a%d%!%#%$%%%&%'%*%+%-%/%=%?%^%_%`%{%|%}%~%@%.%[%]]", "")
-- Return only if it's a valid email
if util.is_email(str) then
return str
end
return ""
end
-- ======================================================================
-- SECURITY FUNCTIONS
-- ======================================================================
-- Basic XSS prevention
function util.xss_clean(str)
if type(str) ~= "string" then
return str
end
-- Convert problematic characters to entities
local result = util.html_special_chars(str)
-- Remove JavaScript event handlers
result = result:gsub("on%w+%s*=", "")
-- Remove JavaScript protocol
result = result:gsub("javascript:", "")
-- Remove CSS expression
result = result:gsub("expression%s*%(", "")
return result
end
-- Base64 encode
function util.base64_encode(str)
if type(str) ~= "string" then
return str
end
return __base64_encode(str)
end
-- Base64 decode
function util.base64_decode(str)
if type(str) ~= "string" then
return str
end
return __base64_decode(str)
end
return util