Moonshark/runner/lua/sandbox.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