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
|
||||
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 (
|
||||
sandboxBytecode atomic.Pointer[[]byte]
|
||||
jsonBytecode atomic.Pointer[[]byte]
|
||||
sqliteBytecode atomic.Pointer[[]byte]
|
||||
fsBytecode atomic.Pointer[[]byte]
|
||||
bytecodeOnce sync.Once
|
||||
jsonBytecodeOnce sync.Once
|
||||
sqliteBytecodeOnce sync.Once
|
||||
fsBytecodeOnce sync.Once
|
||||
sandbox = ModuleInfo{Name: "sandbox", Code: sandboxLuaCode}
|
||||
modules = []ModuleInfo{
|
||||
{Name: "json", Code: jsonLuaCode},
|
||||
{Name: "sqlite", Code: sqliteLuaCode},
|
||||
{Name: "fs", Code: fsLuaCode},
|
||||
{Name: "util", Code: utilLuaCode},
|
||||
{Name: "string", Code: stringLuaCode},
|
||||
}
|
||||
)
|
||||
|
||||
// precompileSandboxCode compiles the sandbox.lua code to bytecode once
|
||||
func precompileSandboxCode() {
|
||||
tempState := luajit.New()
|
||||
if tempState == nil {
|
||||
logger.Fatal("Failed to create temp Lua state for bytecode compilation")
|
||||
}
|
||||
defer tempState.Close()
|
||||
defer tempState.Cleanup()
|
||||
// precompileModule compiles a module's code to bytecode once
|
||||
func precompileModule(m *ModuleInfo) {
|
||||
m.Once.Do(func() {
|
||||
tempState := luajit.New()
|
||||
if tempState == nil {
|
||||
logger.Fatal("Failed to create temp Lua state for %s module compilation", m.Name)
|
||||
return
|
||||
}
|
||||
defer tempState.Close()
|
||||
defer tempState.Cleanup()
|
||||
|
||||
code, err := tempState.CompileBytecode(sandboxLuaCode, "sandbox.lua")
|
||||
if err != nil {
|
||||
logger.Error("Failed to compile sandbox code: %v", err)
|
||||
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")
|
||||
code, err := tempState.CompileBytecode(m.Code, m.Name+".lua")
|
||||
if err != nil {
|
||||
logger.Error("Failed to compile %s module: %v", m.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -142,75 +92,38 @@ func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
state.SetGlobal("json")
|
||||
state.SetGlobal(m.Name)
|
||||
} else {
|
||||
// Fallback to interpreting the source
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load SQLite module
|
||||
sqlBytecode := sqliteBytecode.Load()
|
||||
if sqlBytecode != nil && len(*sqlBytecode) > 0 {
|
||||
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()
|
||||
// Load the sandbox last
|
||||
precompileModule(&sandbox)
|
||||
bytecode := sandbox.Bytecode.Load()
|
||||
if bytecode != nil && len(*bytecode) > 0 {
|
||||
if verbose {
|
||||
logger.Debug("Loading sandbox.lua from precompiled bytecode")
|
||||
|
@ -113,6 +113,10 @@ func (s *Sandbox) registerCoreFunctions(state *luajit.State) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := RegisterUtilFunctions(state); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
--[[
|
||||
Moonshark Lua Sandbox Environment
|
||||
|
||||
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.
|
||||
sandbox.lua
|
||||
]]--
|
||||
|
||||
__http_response = {}
|
||||
@ -421,60 +417,6 @@ local csrf = {
|
||||
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
|
||||
-- ======================================================================
|
||||
@ -619,5 +561,4 @@ _G.http = http
|
||||
_G.session = session
|
||||
_G.csrf = csrf
|
||||
_G.cookie = cookie
|
||||
_G.util = util
|
||||
_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