optimize session set, move lua libs to global

This commit is contained in:
Sky Johnson 2025-05-28 18:28:24 -05:00
parent 6264407d02
commit 39d14d0025
7 changed files with 807 additions and 777 deletions

View File

@ -46,18 +46,19 @@ type ModuleInfo struct {
Code string // Module source code
Bytecode atomic.Pointer[[]byte] // Cached bytecode
Once sync.Once // For one-time compilation
DefinesGlobal bool // Whether module defines globals directly
}
var (
sandbox = ModuleInfo{Name: "sandbox", Code: sandboxLuaCode}
modules = []ModuleInfo{
{Name: "json", Code: jsonLuaCode},
{Name: "json", Code: jsonLuaCode, DefinesGlobal: true},
{Name: "sqlite", Code: sqliteLuaCode},
{Name: "fs", Code: fsLuaCode},
{Name: "util", Code: utilLuaCode},
{Name: "fs", Code: fsLuaCode, DefinesGlobal: true},
{Name: "util", Code: utilLuaCode, DefinesGlobal: true},
{Name: "string", Code: stringLuaCode},
{Name: "table", Code: tableLuaCode},
{Name: "crypto", Code: cryptoLuaCode},
{Name: "crypto", Code: cryptoLuaCode, DefinesGlobal: true},
{Name: "time", Code: timeLuaCode},
{Name: "math", Code: mathLuaCode},
}
@ -104,11 +105,18 @@ func loadModule(state *luajit.State, m *ModuleInfo, verbose bool) error {
return err
}
if m.DefinesGlobal {
// Module defines its own globals, just run it
if err := state.RunBytecode(); err != nil {
return err
}
} else {
// Module returns a table, capture and set as global
if err := state.RunBytecodeWithResults(1); err != nil {
return err
}
state.SetGlobal(m.Name)
}
} else {
// Fallback to interpreting the source
if verbose {

View File

@ -2,8 +2,6 @@
crypto.lua - Cryptographic functions powered by Go
]]--
local crypto = {}
-- ======================================================================
-- HASHING FUNCTIONS
-- ======================================================================
@ -11,9 +9,9 @@ local crypto = {}
-- Generate hash digest using various algorithms
-- Algorithms: md5, sha1, sha256, sha512
-- Formats: hex (default), binary
function crypto.hash(data, algorithm, format)
function hash(data, algorithm, format)
if type(data) ~= "string" then
error("crypto.hash: data must be a string", 2)
error("hash: data must be a string", 2)
end
algorithm = algorithm or "sha256"
@ -22,21 +20,20 @@ function crypto.hash(data, algorithm, format)
return __crypto_hash(data, algorithm, format)
end
-- Convenience functions for common hash algorithms
function crypto.md5(data, format)
return crypto.hash(data, "md5", format)
function md5(data, format)
return hash(data, "md5", format)
end
function crypto.sha1(data, format)
return crypto.hash(data, "sha1", format)
function sha1(data, format)
return hash(data, "sha1", format)
end
function crypto.sha256(data, format)
return crypto.hash(data, "sha256", format)
function sha256(data, format)
return hash(data, "sha256", format)
end
function crypto.sha512(data, format)
return crypto.hash(data, "sha512", format)
function sha512(data, format)
return hash(data, "sha512", format)
end
-- ======================================================================
@ -46,13 +43,13 @@ end
-- Generate HMAC using various algorithms
-- Algorithms: md5, sha1, sha256, sha512
-- Formats: hex (default), binary
function crypto.hmac(data, key, algorithm, format)
function hmac(data, key, algorithm, format)
if type(data) ~= "string" then
error("crypto.hmac: data must be a string", 2)
error("hmac: data must be a string", 2)
end
if type(key) ~= "string" then
error("crypto.hmac: key must be a string", 2)
error("hmac: key must be a string", 2)
end
algorithm = algorithm or "sha256"
@ -61,21 +58,20 @@ function crypto.hmac(data, key, algorithm, format)
return __crypto_hmac(data, key, algorithm, format)
end
-- Convenience functions for common HMAC algorithms
function crypto.hmac_md5(data, key, format)
return crypto.hmac(data, key, "md5", format)
function hmac_md5(data, key, format)
return hmac(data, key, "md5", format)
end
function crypto.hmac_sha1(data, key, format)
return crypto.hmac(data, key, "sha1", format)
function hmac_sha1(data, key, format)
return hmac(data, key, "sha1", format)
end
function crypto.hmac_sha256(data, key, format)
return crypto.hmac(data, key, "sha256", format)
function hmac_sha256(data, key, format)
return hmac(data, key, "sha256", format)
end
function crypto.hmac_sha512(data, key, format)
return crypto.hmac(data, key, "sha512", format)
function hmac_sha512(data, key, format)
return hmac(data, key, "sha512", format)
end
-- ======================================================================
@ -84,9 +80,9 @@ end
-- Generate random bytes
-- Formats: binary (default), hex
function crypto.random_bytes(length, secure, format)
function random_bytes(length, secure, format)
if type(length) ~= "number" or length <= 0 then
error("crypto.random_bytes: length must be positive", 2)
error("random_bytes: length must be positive", 2)
end
secure = secure ~= false -- Default to secure
@ -96,13 +92,13 @@ function crypto.random_bytes(length, secure, format)
end
-- Generate random integer in range [min, max]
function crypto.random_int(min, max, secure)
function random_int(min, max, secure)
if type(min) ~= "number" or type(max) ~= "number" then
error("crypto.random_int: min and max must be numbers", 2)
error("random_int: min and max must be numbers", 2)
end
if max <= min then
error("crypto.random_int: max must be greater than min", 2)
error("random_int: max must be greater than min", 2)
end
secure = secure ~= false -- Default to secure
@ -111,9 +107,9 @@ function crypto.random_int(min, max, secure)
end
-- Generate random string of specified length
function crypto.random_string(length, charset, secure)
function random_string(length, charset, secure)
if type(length) ~= "number" or length <= 0 then
error("crypto.random_string: length must be positive", 2)
error("random_string: length must be positive", 2)
end
secure = secure ~= false -- Default to secure
@ -122,14 +118,14 @@ function crypto.random_string(length, charset, secure)
charset = charset or "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
if type(charset) ~= "string" or #charset == 0 then
error("crypto.random_string: charset must be non-empty", 2)
error("random_string: charset must be non-empty", 2)
end
local result = ""
local charset_length = #charset
for i = 1, length do
local index = crypto.random_int(1, charset_length, secure)
local index = random_int(1, charset_length, secure)
result = result .. charset:sub(index, index)
end
@ -141,8 +137,6 @@ end
-- ======================================================================
-- Generate random UUID (v4)
function crypto.uuid()
function uuid()
return __crypto_uuid()
end
return crypto

View File

@ -1,49 +1,47 @@
local fs = {}
fs.read = function(path)
function fs_read(path)
if type(path) ~= "string" then
error("fs.read: path must be a string", 2)
error("fs_read: path must be a string", 2)
end
return __fs_read_file(path)
end
fs.write = function(path, content)
function fs_write(path, content)
if type(path) ~= "string" then
error("fs.write: path must be a string", 2)
error("fs_write: path must be a string", 2)
end
if type(content) ~= "string" then
error("fs.write: content must be a string", 2)
error("fs_write: content must be a string", 2)
end
return __fs_write_file(path, content)
end
fs.append = function(path, content)
function fs_append(path, content)
if type(path) ~= "string" then
error("fs.append: path must be a string", 2)
error("fs_append: path must be a string", 2)
end
if type(content) ~= "string" then
error("fs.append: content must be a string", 2)
error("fs_append: content must be a string", 2)
end
return __fs_append_file(path, content)
end
fs.exists = function(path)
function fs_exists(path)
if type(path) ~= "string" then
error("fs.exists: path must be a string", 2)
error("fs_exists: path must be a string", 2)
end
return __fs_exists(path)
end
fs.remove = function(path)
function fs_remove(path)
if type(path) ~= "string" then
error("fs.remove: path must be a string", 2)
error("fs_remove: path must be a string", 2)
end
return __fs_remove_file(path)
end
fs.info = function(path)
function fs_info(path)
if type(path) ~= "string" then
error("fs.info: path must be a string", 2)
error("fs_info: path must be a string", 2)
end
local info = __fs_get_info(path)
@ -56,58 +54,58 @@ fs.info = function(path)
end
-- Directory Operations
fs.mkdir = function(path, mode)
function fs_mkdir(path, mode)
if type(path) ~= "string" then
error("fs.mkdir: path must be a string", 2)
error("fs_mkdir: path must be a string", 2)
end
mode = mode or 0755
return __fs_make_dir(path, mode)
end
fs.ls = function(path)
function fs_ls(path)
if type(path) ~= "string" then
error("fs.ls: path must be a string", 2)
error("fs_ls: path must be a string", 2)
end
return __fs_list_dir(path)
end
fs.rmdir = function(path, recursive)
function fs_rmdir(path, recursive)
if type(path) ~= "string" then
error("fs.rmdir: path must be a string", 2)
error("fs_rmdir: path must be a string", 2)
end
recursive = recursive or false
return __fs_remove_dir(path, recursive)
end
-- Path Operations
fs.join_paths = function(...)
function fs_join_paths(...)
return __fs_join_paths(...)
end
fs.dir_name = function(path)
function fs_dir_name(path)
if type(path) ~= "string" then
error("fs.dir_name: path must be a string", 2)
error("fs_dir_name: path must be a string", 2)
end
return __fs_dir_name(path)
end
fs.base_name = function(path)
function fs_base_name(path)
if type(path) ~= "string" then
error("fs.base_name: path must be a string", 2)
error("fs_base_name: path must be a string", 2)
end
return __fs_base_name(path)
end
fs.extension = function(path)
function fs_extension(path)
if type(path) ~= "string" then
error("fs.extension: path must be a string", 2)
error("fs_extension: path must be a string", 2)
end
return __fs_extension(path)
end
-- Utility Functions
fs.read_json = function(path)
local content = fs.read_file(path)
function fs_read_json(path)
local content = fs_read(path)
if not content then
return nil, "Could not read file"
end
@ -120,9 +118,9 @@ fs.read_json = function(path)
return result
end
fs.write_json = function(path, data, pretty)
function fs_write_json(path, data, pretty)
if type(data) ~= "table" then
error("fs.write_json: data must be a table", 2)
error("fs_write_json: data must be a table", 2)
end
local content
@ -132,7 +130,5 @@ fs.write_json = function(path, data, pretty)
content = json.encode(data)
end
return fs.write_file(path, content)
return fs_write(path, content)
end
return fs

View File

@ -1,18 +1,23 @@
-- json.lua: High-performance JSON module for Moonshark
local json = {}
function json.go_encode(value)
-- Pre-computed escape sequences to avoid recreating table
local escape_chars = {
['"'] = '\\"', ['\\'] = '\\\\',
['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t'
}
function json_go_encode(value)
return __json_marshal(value)
end
function json.go_decode(str)
function json_go_decode(str)
if type(str) ~= "string" then
error("json.decode: expected string, got " .. type(str), 2)
error("json_decode: expected string, got " .. type(str), 2)
end
return __json_unmarshal(str)
end
function json.encode(data)
function json_encode(data)
local t = type(data)
if t == "nil" then return "null" end
@ -20,48 +25,34 @@ function json.encode(data)
if t == "number" then return tostring(data) end
if t == "string" then
local escape_chars = {
['"'] = '\\"', ['\\'] = '\\\\',
['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t'
}
return '"' .. data:gsub('[\\"\n\r\t]', escape_chars) .. '"'
end
if t == "table" then
local isArray = true
local count = 0
local max_index = 0
-- Check if it's an array in one pass
for k, _ in pairs(data) do
count = count + 1
if type(k) == "number" and k > 0 and math.floor(k) == k then
max_index = math.max(max_index, k)
else
if type(k) ~= "number" or k ~= count or k < 1 then
isArray = false
break
end
end
local result = {}
if isArray then
for i, v in ipairs(data) do
result[i] = json.encode(v)
local result = {}
for i = 1, count do
result[i] = json_encode(data[i])
end
return "[" .. table.concat(result, ",") .. "]"
else
local size = 0
for k, v in pairs(data) do
if type(k) == "string" and type(v) ~= "function" and type(v) ~= "userdata" then
size = size + 1
end
end
result = {}
local result = {}
local index = 1
for k, v in pairs(data) do
if type(k) == "string" and type(v) ~= "function" and type(v) ~= "userdata" then
result[index] = json.encode(k) .. ":" .. json.encode(v)
result[index] = json_encode(k) .. ":" .. json_encode(v)
index = index + 1
end
end
@ -72,7 +63,7 @@ function json.encode(data)
return "null" -- Unsupported type
end
function json.decode(data)
function json_decode(data)
local pos = 1
local len = #data
@ -372,15 +363,15 @@ function json.decode(data)
return result
end
function json.is_valid(str)
function json_is_valid(str)
if type(str) ~= "string" then return false end
local status, _ = pcall(json.decode, str)
local status, _ = pcall(json_decode, str)
return status
end
function json.pretty_print(value)
function json_pretty_print(value)
if type(value) == "string" then
value = json.decode(value)
value = json_decode(value)
end
local function stringify(val, indent, visited)
@ -429,5 +420,3 @@ function json.pretty_print(value)
return stringify(value)
end
return json

View File

@ -60,75 +60,72 @@ function __ensure_response()
end
-- ======================================================================
-- HTTP MODULE
-- HTTP FUNCTIONS
-- ======================================================================
local http = {
-- Set HTTP status code
set_status = function(code)
-- Set HTTP status code
function http_set_status(code)
if type(code) ~= "number" then
error("http.set_status: status code must be a number", 2)
error("http_set_status: status code must be a number", 2)
end
local resp = __ensure_response()
resp.status = code
end,
end
-- Set HTTP header
set_header = function(name, value)
-- Set HTTP header
function http_set_header(name, value)
if type(name) ~= "string" or type(value) ~= "string" then
error("http.set_header: name and value must be strings", 2)
error("http_set_header: name and value must be strings", 2)
end
local resp = __ensure_response()
resp.headers = resp.headers or {}
resp.headers[name] = value
end,
end
-- Set content type; set_header helper
set_content_type = function(content_type)
http.set_header("Content-Type", content_type)
end,
-- Set content type; http_set_header helper
function http_set_content_type(content_type)
http_set_header("Content-Type", content_type)
end
-- Set metadata (arbitrary data to be returned with response)
set_metadata = function(key, value)
-- Set metadata (arbitrary data to be returned with response)
function http_set_metadata(key, value)
if type(key) ~= "string" then
error("http.set_metadata: key must be a string", 2)
error("http_set_metadata: key must be a string", 2)
end
local resp = __ensure_response()
resp.metadata = resp.metadata or {}
resp.metadata[key] = value
end,
end
-- HTTP client submodule
client = {
-- Generic request function
request = function(method, url, body, options)
-- Generic HTTP request function
function http_request(method, url, body, options)
if type(method) ~= "string" then
error("http.client.request: method must be a string", 2)
error("http_request: method must be a string", 2)
end
if type(url) ~= "string" then
error("http.client.request: url must be a string", 2)
error("http_request: url must be a string", 2)
end
-- Call native implementation
local result = __http_request(method, url, body, options)
return result
end,
end
-- Shorthand function to directly get JSON
get_json = function(url, options)
-- Shorthand function to directly get JSON
function http_get_json(url, options)
options = options or {}
local response = http.client.get(url, options)
local response = http_get(url, options)
if response.ok and response.json then
return response.json
end
return nil, response
end,
end
-- Utility to build a URL with query parameters
build_url = function(base_url, params)
-- Utility to build a URL with query parameters
function http_build_url(base_url, params)
if not params or type(params) ~= "table" then
return base_url
end
@ -137,10 +134,10 @@ local http = {
for k, v in pairs(params) do
if type(v) == "table" then
for _, item in ipairs(v) do
table.insert(query, util.url_encode(k) .. "=" .. util.url_encode(tostring(item)))
table.insert(query, url_encode(k) .. "=" .. url_encode(tostring(item)))
end
else
table.insert(query, util.url_encode(k) .. "=" .. util.url_encode(tostring(v)))
table.insert(query, url_encode(k) .. "=" .. url_encode(tostring(v)))
end
end
@ -153,33 +150,31 @@ local http = {
end
return base_url
end
}
}
end
local function make_method(method, needs_body)
return function(url, body_or_options, options)
if needs_body then
options = options or {}
return http.client.request(method, url, body_or_options, options)
return http_request(method, url, body_or_options, options)
else
body_or_options = body_or_options or {}
return http.client.request(method, url, nil, body_or_options)
return http_request(method, url, nil, body_or_options)
end
end
end
http.client.get = make_method("GET", false)
http.client.delete = make_method("DELETE", false)
http.client.head = make_method("HEAD", false)
http.client.options = make_method("OPTIONS", false)
http.client.post = make_method("POST", true)
http.client.put = make_method("PUT", true)
http.client.patch = make_method("PATCH", true)
http_get = make_method("GET", false)
http_delete = make_method("DELETE", false)
http_head = make_method("HEAD", false)
http_options = make_method("OPTIONS", false)
http_post = make_method("POST", true)
http_put = make_method("PUT", true)
http_patch = make_method("PATCH", true)
http.redirect = function(url, status)
function http_redirect(url, status)
if type(url) ~= "string" then
error("http.redirect: url must be a string", 2)
error("http_redirect: url must be a string", 2)
end
status = status or 302 -- Default to temporary redirect
@ -194,14 +189,13 @@ http.redirect = function(url, status)
end
-- ======================================================================
-- COOKIE MODULE
-- COOKIE FUNCTIONS
-- ======================================================================
local cookie = {
-- Set a cookie
set = function(name, value, options)
-- Set a cookie
function cookie_set(name, value, options)
if type(name) ~= "string" then
error("cookie.set: name must be a string", 2)
error("cookie_set: name must be a string", 2)
end
local resp = __ensure_response()
@ -237,7 +231,7 @@ local cookie = {
local valid_values = {none = true, lax = true, strict = true}
if not valid_values[same_site] then
error("cookie.set: same_site must be one of 'None', 'Lax', or 'Strict'", 2)
error("cookie_set: same_site must be one of 'None', 'Lax', or 'Strict'", 2)
end
-- If SameSite=None, the cookie must be secure
@ -252,12 +246,12 @@ local cookie = {
table.insert(resp.cookies, cookie)
return true
end,
end
-- Get a cookie value
get = function(name)
-- Get a cookie value
function cookie_get(name)
if type(name) ~= "string" then
error("cookie.get: name must be a string", 2)
error("cookie_get: name must be a string", 2)
end
local env = getfenv(2)
@ -271,26 +265,24 @@ local cookie = {
end
return nil
end,
end
-- Remove a cookie
remove = function(name, path, domain)
-- Remove a cookie
function cookie_remove(name, path, domain)
if type(name) ~= "string" then
error("cookie.remove: name must be a string", 2)
error("cookie_remove: name must be a string", 2)
end
return cookie.set(name, "", {expires = 0, path = path or "/", domain = domain})
end
}
return cookie_set(name, "", {expires = 0, path = path or "/", domain = domain})
end
-- ======================================================================
-- SESSION MODULE
-- SESSION FUNCTIONS
-- ======================================================================
local session = {
get = function(key)
function session_get(key)
if type(key) ~= "string" then
error("session.get: key must be a string", 2)
error("session_get: key must be a string", 2)
end
local env = getfenv(2)
@ -300,22 +292,27 @@ local session = {
end
return nil
end,
end
set = function(key, value)
function session_set(key, value)
if type(key) ~= "string" then
error("session.set: key must be a string", 2)
error("session_set: key must be a string", 2)
end
if type(value) == nil then
error("session.set: value cannot be nil", 2)
error("session_set: value cannot be nil", 2)
end
local resp = __ensure_response()
resp.session = resp.session or {}
resp.session[key] = value
end,
id = function()
local env = getfenv(2)
if env.ctx and env.ctx.session and env.ctx.session.data then
env.ctx.session.data[key] = value
end
end
function session_id()
local env = getfenv(2)
if env.ctx and env.ctx.session then
@ -323,9 +320,9 @@ local session = {
end
return nil
end,
end
get_all = function()
function session_get_all()
local env = getfenv(2)
if env.ctx and env.ctx.session then
@ -333,11 +330,11 @@ local session = {
end
return nil
end,
end
delete = function(key)
function session_delete(key)
if type(key) ~= "string" then
error("session.delete: key must be a string", 2)
error("session_delete: key must be a string", 2)
end
local resp = __ensure_response()
@ -348,9 +345,9 @@ local session = {
if env.ctx and env.ctx.session and env.ctx.session.data then
env.ctx.session.data[key] = nil
end
end,
end
clear = function()
function session_clear()
local env = getfenv(2)
if env.ctx and env.ctx.session and env.ctx.session.data then
for k, _ in pairs(env.ctx.session.data) do
@ -361,30 +358,28 @@ local session = {
local resp = __ensure_response()
resp.session = {}
resp.session["__clear_all"] = true
end
}
end
-- ======================================================================
-- CSRF MODULE
-- CSRF FUNCTIONS
-- ======================================================================
local csrf = {
generate = function()
local token = util.generate_token(32)
session.set("_csrf_token", token)
function csrf_generate()
local token = generate_token(32)
session_set("_csrf_token", token)
return token
end,
end
field = function()
local token = session.get("_csrf_token")
function csrf_field()
local token = session_get("_csrf_token")
if not token then
token = csrf.generate()
token = csrf_generate()
end
return string.format('<input type="hidden" name="_csrf_token" value="%s" />',
util.html_special_chars(token))
end,
html_special_chars(token))
end
validate = function()
function csrf_validate()
local env = getfenv(2)
local token = false
if env.ctx and env.ctx.session and env.ctx.session.data then
@ -392,7 +387,7 @@ local csrf = {
end
if not token then
http.set_status(403)
http_set_status(403)
__http_response.body = "CSRF validation failed"
exit()
end
@ -408,14 +403,13 @@ local csrf = {
end
if not request_token or request_token ~= token then
http.set_status(403)
http_set_status(403)
__http_response.body = "CSRF validation failed"
exit()
end
return true
end
}
end
-- ======================================================================
-- TEMPLATE RENDER FUNCTIONS
@ -502,7 +496,7 @@ _G.render = function(template_str, env)
setfenv(fn, runtime_env)
local output_buffer = {}
fn(tostring, util.html_special_chars, output_buffer, 0)
fn(tostring, html_special_chars, output_buffer, 0)
return table.concat(output_buffer)
end
@ -536,7 +530,7 @@ _G.parse = function(template_str, env)
local value = env[name]
local str = tostring(value or "")
if escaped then
str = util.html_special_chars(str)
str = html_special_chars(str)
end
table.insert(output, str)
@ -576,7 +570,7 @@ _G.iparse = function(template_str, values)
local value = values[value_index]
local str = tostring(value or "")
if escaped then
str = util.html_special_chars(str)
str = html_special_chars(str)
end
table.insert(output, str)
@ -588,11 +582,9 @@ _G.iparse = function(template_str, values)
end
-- ======================================================================
-- PASSWORD MODULE
-- PASSWORD FUNCTIONS
-- ======================================================================
local password = {}
-- Hash a password using Argon2id
-- Options:
-- memory: Amount of memory to use in KB (default: 128MB)
@ -600,85 +592,72 @@ local password = {}
-- parallelism: Number of threads (default: 4)
-- salt_length: Length of salt in bytes (default: 16)
-- key_length: Length of the derived key in bytes (default: 32)
function password.hash(plain_password, options)
function password_hash(plain_password, options)
if type(plain_password) ~= "string" then
error("password.hash: expected string password", 2)
error("password_hash: expected string password", 2)
end
return __password_hash(plain_password, options)
end
-- Verify a password against a hash
function password.verify(plain_password, hash_string)
function password_verify(plain_password, hash_string)
if type(plain_password) ~= "string" then
error("password.verify: expected string password", 2)
error("password_verify: expected string password", 2)
end
if type(hash_string) ~= "string" then
error("password.verify: expected string hash", 2)
error("password_verify: expected string hash", 2)
end
return __password_verify(plain_password, hash_string)
end
-- ======================================================================
-- SEND MODULE
-- SEND FUNCTIONS
-- ======================================================================
local send = {}
function send.html(content)
http.set_content_type("text/html")
function send_html(content)
http_set_content_type("text/html")
return content
end
function send.json(content)
http.set_content_type("application/json")
function send_json(content)
http_set_content_type("application/json")
return content
end
function send.text(content)
http.set_content_type("text/plain")
function send_text(content)
http_set_content_type("text/plain")
return content
end
function send.xml(content)
http.set_content_type("application/xml")
function send_xml(content)
http_set_content_type("application/xml")
return content
end
function send.javascript(content)
http.set_content_type("application/javascript")
function send_javascript(content)
http_set_content_type("application/javascript")
return content
end
function send.css(content)
http.set_content_type("text/css")
function send_css(content)
http_set_content_type("text/css")
return content
end
function send.svg(content)
http.set_content_type("image/svg+xml")
function send_svg(content)
http_set_content_type("image/svg+xml")
return content
end
function send.csv(content)
http.set_content_type("text/csv")
function send_csv(content)
http_set_content_type("text/csv")
return content
end
function send.binary(content, mime_type)
http.set_content_type(mime_type or "application/octet-stream")
function send_binary(content, mime_type)
http_set_content_type(mime_type or "application/octet-stream")
return content
end
-- ======================================================================
-- REGISTER MODULES GLOBALLY
-- ======================================================================
_G.http = http
_G.session = session
_G.csrf = csrf
_G.cookie = cookie
_G.password = password
_G.send = send

View File

@ -1,16 +1,13 @@
--[[
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)
function generate_token(length)
return __generate_token(length or 32)
end
@ -18,20 +15,8 @@ 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)
function html_special_chars(str)
if type(str) ~= "string" then
return str
end
@ -40,7 +25,7 @@ function util.html_special_chars(str)
end
-- Convert all applicable characters to HTML entities (like htmlentities)
function util.html_entities(str)
function html_entities(str)
if type(str) ~= "string" then
return str
end
@ -49,7 +34,7 @@ function util.html_entities(str)
end
-- Convert HTML entities back to characters (simple version)
function util.html_entity_decode(str)
function html_entity_decode(str)
if type(str) ~= "string" then
return str
end
@ -64,7 +49,7 @@ function util.html_entity_decode(str)
end
-- Convert newlines to <br> tags
function util.nl2br(str)
function nl2br(str)
if type(str) ~= "string" then
return str
end
@ -77,7 +62,7 @@ end
-- ======================================================================
-- URL encode a string
function util.url_encode(str)
function url_encode(str)
if type(str) ~= "string" then
return str
end
@ -91,7 +76,7 @@ function util.url_encode(str)
end
-- URL decode a string
function util.url_decode(str)
function url_decode(str)
if type(str) ~= "string" then
return str
end
@ -108,7 +93,7 @@ end
-- ======================================================================
-- Email validation
function util.is_email(str)
function is_email(str)
if type(str) ~= "string" then
return false
end
@ -119,7 +104,7 @@ function util.is_email(str)
end
-- URL validation
function util.is_url(str)
function is_url(str)
if type(str) ~= "string" then
return false
end
@ -130,7 +115,7 @@ function util.is_url(str)
end
-- IP address validation (IPv4)
function util.is_ipv4(str)
function is_ipv4(str)
if type(str) ~= "string" then
return false
end
@ -147,7 +132,7 @@ function util.is_ipv4(str)
end
-- Integer validation
function util.is_int(str)
function is_int(str)
if type(str) == "number" then
return math.floor(str) == str
elseif type(str) ~= "string" then
@ -158,7 +143,7 @@ function util.is_int(str)
end
-- Float validation
function util.is_float(str)
function is_float(str)
if type(str) == "number" then
return true
elseif type(str) ~= "string" then
@ -169,7 +154,7 @@ function util.is_float(str)
end
-- Boolean validation
function util.is_bool(value)
function is_bool(value)
if type(value) == "boolean" then
return true
elseif type(value) ~= "string" and type(value) ~= "number" then
@ -183,7 +168,7 @@ function util.is_bool(value)
end
-- Convert to boolean
function util.to_bool(value)
function to_bool(value)
if type(value) == "boolean" then
return value
elseif type(value) ~= "string" and type(value) ~= "number" then
@ -195,16 +180,16 @@ function util.to_bool(value)
end
-- Sanitize string (simple version)
function util.sanitize_string(str)
function sanitize_string(str)
if type(str) ~= "string" then
return ""
end
return util.html_special_chars(str)
return html_special_chars(str)
end
-- Sanitize to integer
function util.sanitize_int(value)
function sanitize_int(value)
if type(value) ~= "string" and type(value) ~= "number" then
return 0
end
@ -215,7 +200,7 @@ function util.sanitize_int(value)
end
-- Sanitize to float
function util.sanitize_float(value)
function sanitize_float(value)
if type(value) ~= "string" and type(value) ~= "number" then
return 0
end
@ -226,7 +211,7 @@ function util.sanitize_float(value)
end
-- Sanitize URL
function util.sanitize_url(str)
function sanitize_url(str)
if type(str) ~= "string" then
return ""
end
@ -235,12 +220,12 @@ function util.sanitize_url(str)
str = str:gsub("[\000-\031]", "")
-- Make sure it's a valid URL
if util.is_url(str) then
if 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
if not str:match("^https?://") and is_url("http://" .. str) then
return "http://" .. str
end
@ -248,7 +233,7 @@ function util.sanitize_url(str)
end
-- Sanitize email
function util.sanitize_email(str)
function sanitize_email(str)
if type(str) ~= "string" then
return ""
end
@ -257,7 +242,7 @@ function util.sanitize_email(str)
str = str:gsub("[^%a%d%!%#%$%%%&%'%*%+%-%/%=%?%^%_%`%{%|%}%~%@%.%[%]]", "")
-- Return only if it's a valid email
if util.is_email(str) then
if is_email(str) then
return str
end
@ -269,13 +254,13 @@ end
-- ======================================================================
-- Basic XSS prevention
function util.xss_clean(str)
function xss_clean(str)
if type(str) ~= "string" then
return str
end
-- Convert problematic characters to entities
local result = util.html_special_chars(str)
local result = html_special_chars(str)
-- Remove JavaScript event handlers
result = result:gsub("on%w+%s*=", "")
@ -290,7 +275,7 @@ function util.xss_clean(str)
end
-- Base64 encode
function util.base64_encode(str)
function base64_encode(str)
if type(str) ~= "string" then
return str
end
@ -299,12 +284,10 @@ function util.base64_encode(str)
end
-- Base64 decode
function util.base64_decode(str)
function base64_decode(str)
if type(str) ~= "string" then
return str
end
return __base64_decode(str)
end
return util

View File

@ -81,6 +81,9 @@ func (s *Session) GetAll() map[string]any {
// Set stores a value in the session
func (s *Session) Set(key string, value any) {
if existing, ok := s.Data[key]; ok && deepEqual(existing, value) {
return // No change
}
s.Data[key] = value
s.UpdatedAt = time.Now()
s.dirty = true
@ -346,3 +349,81 @@ func validate(v any) error {
}
return nil
}
// deepEqual efficiently compares two values for deep equality
func deepEqual(a, b any) bool {
if a == b {
return true
}
if a == nil || b == nil {
return false
}
switch va := a.(type) {
case string:
if vb, ok := b.(string); ok {
return va == vb
}
case int:
if vb, ok := b.(int); ok {
return va == vb
}
if vb, ok := b.(int64); ok {
return int64(va) == vb
}
case int64:
if vb, ok := b.(int64); ok {
return va == vb
}
if vb, ok := b.(int); ok {
return va == int64(vb)
}
case float64:
if vb, ok := b.(float64); ok {
return va == vb
}
case bool:
if vb, ok := b.(bool); ok {
return va == vb
}
case []byte:
if vb, ok := b.([]byte); ok {
if len(va) != len(vb) {
return false
}
for i, v := range va {
if v != vb[i] {
return false
}
}
return true
}
case map[string]any:
if vb, ok := b.(map[string]any); ok {
if len(va) != len(vb) {
return false
}
for k, v := range va {
if bv, exists := vb[k]; !exists || !deepEqual(v, bv) {
return false
}
}
return true
}
case []any:
if vb, ok := b.([]any); ok {
if len(va) != len(vb) {
return false
}
for i, v := range va {
if !deepEqual(v, vb[i]) {
return false
}
}
return true
}
}
return false
}