244 lines
6.5 KiB
Lua
244 lines
6.5 KiB
Lua
-- Simplified sandbox.lua - Direct execution with explicit state passing
|
|
|
|
-- Main execution wrapper - receives script, context, and response object
|
|
function __execute(script_func, ctx, response)
|
|
-- Create clean environment
|
|
local env = setmetatable({}, {__index = _G})
|
|
|
|
-- Direct context and response access
|
|
env.ctx = ctx
|
|
env.response = response
|
|
|
|
-- Exit sentinel
|
|
env.exit = function() error("__EXIT__") end
|
|
|
|
-- ======================================================================
|
|
-- HTTP FUNCTIONS - Modify response directly
|
|
-- ======================================================================
|
|
|
|
env.http_set_status = function(code)
|
|
if type(code) ~= "number" then
|
|
error("http_set_status: status code must be a number", 2)
|
|
end
|
|
response.status = code
|
|
end
|
|
|
|
env.http_set_header = function(name, value)
|
|
if type(name) ~= "string" or type(value) ~= "string" then
|
|
error("http_set_header: name and value must be strings", 2)
|
|
end
|
|
response.headers = response.headers or {}
|
|
response.headers[name] = value
|
|
end
|
|
|
|
env.http_set_content_type = function(ct)
|
|
env.http_set_header("Content-Type", ct)
|
|
end
|
|
|
|
env.http_set_metadata = function(key, value)
|
|
if type(key) ~= "string" then
|
|
error("http_set_metadata: key must be a string", 2)
|
|
end
|
|
response.metadata = response.metadata or {}
|
|
response.metadata[key] = value
|
|
end
|
|
|
|
env.http_redirect = function(url, status)
|
|
if type(url) ~= "string" then
|
|
error("http_redirect: url must be a string", 2)
|
|
end
|
|
response.status = status or 302
|
|
response.headers = response.headers or {}
|
|
response.headers["Location"] = url
|
|
env.exit()
|
|
end
|
|
|
|
-- HTTP request functions (use native implementation)
|
|
env.http_request = __http_request
|
|
|
|
local function make_method(method, needs_body)
|
|
return function(url, body_or_options, options)
|
|
if needs_body then
|
|
options = options or {}
|
|
return env.http_request(method, url, body_or_options, options)
|
|
else
|
|
body_or_options = body_or_options or {}
|
|
return env.http_request(method, url, nil, body_or_options)
|
|
end
|
|
end
|
|
end
|
|
|
|
env.http_get = make_method("GET", false)
|
|
env.http_post = make_method("POST", true)
|
|
env.http_put = make_method("PUT", true)
|
|
env.http_patch = make_method("PATCH", true)
|
|
env.http_delete = make_method("DELETE", false)
|
|
env.http_head = make_method("HEAD", false)
|
|
env.http_options = make_method("OPTIONS", false)
|
|
|
|
-- ======================================================================
|
|
-- COOKIE FUNCTIONS - Direct access to ctx and response
|
|
-- ======================================================================
|
|
|
|
env.cookie_set = function(name, value, options)
|
|
if type(name) ~= "string" then
|
|
error("cookie_set: name must be a string", 2)
|
|
end
|
|
|
|
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,
|
|
same_site = opts.same_site or "Lax"
|
|
}
|
|
|
|
if opts.expires then
|
|
if type(opts.expires) == "number" and opts.expires > 0 then
|
|
cookie.max_age = opts.expires
|
|
end
|
|
end
|
|
|
|
table.insert(response.cookies, cookie)
|
|
return true
|
|
end
|
|
|
|
env.cookie_get = function(name)
|
|
if type(name) ~= "string" then
|
|
error("cookie_get: name must be a string", 2)
|
|
end
|
|
return ctx.cookies and ctx.cookies[name]
|
|
end
|
|
|
|
env.cookie_remove = function(name, path, domain)
|
|
return env.cookie_set(name, "", {expires = -1, path = path or "/", domain = domain})
|
|
end
|
|
|
|
-- ======================================================================
|
|
-- SESSION FUNCTIONS - Direct access to ctx and response
|
|
-- ======================================================================
|
|
|
|
env.session_get = function(key)
|
|
if type(key) ~= "string" then
|
|
error("session_get: key must be a string", 2)
|
|
end
|
|
return ctx.session and ctx.session.data and ctx.session.data[key]
|
|
end
|
|
|
|
env.session_set = function(key, value)
|
|
if type(key) ~= "string" then
|
|
error("session_set: key must be a string", 2)
|
|
end
|
|
response.session = response.session or {}
|
|
response.session[key] = value
|
|
-- Update context if available
|
|
if ctx.session and ctx.session.data then
|
|
ctx.session.data[key] = value
|
|
end
|
|
end
|
|
|
|
env.session_id = function()
|
|
return ctx.session and ctx.session.id
|
|
end
|
|
|
|
env.session_get_all = function()
|
|
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
|
|
|
|
env.session_delete = function(key)
|
|
if type(key) ~= "string" then
|
|
error("session_delete: key must be a string", 2)
|
|
end
|
|
response.session = response.session or {}
|
|
response.session[key] = "__DELETE__"
|
|
if ctx.session and ctx.session.data then
|
|
ctx.session.data[key] = nil
|
|
end
|
|
end
|
|
|
|
env.session_clear = function()
|
|
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
|
|
|
|
-- ======================================================================
|
|
-- CONTENT TYPE HELPERS
|
|
-- ======================================================================
|
|
|
|
env.send_html = function(content)
|
|
env.http_set_content_type("text/html")
|
|
return content
|
|
end
|
|
|
|
env.send_json = function(content)
|
|
env.http_set_content_type("application/json")
|
|
return content
|
|
end
|
|
|
|
env.send_text = function(content)
|
|
env.http_set_content_type("text/plain")
|
|
return content
|
|
end
|
|
|
|
-- ======================================================================
|
|
-- NATIVE FUNCTIONS (injected by Go)
|
|
-- ======================================================================
|
|
|
|
-- Copy over native functions
|
|
local natives = {
|
|
"__password_hash", "__password_verify",
|
|
"__sqlite_open", "__sqlite_exec", "__sqlite_query", "__sqlite_close",
|
|
"__fs_read", "__fs_write", "__fs_exists", "__fs_delete",
|
|
"generate_token", "url_encode", "url_decode",
|
|
"html_special_chars", "html_entities",
|
|
"base64_encode", "base64_decode",
|
|
"json_encode", "json_decode",
|
|
"sha256", "md5", "hmac",
|
|
"env_get", "env_set"
|
|
}
|
|
|
|
for _, name in ipairs(natives) do
|
|
if _G[name] then
|
|
env[name:gsub("^__", "")] = _G[name]
|
|
end
|
|
end
|
|
|
|
-- Template functions
|
|
env.render = _G.render
|
|
env.parse = _G.parse
|
|
env.iparse = _G.iparse
|
|
|
|
-- Module support
|
|
if __setup_require then
|
|
__setup_require(env)
|
|
end
|
|
|
|
-- Set function environment and execute
|
|
setfenv(script_func, env)
|
|
|
|
local ok, result = pcall(script_func)
|
|
if not ok then
|
|
if result == "__EXIT__" then
|
|
return nil
|
|
end
|
|
error(result, 0)
|
|
end
|
|
|
|
return result
|
|
end |