refactor lua code into more granular modules
This commit is contained in:
parent
c2be77bf6a
commit
d6e24f0185
@ -40,6 +40,21 @@ var mathLuaCode string
|
||||
//go:embed lua/env.lua
|
||||
var envLuaCode string
|
||||
|
||||
//go:embed lua/http.lua
|
||||
var httpLuaCode string
|
||||
|
||||
//go:embed lua/cookie.lua
|
||||
var cookieLuaCode string
|
||||
|
||||
//go:embed lua/csrf.lua
|
||||
var csrfLuaCode string
|
||||
|
||||
//go:embed lua/render.lua
|
||||
var renderLuaCode string
|
||||
|
||||
//go:embed lua/session.lua
|
||||
var sessionLuaCode string
|
||||
|
||||
// Module represents a Lua module to load
|
||||
type Module struct {
|
||||
name string
|
||||
@ -48,6 +63,11 @@ type Module struct {
|
||||
}
|
||||
|
||||
var modules = []Module{
|
||||
{"http", httpLuaCode, true},
|
||||
{"cookie", cookieLuaCode, true},
|
||||
{"session", sessionLuaCode, true},
|
||||
{"csrf", csrfLuaCode, true},
|
||||
{"render", renderLuaCode, true},
|
||||
{"json", jsonLuaCode, true},
|
||||
{"sqlite", sqliteLuaCode, false},
|
||||
{"fs", fsLuaCode, true},
|
||||
|
26
runner/lua/cookie.lua
Normal file
26
runner/lua/cookie.lua
Normal file
@ -0,0 +1,26 @@
|
||||
-- cookie.lua
|
||||
|
||||
function cookie_set(name, value, options)
|
||||
__response.cookies = __response.cookies or {}
|
||||
local opts = options or {}
|
||||
local cookie = {
|
||||
name = name,
|
||||
value = value or "",
|
||||
path = opts.path or "/",
|
||||
domain = opts.domain,
|
||||
secure = opts.secure ~= false,
|
||||
http_only = opts.http_only ~= false
|
||||
}
|
||||
if opts.expires and opts.expires > 0 then
|
||||
cookie.max_age = opts.expires
|
||||
end
|
||||
table.insert(__response.cookies, cookie)
|
||||
end
|
||||
|
||||
function cookie_get(name)
|
||||
return __ctx._request_cookies and __ctx._request_cookies[name]
|
||||
end
|
||||
|
||||
function cookie_delete(name, path, domain)
|
||||
return cookie_set(name, "", {expires = -1, path = path or "/", domain = domain})
|
||||
end
|
@ -1,6 +1,4 @@
|
||||
--[[
|
||||
crypto.lua - Cryptographic functions powered by Go
|
||||
]]--
|
||||
-- crypto.lua
|
||||
|
||||
-- ======================================================================
|
||||
-- HASHING FUNCTIONS
|
||||
@ -13,10 +11,10 @@ function hash(data, algorithm, format)
|
||||
if type(data) ~= "string" then
|
||||
error("hash: data must be a string", 2)
|
||||
end
|
||||
|
||||
|
||||
algorithm = algorithm or "sha256"
|
||||
format = format or "hex"
|
||||
|
||||
|
||||
return __crypto_hash(data, algorithm, format)
|
||||
end
|
||||
|
||||
@ -47,14 +45,14 @@ function hmac(data, key, algorithm, format)
|
||||
if type(data) ~= "string" then
|
||||
error("hmac: data must be a string", 2)
|
||||
end
|
||||
|
||||
|
||||
if type(key) ~= "string" then
|
||||
error("hmac: key must be a string", 2)
|
||||
end
|
||||
|
||||
|
||||
algorithm = algorithm or "sha256"
|
||||
format = format or "hex"
|
||||
|
||||
|
||||
return __crypto_hmac(data, key, algorithm, format)
|
||||
end
|
||||
|
||||
@ -84,10 +82,10 @@ function random_bytes(length, secure, format)
|
||||
if type(length) ~= "number" or length <= 0 then
|
||||
error("random_bytes: length must be positive", 2)
|
||||
end
|
||||
|
||||
|
||||
secure = secure ~= false -- Default to secure
|
||||
format = format or "binary"
|
||||
|
||||
|
||||
return __crypto_random_bytes(length, secure, format)
|
||||
end
|
||||
|
||||
@ -96,13 +94,13 @@ function random_int(min, max, secure)
|
||||
if type(min) ~= "number" or type(max) ~= "number" then
|
||||
error("random_int: min and max must be numbers", 2)
|
||||
end
|
||||
|
||||
|
||||
if max <= min then
|
||||
error("random_int: max must be greater than min", 2)
|
||||
end
|
||||
|
||||
|
||||
secure = secure ~= false -- Default to secure
|
||||
|
||||
|
||||
return __crypto_random_int(min, max, secure)
|
||||
end
|
||||
|
||||
@ -111,24 +109,24 @@ function random_string(length, charset, secure)
|
||||
if type(length) ~= "number" or length <= 0 then
|
||||
error("random_string: length must be positive", 2)
|
||||
end
|
||||
|
||||
|
||||
secure = secure ~= false -- Default to secure
|
||||
|
||||
|
||||
-- Default character set: alphanumeric
|
||||
charset = charset or "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
|
||||
if type(charset) ~= "string" or #charset == 0 then
|
||||
error("random_string: charset must be non-empty", 2)
|
||||
end
|
||||
|
||||
|
||||
local result = ""
|
||||
local charset_length = #charset
|
||||
|
||||
|
||||
for i = 1, length do
|
||||
local index = random_int(1, charset_length, secure)
|
||||
result = result .. charset:sub(index, index)
|
||||
end
|
||||
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
@ -139,4 +137,4 @@ end
|
||||
-- Generate random UUID (v4)
|
||||
function uuid()
|
||||
return __crypto_uuid()
|
||||
end
|
||||
end
|
||||
|
33
runner/lua/csrf.lua
Normal file
33
runner/lua/csrf.lua
Normal file
@ -0,0 +1,33 @@
|
||||
-- csrf.lua
|
||||
|
||||
function csrf_generate()
|
||||
local token = generate_token(32)
|
||||
session_set("_csrf_token", token)
|
||||
return token
|
||||
end
|
||||
|
||||
function csrf_field()
|
||||
local token = session_get("_csrf_token")
|
||||
if not token then
|
||||
token = csrf_generate()
|
||||
end
|
||||
return string.format('<input type="hidden" name="_csrf_token" value="%s" />',
|
||||
html_special_chars(token))
|
||||
end
|
||||
|
||||
function csrf_validate()
|
||||
local token = __ctx.session and __ctx.session.data and __ctx.session.data["_csrf_token"]
|
||||
if not token then
|
||||
__response.status = 403
|
||||
coroutine.yield("__EXIT__")
|
||||
end
|
||||
|
||||
local request_token = (__ctx._request_form and __ctx._request_form._csrf_token) or
|
||||
(__ctx._request_headers and (__ctx._request_headers["x-csrf-token"] or __ctx._request_headers["csrf-token"]))
|
||||
|
||||
if not request_token or request_token ~= token then
|
||||
__response.status = 403
|
||||
coroutine.yield("__EXIT__")
|
||||
end
|
||||
return true
|
||||
end
|
@ -1,3 +1,5 @@
|
||||
-- fs.lua
|
||||
|
||||
function fs_read(path)
|
||||
if type(path) ~= "string" then
|
||||
error("fs_read: path must be a string", 2)
|
||||
@ -131,4 +133,4 @@ function fs_write_json(path, data, pretty)
|
||||
end
|
||||
|
||||
return fs_write(path, content)
|
||||
end
|
||||
end
|
||||
|
72
runner/lua/http.lua
Normal file
72
runner/lua/http.lua
Normal file
@ -0,0 +1,72 @@
|
||||
-- http.lua
|
||||
|
||||
function http_set_status(code)
|
||||
__response.status = code
|
||||
end
|
||||
|
||||
function http_set_header(name, value)
|
||||
__response.headers = __response.headers or {}
|
||||
__response.headers[name] = value
|
||||
end
|
||||
|
||||
function http_set_content_type(ct)
|
||||
__response.headers = __response.headers or {}
|
||||
__response.headers["Content-Type"] = ct
|
||||
end
|
||||
|
||||
function http_set_metadata(key, value)
|
||||
__response.metadata = __response.metadata or {}
|
||||
__response.metadata[key] = value
|
||||
end
|
||||
|
||||
function http_redirect(url, status)
|
||||
__response.status = status or 302
|
||||
__response.headers = __response.headers or {}
|
||||
__response.headers["Location"] = url
|
||||
coroutine.yield("__EXIT__")
|
||||
end
|
||||
|
||||
function send_html(content)
|
||||
http_set_content_type("text/html")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_json(content)
|
||||
http_set_content_type("application/json")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_text(content)
|
||||
http_set_content_type("text/plain")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_xml(content)
|
||||
http_set_content_type("application/xml")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_javascript(content)
|
||||
http_set_content_type("application/javascript")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_css(content)
|
||||
http_set_content_type("text/css")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_svg(content)
|
||||
http_set_content_type("image/svg+xml")
|
||||
return content
|
||||
end
|
||||
|
||||
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")
|
||||
return content
|
||||
end
|
@ -1,6 +1,5 @@
|
||||
-- json.lua: High-performance JSON module for Moonshark
|
||||
-- json.lua
|
||||
|
||||
-- Pre-computed escape sequences to avoid recreating table
|
||||
local escape_chars = {
|
||||
['"'] = '\\"', ['\\'] = '\\\\',
|
||||
['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t'
|
||||
@ -419,4 +418,4 @@ function json_pretty_print(value)
|
||||
end
|
||||
|
||||
return stringify(value)
|
||||
end
|
||||
end
|
||||
|
@ -1,6 +1,4 @@
|
||||
--[[
|
||||
math.lua - High-performance math library
|
||||
]]--
|
||||
-- math.lua
|
||||
|
||||
local math_ext = {}
|
||||
|
||||
|
190
runner/lua/render.lua
Normal file
190
runner/lua/render.lua
Normal file
@ -0,0 +1,190 @@
|
||||
-- render.lua
|
||||
|
||||
-- Template processing with code execution
|
||||
function render(template_str, env)
|
||||
local function is_control_structure(code)
|
||||
-- Check if code is a control structure that doesn't produce output
|
||||
local trimmed = code:match("^%s*(.-)%s*$")
|
||||
return trimmed == "else" or
|
||||
trimmed == "end" or
|
||||
trimmed:match("^if%s") or
|
||||
trimmed:match("^elseif%s") or
|
||||
trimmed:match("^for%s") or
|
||||
trimmed:match("^while%s") or
|
||||
trimmed:match("^repeat%s*$") or
|
||||
trimmed:match("^until%s") or
|
||||
trimmed:match("^do%s*$") or
|
||||
trimmed:match("^local%s") or
|
||||
trimmed:match("^function%s") or
|
||||
trimmed:match(".*=%s*function%s*%(") or
|
||||
trimmed:match(".*then%s*$") or
|
||||
trimmed:match(".*do%s*$")
|
||||
end
|
||||
|
||||
local pos, chunks = 1, {}
|
||||
while pos <= #template_str do
|
||||
local unescaped_start = template_str:find("{{{", pos, true)
|
||||
local escaped_start = template_str:find("{{", pos, true)
|
||||
|
||||
local start, tag_type, open_len
|
||||
if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then
|
||||
start, tag_type, open_len = unescaped_start, "-", 3
|
||||
elseif escaped_start then
|
||||
start, tag_type, open_len = escaped_start, "=", 2
|
||||
else
|
||||
table.insert(chunks, template_str:sub(pos))
|
||||
break
|
||||
end
|
||||
|
||||
if start > pos then
|
||||
table.insert(chunks, template_str:sub(pos, start-1))
|
||||
end
|
||||
|
||||
pos = start + open_len
|
||||
local close_tag = tag_type == "-" and "}}}" or "}}"
|
||||
local close_start, close_stop = template_str:find(close_tag, pos, true)
|
||||
if not close_start then
|
||||
error("Failed to find closing tag at position " .. pos)
|
||||
end
|
||||
|
||||
local code = template_str:sub(pos, close_start-1):match("^%s*(.-)%s*$")
|
||||
local is_control = is_control_structure(code)
|
||||
|
||||
table.insert(chunks, {tag_type, code, pos, is_control})
|
||||
pos = close_stop + 1
|
||||
end
|
||||
|
||||
local buffer = {"local _tostring, _escape, _b, _b_i = ...\n"}
|
||||
for _, chunk in ipairs(chunks) do
|
||||
local t = type(chunk)
|
||||
if t == "string" then
|
||||
table.insert(buffer, "_b_i = _b_i + 1\n")
|
||||
table.insert(buffer, "_b[_b_i] = " .. string.format("%q", chunk) .. "\n")
|
||||
else
|
||||
local tag_type, code, pos, is_control = chunk[1], chunk[2], chunk[3], chunk[4]
|
||||
|
||||
if is_control then
|
||||
-- Control structure - just insert as raw Lua code
|
||||
table.insert(buffer, "--[[" .. pos .. "]] " .. code .. "\n")
|
||||
elseif tag_type == "=" then
|
||||
-- Simple variable check
|
||||
if code:match("^[%w_]+$") then
|
||||
table.insert(buffer, "_b_i = _b_i + 1\n")
|
||||
table.insert(buffer, "--[[" .. pos .. "]] _b[_b_i] = _escape(_tostring(" .. code .. "))\n")
|
||||
else
|
||||
-- Expression output with escaping
|
||||
table.insert(buffer, "_b_i = _b_i + 1\n")
|
||||
table.insert(buffer, "--[[" .. pos .. "]] _b[_b_i] = _escape(_tostring(" .. code .. "))\n")
|
||||
end
|
||||
elseif tag_type == "-" then
|
||||
-- Unescaped output
|
||||
table.insert(buffer, "_b_i = _b_i + 1\n")
|
||||
table.insert(buffer, "--[[" .. pos .. "]] _b[_b_i] = _tostring(" .. code .. ")\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(buffer, "return _b")
|
||||
|
||||
local generated_code = table.concat(buffer)
|
||||
|
||||
-- DEBUG: Uncomment to see generated code
|
||||
-- print("Generated Lua code:")
|
||||
-- print(generated_code)
|
||||
-- print("---")
|
||||
|
||||
local fn, err = loadstring(generated_code)
|
||||
if not fn then
|
||||
print("Generated code that failed to compile:")
|
||||
print(generated_code)
|
||||
error(err)
|
||||
end
|
||||
|
||||
env = env or {}
|
||||
local runtime_env = setmetatable({}, {__index = function(_, k) return env[k] or _G[k] end})
|
||||
setfenv(fn, runtime_env)
|
||||
|
||||
local output_buffer = {}
|
||||
fn(tostring, html_special_chars, output_buffer, 0)
|
||||
return table.concat(output_buffer)
|
||||
end
|
||||
|
||||
-- Named placeholder processing
|
||||
function parse(template_str, env)
|
||||
local pos, output = 1, {}
|
||||
env = env or {}
|
||||
|
||||
while pos <= #template_str do
|
||||
local unescaped_start, unescaped_end, unescaped_name = template_str:find("{{{%s*([%w_]+)%s*}}}", pos)
|
||||
local escaped_start, escaped_end, escaped_name = template_str:find("{{%s*([%w_]+)%s*}}", pos)
|
||||
|
||||
local next_pos, placeholder_end, name, escaped
|
||||
if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then
|
||||
next_pos, placeholder_end, name, escaped = unescaped_start, unescaped_end, unescaped_name, false
|
||||
elseif escaped_start then
|
||||
next_pos, placeholder_end, name, escaped = escaped_start, escaped_end, escaped_name, true
|
||||
else
|
||||
local text = template_str:sub(pos)
|
||||
if text and #text > 0 then
|
||||
table.insert(output, text)
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
local text = template_str:sub(pos, next_pos - 1)
|
||||
if text and #text > 0 then
|
||||
table.insert(output, text)
|
||||
end
|
||||
|
||||
local value = env[name]
|
||||
local str = tostring(value or "")
|
||||
if escaped then
|
||||
str = html_special_chars(str)
|
||||
end
|
||||
table.insert(output, str)
|
||||
|
||||
pos = placeholder_end + 1
|
||||
end
|
||||
|
||||
return table.concat(output)
|
||||
end
|
||||
|
||||
-- Indexed placeholder processing
|
||||
function iparse(template_str, values)
|
||||
local pos, output, value_index = 1, {}, 1
|
||||
values = values or {}
|
||||
|
||||
while pos <= #template_str do
|
||||
local unescaped_start, unescaped_end = template_str:find("{{{}}}", pos, true)
|
||||
local escaped_start, escaped_end = template_str:find("{{}}", pos, true)
|
||||
|
||||
local next_pos, placeholder_end, escaped
|
||||
if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then
|
||||
next_pos, placeholder_end, escaped = unescaped_start, unescaped_end, false
|
||||
elseif escaped_start then
|
||||
next_pos, placeholder_end, escaped = escaped_start, escaped_end, true
|
||||
else
|
||||
local text = template_str:sub(pos)
|
||||
if text and #text > 0 then
|
||||
table.insert(output, text)
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
local text = template_str:sub(pos, next_pos - 1)
|
||||
if text and #text > 0 then
|
||||
table.insert(output, text)
|
||||
end
|
||||
|
||||
local value = values[value_index]
|
||||
local str = tostring(value or "")
|
||||
if escaped then
|
||||
str = html_special_chars(str)
|
||||
end
|
||||
table.insert(output, str)
|
||||
|
||||
pos = placeholder_end + 1
|
||||
value_index = value_index + 1
|
||||
end
|
||||
|
||||
return table.concat(output)
|
||||
end
|
@ -34,559 +34,3 @@ end
|
||||
function exit()
|
||||
coroutine.yield("__EXIT__")
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- HTTP RESPONSE FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
function http_set_status(code)
|
||||
__response.status = code
|
||||
end
|
||||
|
||||
function http_set_header(name, value)
|
||||
__response.headers = __response.headers or {}
|
||||
__response.headers[name] = value
|
||||
end
|
||||
|
||||
function http_set_content_type(ct)
|
||||
__response.headers = __response.headers or {}
|
||||
__response.headers["Content-Type"] = ct
|
||||
end
|
||||
|
||||
function http_set_metadata(key, value)
|
||||
__response.metadata = __response.metadata or {}
|
||||
__response.metadata[key] = value
|
||||
end
|
||||
|
||||
function http_redirect(url, status)
|
||||
__response.status = status or 302
|
||||
__response.headers = __response.headers or {}
|
||||
__response.headers["Location"] = url
|
||||
coroutine.yield("__EXIT__")
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- COOKIE FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
function cookie_set(name, value, options)
|
||||
__response.cookies = __response.cookies or {}
|
||||
local opts = options or {}
|
||||
local cookie = {
|
||||
name = name,
|
||||
value = value or "",
|
||||
path = opts.path or "/",
|
||||
domain = opts.domain,
|
||||
secure = opts.secure ~= false,
|
||||
http_only = opts.http_only ~= false
|
||||
}
|
||||
if opts.expires and opts.expires > 0 then
|
||||
cookie.max_age = opts.expires
|
||||
end
|
||||
table.insert(__response.cookies, cookie)
|
||||
end
|
||||
|
||||
function cookie_get(name)
|
||||
return __ctx._request_cookies and __ctx._request_cookies[name]
|
||||
end
|
||||
|
||||
function cookie_delete(name, path, domain)
|
||||
return cookie_set(name, "", {expires = -1, path = path or "/", domain = domain})
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- SESSION FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
function session_set(key, value)
|
||||
__response.session = __response.session or {}
|
||||
__response.session[key] = value
|
||||
if __ctx.session and __ctx.session.data then
|
||||
__ctx.session.data[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
function session_get(key)
|
||||
return __ctx.session and __ctx.session.data and __ctx.session.data[key]
|
||||
end
|
||||
|
||||
function session_id()
|
||||
return __ctx.session and __ctx.session.id
|
||||
end
|
||||
|
||||
function session_get_all()
|
||||
if __ctx.session and __ctx.session.data then
|
||||
local copy = {}
|
||||
for k, v in pairs(__ctx.session.data) do
|
||||
copy[k] = v
|
||||
end
|
||||
return copy
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
function session_delete(key)
|
||||
__response.session = __response.session or {}
|
||||
__response.session[key] = "__DELETE__"
|
||||
if __ctx.session and __ctx.session.data then
|
||||
__ctx.session.data[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function session_clear()
|
||||
__response.session = {__clear_all = true}
|
||||
if __ctx.session and __ctx.session.data then
|
||||
for k in pairs(__ctx.session.data) do
|
||||
__ctx.session.data[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- CSRF FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
function csrf_generate()
|
||||
local token = generate_token(32)
|
||||
session_set("_csrf_token", token)
|
||||
return token
|
||||
end
|
||||
|
||||
function csrf_field()
|
||||
local token = session_get("_csrf_token")
|
||||
if not token then
|
||||
token = csrf_generate()
|
||||
end
|
||||
return string.format('<input type="hidden" name="_csrf_token" value="%s" />',
|
||||
html_special_chars(token))
|
||||
end
|
||||
|
||||
function csrf_validate()
|
||||
local token = __ctx.session and __ctx.session.data and __ctx.session.data["_csrf_token"]
|
||||
if not token then
|
||||
__response.status = 403
|
||||
coroutine.yield("__EXIT__")
|
||||
end
|
||||
|
||||
local request_token = (__ctx._request_form and __ctx._request_form._csrf_token) or
|
||||
(__ctx._request_headers and (__ctx._request_headers["x-csrf-token"] or __ctx._request_headers["csrf-token"]))
|
||||
|
||||
if not request_token or request_token ~= token then
|
||||
__response.status = 403
|
||||
coroutine.yield("__EXIT__")
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- CONTENT TYPE HELPERS
|
||||
-- ======================================================================
|
||||
|
||||
function send_html(content)
|
||||
http_set_content_type("text/html")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_json(content)
|
||||
http_set_content_type("application/json")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_text(content)
|
||||
http_set_content_type("text/plain")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_xml(content)
|
||||
http_set_content_type("application/xml")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_javascript(content)
|
||||
http_set_content_type("application/javascript")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_css(content)
|
||||
http_set_content_type("text/css")
|
||||
return content
|
||||
end
|
||||
|
||||
function send_svg(content)
|
||||
http_set_content_type("image/svg+xml")
|
||||
return content
|
||||
end
|
||||
|
||||
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")
|
||||
return content
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- TEMPLATE RENDER FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
-- Template processing with code execution
|
||||
function render(template_str, env)
|
||||
local function is_control_structure(code)
|
||||
-- Check if code is a control structure that doesn't produce output
|
||||
local trimmed = code:match("^%s*(.-)%s*$")
|
||||
return trimmed == "else" or
|
||||
trimmed == "end" or
|
||||
trimmed:match("^if%s") or
|
||||
trimmed:match("^elseif%s") or
|
||||
trimmed:match("^for%s") or
|
||||
trimmed:match("^while%s") or
|
||||
trimmed:match("^repeat%s*$") or
|
||||
trimmed:match("^until%s") or
|
||||
trimmed:match("^do%s*$") or
|
||||
trimmed:match("^local%s") or
|
||||
trimmed:match("^function%s") or
|
||||
trimmed:match(".*=%s*function%s*%(") or
|
||||
trimmed:match(".*then%s*$") or
|
||||
trimmed:match(".*do%s*$")
|
||||
end
|
||||
|
||||
local pos, chunks = 1, {}
|
||||
while pos <= #template_str do
|
||||
local unescaped_start = template_str:find("{{{", pos, true)
|
||||
local escaped_start = template_str:find("{{", pos, true)
|
||||
|
||||
local start, tag_type, open_len
|
||||
if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then
|
||||
start, tag_type, open_len = unescaped_start, "-", 3
|
||||
elseif escaped_start then
|
||||
start, tag_type, open_len = escaped_start, "=", 2
|
||||
else
|
||||
table.insert(chunks, template_str:sub(pos))
|
||||
break
|
||||
end
|
||||
|
||||
if start > pos then
|
||||
table.insert(chunks, template_str:sub(pos, start-1))
|
||||
end
|
||||
|
||||
pos = start + open_len
|
||||
local close_tag = tag_type == "-" and "}}}" or "}}"
|
||||
local close_start, close_stop = template_str:find(close_tag, pos, true)
|
||||
if not close_start then
|
||||
error("Failed to find closing tag at position " .. pos)
|
||||
end
|
||||
|
||||
local code = template_str:sub(pos, close_start-1):match("^%s*(.-)%s*$")
|
||||
local is_control = is_control_structure(code)
|
||||
|
||||
table.insert(chunks, {tag_type, code, pos, is_control})
|
||||
pos = close_stop + 1
|
||||
end
|
||||
|
||||
local buffer = {"local _tostring, _escape, _b, _b_i = ...\n"}
|
||||
for _, chunk in ipairs(chunks) do
|
||||
local t = type(chunk)
|
||||
if t == "string" then
|
||||
table.insert(buffer, "_b_i = _b_i + 1\n")
|
||||
table.insert(buffer, "_b[_b_i] = " .. string.format("%q", chunk) .. "\n")
|
||||
else
|
||||
local tag_type, code, pos, is_control = chunk[1], chunk[2], chunk[3], chunk[4]
|
||||
|
||||
if is_control then
|
||||
-- Control structure - just insert as raw Lua code
|
||||
table.insert(buffer, "--[[" .. pos .. "]] " .. code .. "\n")
|
||||
elseif tag_type == "=" then
|
||||
-- Simple variable check
|
||||
if code:match("^[%w_]+$") then
|
||||
table.insert(buffer, "_b_i = _b_i + 1\n")
|
||||
table.insert(buffer, "--[[" .. pos .. "]] _b[_b_i] = _escape(_tostring(" .. code .. "))\n")
|
||||
else
|
||||
-- Expression output with escaping
|
||||
table.insert(buffer, "_b_i = _b_i + 1\n")
|
||||
table.insert(buffer, "--[[" .. pos .. "]] _b[_b_i] = _escape(_tostring(" .. code .. "))\n")
|
||||
end
|
||||
elseif tag_type == "-" then
|
||||
-- Unescaped output
|
||||
table.insert(buffer, "_b_i = _b_i + 1\n")
|
||||
table.insert(buffer, "--[[" .. pos .. "]] _b[_b_i] = _tostring(" .. code .. ")\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(buffer, "return _b")
|
||||
|
||||
local generated_code = table.concat(buffer)
|
||||
|
||||
-- DEBUG: Uncomment to see generated code
|
||||
-- print("Generated Lua code:")
|
||||
-- print(generated_code)
|
||||
-- print("---")
|
||||
|
||||
local fn, err = loadstring(generated_code)
|
||||
if not fn then
|
||||
print("Generated code that failed to compile:")
|
||||
print(generated_code)
|
||||
error(err)
|
||||
end
|
||||
|
||||
env = env or {}
|
||||
local runtime_env = setmetatable({}, {__index = function(_, k) return env[k] or _G[k] end})
|
||||
setfenv(fn, runtime_env)
|
||||
|
||||
local output_buffer = {}
|
||||
fn(tostring, html_special_chars, output_buffer, 0)
|
||||
return table.concat(output_buffer)
|
||||
end
|
||||
|
||||
-- Named placeholder processing
|
||||
function parse(template_str, env)
|
||||
local pos, output = 1, {}
|
||||
env = env or {}
|
||||
|
||||
while pos <= #template_str do
|
||||
local unescaped_start, unescaped_end, unescaped_name = template_str:find("{{{%s*([%w_]+)%s*}}}", pos)
|
||||
local escaped_start, escaped_end, escaped_name = template_str:find("{{%s*([%w_]+)%s*}}", pos)
|
||||
|
||||
local next_pos, placeholder_end, name, escaped
|
||||
if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then
|
||||
next_pos, placeholder_end, name, escaped = unescaped_start, unescaped_end, unescaped_name, false
|
||||
elseif escaped_start then
|
||||
next_pos, placeholder_end, name, escaped = escaped_start, escaped_end, escaped_name, true
|
||||
else
|
||||
local text = template_str:sub(pos)
|
||||
if text and #text > 0 then
|
||||
table.insert(output, text)
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
local text = template_str:sub(pos, next_pos - 1)
|
||||
if text and #text > 0 then
|
||||
table.insert(output, text)
|
||||
end
|
||||
|
||||
local value = env[name]
|
||||
local str = tostring(value or "")
|
||||
if escaped then
|
||||
str = html_special_chars(str)
|
||||
end
|
||||
table.insert(output, str)
|
||||
|
||||
pos = placeholder_end + 1
|
||||
end
|
||||
|
||||
return table.concat(output)
|
||||
end
|
||||
|
||||
-- Indexed placeholder processing
|
||||
function iparse(template_str, values)
|
||||
local pos, output, value_index = 1, {}, 1
|
||||
values = values or {}
|
||||
|
||||
while pos <= #template_str do
|
||||
local unescaped_start, unescaped_end = template_str:find("{{{}}}", pos, true)
|
||||
local escaped_start, escaped_end = template_str:find("{{}}", pos, true)
|
||||
|
||||
local next_pos, placeholder_end, escaped
|
||||
if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then
|
||||
next_pos, placeholder_end, escaped = unescaped_start, unescaped_end, false
|
||||
elseif escaped_start then
|
||||
next_pos, placeholder_end, escaped = escaped_start, escaped_end, true
|
||||
else
|
||||
local text = template_str:sub(pos)
|
||||
if text and #text > 0 then
|
||||
table.insert(output, text)
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
local text = template_str:sub(pos, next_pos - 1)
|
||||
if text and #text > 0 then
|
||||
table.insert(output, text)
|
||||
end
|
||||
|
||||
local value = values[value_index]
|
||||
local str = tostring(value or "")
|
||||
if escaped then
|
||||
str = html_special_chars(str)
|
||||
end
|
||||
table.insert(output, str)
|
||||
|
||||
pos = placeholder_end + 1
|
||||
value_index = value_index + 1
|
||||
end
|
||||
|
||||
return table.concat(output)
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- PASSWORD FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
-- Hash a password using Argon2id
|
||||
-- Options:
|
||||
-- memory: Amount of memory to use in KB (default: 128MB)
|
||||
-- iterations: Number of iterations (default: 4)
|
||||
-- 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)
|
||||
if type(plain_password) ~= "string" then
|
||||
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)
|
||||
if type(plain_password) ~= "string" then
|
||||
error("password_verify: expected string password", 2)
|
||||
end
|
||||
|
||||
if type(hash_string) ~= "string" then
|
||||
error("password_verify: expected string hash", 2)
|
||||
end
|
||||
|
||||
return __password_verify(plain_password, hash_string)
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- SESSION FLASH FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
function session_flash(key, value)
|
||||
__response.flash = __response.flash or {}
|
||||
__response.flash[key] = value
|
||||
end
|
||||
|
||||
function session_get_flash(key)
|
||||
-- Check current flash data first
|
||||
if __response.flash and __response.flash[key] ~= nil then
|
||||
return __response.flash[key]
|
||||
end
|
||||
|
||||
-- Check session flash data
|
||||
if __ctx.session and __ctx.session.flash and __ctx.session.flash[key] ~= nil then
|
||||
return __ctx.session.flash[key]
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function session_has_flash(key)
|
||||
-- Check current flash
|
||||
if __response.flash and __response.flash[key] ~= nil then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Check session flash
|
||||
if __ctx.session and __ctx.session.flash and __ctx.session.flash[key] ~= nil then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function session_get_all_flash()
|
||||
local flash = {}
|
||||
|
||||
-- Add session flash data first
|
||||
if __ctx.session and __ctx.session.flash then
|
||||
for k, v in pairs(__ctx.session.flash) do
|
||||
flash[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
-- Add current response flash (overwrites session flash if same key)
|
||||
if __response.flash then
|
||||
for k, v in pairs(__response.flash) do
|
||||
flash[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return flash
|
||||
end
|
||||
|
||||
function session_flash_now(key, value)
|
||||
-- Flash for current request only (not persisted)
|
||||
_G._current_flash = _G._current_flash or {}
|
||||
_G._current_flash[key] = value
|
||||
end
|
||||
|
||||
function session_get_flash_now(key)
|
||||
return _G._current_flash and _G._current_flash[key]
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- FLASH HELPER FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
function flash_success(message)
|
||||
session_flash("success", message)
|
||||
end
|
||||
|
||||
function flash_error(message)
|
||||
session_flash("error", message)
|
||||
end
|
||||
|
||||
function flash_warning(message)
|
||||
session_flash("warning", message)
|
||||
end
|
||||
|
||||
function flash_info(message)
|
||||
session_flash("info", message)
|
||||
end
|
||||
|
||||
function flash_message(type, message)
|
||||
session_flash(type, message)
|
||||
end
|
||||
|
||||
-- Get flash messages by type
|
||||
function get_flash_success()
|
||||
return session_get_flash("success")
|
||||
end
|
||||
|
||||
function get_flash_error()
|
||||
return session_get_flash("error")
|
||||
end
|
||||
|
||||
function get_flash_warning()
|
||||
return session_get_flash("warning")
|
||||
end
|
||||
|
||||
function get_flash_info()
|
||||
return session_get_flash("info")
|
||||
end
|
||||
|
||||
-- Check if flash messages exist
|
||||
function has_flash_success()
|
||||
return session_has_flash("success")
|
||||
end
|
||||
|
||||
function has_flash_error()
|
||||
return session_has_flash("error")
|
||||
end
|
||||
|
||||
function has_flash_warning()
|
||||
return session_has_flash("warning")
|
||||
end
|
||||
|
||||
function has_flash_info()
|
||||
return session_has_flash("info")
|
||||
end
|
||||
|
||||
-- Convenience function for redirects with flash
|
||||
function redirect_with_flash(url, type, message, status)
|
||||
session_flash(type or "info", message)
|
||||
http_redirect(url, status)
|
||||
end
|
||||
|
||||
function redirect_with_success(url, message, status)
|
||||
redirect_with_flash(url, "success", message, status)
|
||||
end
|
||||
|
||||
function redirect_with_error(url, message, status)
|
||||
redirect_with_flash(url, "error", message, status)
|
||||
end
|
||||
|
179
runner/lua/session.lua
Normal file
179
runner/lua/session.lua
Normal file
@ -0,0 +1,179 @@
|
||||
-- session.lua
|
||||
|
||||
function session_set(key, value)
|
||||
__response.session = __response.session or {}
|
||||
__response.session[key] = value
|
||||
if __ctx.session and __ctx.session.data then
|
||||
__ctx.session.data[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
function session_get(key)
|
||||
return __ctx.session and __ctx.session.data and __ctx.session.data[key]
|
||||
end
|
||||
|
||||
function session_id()
|
||||
return __ctx.session and __ctx.session.id
|
||||
end
|
||||
|
||||
function session_get_all()
|
||||
if __ctx.session and __ctx.session.data then
|
||||
local copy = {}
|
||||
for k, v in pairs(__ctx.session.data) do
|
||||
copy[k] = v
|
||||
end
|
||||
return copy
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
function session_delete(key)
|
||||
__response.session = __response.session or {}
|
||||
__response.session[key] = "__DELETE__"
|
||||
if __ctx.session and __ctx.session.data then
|
||||
__ctx.session.data[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function session_clear()
|
||||
__response.session = {__clear_all = true}
|
||||
if __ctx.session and __ctx.session.data then
|
||||
for k in pairs(__ctx.session.data) do
|
||||
__ctx.session.data[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function session_flash(key, value)
|
||||
__response.flash = __response.flash or {}
|
||||
__response.flash[key] = value
|
||||
end
|
||||
|
||||
function session_get_flash(key)
|
||||
-- Check current flash data first
|
||||
if __response.flash and __response.flash[key] ~= nil then
|
||||
return __response.flash[key]
|
||||
end
|
||||
|
||||
-- Check session flash data
|
||||
if __ctx.session and __ctx.session.flash and __ctx.session.flash[key] ~= nil then
|
||||
return __ctx.session.flash[key]
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function session_has_flash(key)
|
||||
-- Check current flash
|
||||
if __response.flash and __response.flash[key] ~= nil then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Check session flash
|
||||
if __ctx.session and __ctx.session.flash and __ctx.session.flash[key] ~= nil then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function session_get_all_flash()
|
||||
local flash = {}
|
||||
|
||||
-- Add session flash data first
|
||||
if __ctx.session and __ctx.session.flash then
|
||||
for k, v in pairs(__ctx.session.flash) do
|
||||
flash[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
-- Add current response flash (overwrites session flash if same key)
|
||||
if __response.flash then
|
||||
for k, v in pairs(__response.flash) do
|
||||
flash[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return flash
|
||||
end
|
||||
|
||||
function session_flash_now(key, value)
|
||||
-- Flash for current request only (not persisted)
|
||||
_G._current_flash = _G._current_flash or {}
|
||||
_G._current_flash[key] = value
|
||||
end
|
||||
|
||||
function session_get_flash_now(key)
|
||||
return _G._current_flash and _G._current_flash[key]
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- FLASH HELPER FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
function flash_success(message)
|
||||
session_flash("success", message)
|
||||
end
|
||||
|
||||
function flash_error(message)
|
||||
session_flash("error", message)
|
||||
end
|
||||
|
||||
function flash_warning(message)
|
||||
session_flash("warning", message)
|
||||
end
|
||||
|
||||
function flash_info(message)
|
||||
session_flash("info", message)
|
||||
end
|
||||
|
||||
function flash_message(type, message)
|
||||
session_flash(type, message)
|
||||
end
|
||||
|
||||
-- Get flash messages by type
|
||||
function get_flash_success()
|
||||
return session_get_flash("success")
|
||||
end
|
||||
|
||||
function get_flash_error()
|
||||
return session_get_flash("error")
|
||||
end
|
||||
|
||||
function get_flash_warning()
|
||||
return session_get_flash("warning")
|
||||
end
|
||||
|
||||
function get_flash_info()
|
||||
return session_get_flash("info")
|
||||
end
|
||||
|
||||
-- Check if flash messages exist
|
||||
function has_flash_success()
|
||||
return session_has_flash("success")
|
||||
end
|
||||
|
||||
function has_flash_error()
|
||||
return session_has_flash("error")
|
||||
end
|
||||
|
||||
function has_flash_warning()
|
||||
return session_has_flash("warning")
|
||||
end
|
||||
|
||||
function has_flash_info()
|
||||
return session_has_flash("info")
|
||||
end
|
||||
|
||||
function redirect_with_flash(url, type, message, status)
|
||||
session_flash(type or "info", message)
|
||||
http_redirect(url, status)
|
||||
end
|
||||
|
||||
function redirect_with_success(url, message, status)
|
||||
redirect_with_flash(url, "success", message, status)
|
||||
end
|
||||
|
||||
function redirect_with_error(url, message, status)
|
||||
redirect_with_flash(url, "error", message, status)
|
||||
end
|
@ -1,3 +1,5 @@
|
||||
-- sqlite.lua
|
||||
|
||||
local function normalize_params(params, ...)
|
||||
if type(params) == "table" then return params end
|
||||
local args = {...}
|
||||
|
@ -1,6 +1,4 @@
|
||||
--[[
|
||||
string.lua - Extended string library functions
|
||||
]]--
|
||||
-- string.lua
|
||||
|
||||
local string_ext = {}
|
||||
|
||||
@ -17,7 +15,7 @@ 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
|
||||
@ -41,39 +39,39 @@ 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)
|
||||
@ -93,7 +91,7 @@ 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)
|
||||
@ -102,7 +100,7 @@ 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
|
||||
@ -110,20 +108,20 @@ 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
|
||||
|
||||
@ -142,47 +140,47 @@ 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
|
||||
|
||||
@ -194,4 +192,4 @@ for name, func in pairs(string) do
|
||||
string_ext[name] = func
|
||||
end
|
||||
|
||||
return string_ext
|
||||
return string_ext
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,4 @@
|
||||
--[[
|
||||
time.lua - High performance timing functions
|
||||
]]--
|
||||
-- time.lua
|
||||
|
||||
local ffi = require('ffi')
|
||||
local is_windows = (ffi.os == "Windows")
|
||||
|
@ -1,6 +1,4 @@
|
||||
--[[
|
||||
util.lua - Utility functions for the Lua sandbox
|
||||
]]--
|
||||
-- util.lua
|
||||
|
||||
-- ======================================================================
|
||||
-- CORE UTILITY FUNCTIONS
|
||||
@ -20,7 +18,7 @@ function html_special_chars(str)
|
||||
if type(str) ~= "string" then
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
return __html_special_chars(str)
|
||||
end
|
||||
|
||||
@ -29,7 +27,7 @@ function html_entities(str)
|
||||
if type(str) ~= "string" then
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
return __html_entities(str)
|
||||
end
|
||||
|
||||
@ -38,13 +36,13 @@ function 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
|
||||
|
||||
@ -53,7 +51,7 @@ function nl2br(str)
|
||||
if type(str) ~= "string" then
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
return str:gsub("\r\n", "<br>"):gsub("\n", "<br>"):gsub("\r", "<br>")
|
||||
end
|
||||
|
||||
@ -66,7 +64,7 @@ function 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))
|
||||
@ -80,7 +78,7 @@ function 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))
|
||||
@ -97,7 +95,7 @@ function 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
|
||||
@ -108,7 +106,7 @@ function is_url(str)
|
||||
if type(str) ~= "string" then
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- Simple URL validation
|
||||
local pattern = "^https?://[%w-_%.%?%.:/%+=&%%]+$"
|
||||
return str:match(pattern) ~= nil
|
||||
@ -119,14 +117,14 @@ function 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
|
||||
@ -138,7 +136,7 @@ function is_int(str)
|
||||
elseif type(str) ~= "string" then
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
return str:match("^-?%d+$") ~= nil
|
||||
end
|
||||
|
||||
@ -149,7 +147,7 @@ function is_float(str)
|
||||
elseif type(str) ~= "string" then
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
return str:match("^-?%d+%.?%d*$") ~= nil
|
||||
end
|
||||
|
||||
@ -160,7 +158,7 @@ function is_bool(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 == "0" or v == "false" or v == "off" or v == "no" or
|
||||
@ -174,7 +172,7 @@ function to_bool(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
|
||||
@ -184,7 +182,7 @@ function sanitize_string(str)
|
||||
if type(str) ~= "string" then
|
||||
return ""
|
||||
end
|
||||
|
||||
|
||||
return html_special_chars(str)
|
||||
end
|
||||
|
||||
@ -193,7 +191,7 @@ function 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
|
||||
@ -204,7 +202,7 @@ function 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
|
||||
@ -215,20 +213,20 @@ function 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 is_url(str) then
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
-- Try to prepend http:// if it's missing
|
||||
if not str:match("^https?://") and is_url("http://" .. str) then
|
||||
return "http://" .. str
|
||||
end
|
||||
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
@ -237,15 +235,15 @@ function 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 is_email(str) then
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
@ -258,19 +256,19 @@ function xss_clean(str)
|
||||
if type(str) ~= "string" then
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
-- Convert problematic characters to entities
|
||||
local result = 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
|
||||
|
||||
@ -279,7 +277,7 @@ function base64_encode(str)
|
||||
if type(str) ~= "string" then
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
return __base64_encode(str)
|
||||
end
|
||||
|
||||
@ -288,6 +286,38 @@ function base64_decode(str)
|
||||
if type(str) ~= "string" then
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
return __base64_decode(str)
|
||||
end
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- PASSWORD FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
-- Hash a password using Argon2id
|
||||
-- Options:
|
||||
-- memory: Amount of memory to use in KB (default: 128MB)
|
||||
-- iterations: Number of iterations (default: 4)
|
||||
-- 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)
|
||||
if type(plain_password) ~= "string" then
|
||||
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)
|
||||
if type(plain_password) ~= "string" then
|
||||
error("password_verify: expected string password", 2)
|
||||
end
|
||||
|
||||
if type(hash_string) ~= "string" then
|
||||
error("password_verify: expected string hash", 2)
|
||||
end
|
||||
|
||||
return __password_verify(plain_password, hash_string)
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user