util library and string lib extension
This commit is contained in:
parent
30a126909b
commit
861ab73d83
@ -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")
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
197
core/runner/string.lua
Normal 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
116
core/runner/util.go
Normal 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{
|
||||||
|
"©": "©",
|
||||||
|
"®": "®",
|
||||||
|
"™": "™",
|
||||||
|
"€": "€",
|
||||||
|
"£": "£",
|
||||||
|
"¥": "¥",
|
||||||
|
"—": "—",
|
||||||
|
"–": "–",
|
||||||
|
"…": "…",
|
||||||
|
"•": "•",
|
||||||
|
"°": "°",
|
||||||
|
"±": "±",
|
||||||
|
"¼": "¼",
|
||||||
|
"½": "½",
|
||||||
|
"¾": "¾",
|
||||||
|
}
|
||||||
|
|
||||||
|
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
335
core/runner/util.lua
Normal 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 = {
|
||||||
|
["&"] = "&",
|
||||||
|
["<"] = "<",
|
||||||
|
[">"] = ">",
|
||||||
|
['"'] = """,
|
||||||
|
["'"] = "'",
|
||||||
|
["/"] = "/",
|
||||||
|
["`"] = "`",
|
||||||
|
["="] = "="
|
||||||
|
}
|
||||||
|
|
||||||
|
-- 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("<", "<")
|
||||||
|
str = str:gsub(">", ">")
|
||||||
|
str = str:gsub(""", '"')
|
||||||
|
str = str:gsub("'", "'")
|
||||||
|
str = str:gsub("&", "&")
|
||||||
|
|
||||||
|
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
|
Loading…
x
Reference in New Issue
Block a user