--[[ Moonshark Lua Sandbox Environment This file contains all the Lua code needed for the sandbox environment, including core modules and utilities. It's designed to be embedded in the Go binary at build time. ]]-- __http_response = {} __module_paths = {} __module_bytecode = {} __ready_modules = {} __EXIT_SENTINEL = {} -- Unique object for exit identification -- ====================================================================== -- CORE SANDBOX FUNCTIONALITY -- ====================================================================== function exit() error(__EXIT_SENTINEL) end -- Create environment inheriting from _G function __create_env(ctx) local env = setmetatable({}, {__index = _G}) if ctx then env.ctx = ctx end if __setup_require then __setup_require(env) end return env end -- Execute script with clean environment function __execute_script(fn, ctx) __http_response = nil local env = __create_env(ctx) env.exit = exit setfenv(fn, env) local ok, result = pcall(fn) if not ok then if result == __EXIT_SENTINEL then return end error(result, 0) end return result end -- Ensure __http_response exists, then return it function __ensure_response() if not __http_response then __http_response = {} end return __http_response end -- ====================================================================== -- HTTP MODULE -- ====================================================================== local http = { -- Set HTTP status code set_status = function(code) if type(code) ~= "number" then error("http.set_status: status code must be a number", 2) end local resp = __ensure_response() resp.status = code end, -- Set HTTP header 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 local resp = __ensure_response() resp.headers = resp.headers or {} resp.headers[name] = value end, -- Set content type; set_header helper set_content_type = function(content_type) http.set_header("Content-Type", content_type) end, -- Set metadata (arbitrary data to be returned with response) set_metadata = function(key, value) if type(key) ~= "string" then 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, -- HTTP client submodule client = { -- Generic request function request = function(method, url, body, options) if type(method) ~= "string" then error("http.client.request: method must be a string", 2) end if type(url) ~= "string" then error("http.client.request: url must be a string", 2) end -- Call native implementation local result = __http_request(method, url, body, options) return result end, -- Shorthand function to directly get JSON get_json = function(url, options) options = options or {} local response = http.client.get(url, options) if response.ok and response.json then return response.json end return nil, response end, -- Utility to build a URL with query parameters build_url = function(base_url, params) if not params or type(params) ~= "table" then return base_url end local query = {} for k, v in pairs(params) do if type(v) == "table" then for _, item in ipairs(v) do table.insert(query, k .. "=" .. tostring(item)) end else table.insert(query, k .. "=" .. tostring(v)) end end if #query > 0 then if base_url:find("?") then return base_url .. "&" .. table.concat(query, "&") else return base_url .. "?" .. table.concat(query, "&") end end return base_url 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) else body_or_options = body_or_options or {} return http.client.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) -- ====================================================================== -- COOKIE MODULE -- ====================================================================== local cookie = { -- Set a cookie set = function(name, value, options) if type(name) ~= "string" then error("cookie.set: name must be a string", 2) end local resp = __ensure_response() resp.cookies = resp.cookies or {} local opts = options or {} local cookie = { name = name, value = value or "", path = opts.path or "/", domain = opts.domain } if opts.expires then if type(opts.expires) == "number" then if opts.expires > 0 then cookie.max_age = opts.expires local now = os.time() cookie.expires = now + opts.expires elseif opts.expires < 0 then cookie.expires = 1 cookie.max_age = 0 end -- opts.expires == 0: Session cookie (omitting both expires and max-age) end end cookie.secure = (opts.secure ~= false) cookie.http_only = (opts.http_only ~= false) if opts.same_site then local valid_values = {none = true, lax = true, strict = true} local same_site = string.lower(opts.same_site) if not valid_values[same_site] then error("cookie.set: same_site must be one of 'None', 'Lax', or 'Strict'", 2) end -- If SameSite=None, the cookie must be secure if same_site == "none" and not cookie.secure then cookie.secure = true end cookie.same_site = opts.same_site else cookie.same_site = "Lax" end table.insert(resp.cookies, cookie) return true end, -- Get a cookie value get = function(name) if type(name) ~= "string" then error("cookie.get: name must be a string", 2) end local env = getfenv(2) if env.ctx and env.ctx.cookies then return env.ctx.cookies[name] end if env.ctx and env.ctx._request_cookies then return env.ctx._request_cookies[name] end return nil end, -- Remove a cookie remove = function(name, path, domain) if type(name) ~= "string" then error("cookie.remove: name must be a string", 2) end return cookie.set(name, "", {expires = 0, path = path or "/", domain = domain}) end } -- ====================================================================== -- SESSION MODULE -- ====================================================================== local session = { get = function(key) if type(key) ~= "string" then error("session.get: key must be a string", 2) end local env = getfenv(2) if env.ctx and env.ctx.session and env.ctx.session.data then return env.ctx.session.data[key] end return nil end, set = function(key, value) if type(key) ~= "string" then error("session.set: key must be a string", 2) end if type(value) == nil then 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 then return env.ctx.session.id end return nil end, get_all = function() local env = getfenv(2) if env.ctx and env.ctx.session then return env.ctx.session.data end return nil end } -- ====================================================================== -- CSRF MODULE -- ====================================================================== local csrf = { generate = function() local token = util.generate_token(32) session.set("_csrf_token", token) return token end, field = function() local token = session.get("_csrf_token") if not token then token = csrf.generate() end return string.format('', token) end, validate = function() local env = getfenv(2) local token = false if env.ctx and env.ctx.session and env.ctx.session.data then token = env.ctx.session.data["_csrf_token"] end if not token then http.set_status(403) __http_response.body = "CSRF validation failed" exit() end local request_token = nil if env.ctx and env.ctx.form then request_token = env.ctx.form._csrf_token end if not request_token and env.ctx and env.ctx._request_headers then request_token = env.ctx._request_headers["x-csrf-token"] or env.ctx._request_headers["csrf-token"] end if not request_token or request_token ~= token then http.set_status(403) __http_response.body = "CSRF validation failed" exit() end return true end } -- ====================================================================== -- UTIL MODULE -- ====================================================================== -- Utility module implementation local util = { generate_token = function(length) return __generate_token(length or 32) end, json_encode = function(string) return __json_marshal(string or "{}") end, json_decode = function(string) return __json_unmarshal(string or "{}") end, -- Deep copy of tables deep_copy = function(obj) if type(obj) ~= 'table' then return obj end local res = {} for k, v in pairs(obj) do res[k] = util.deep_copy(v) end return res end, -- Merge tables merge_tables = function(t1, t2) if type(t1) ~= 'table' or type(t2) ~= 'table' then error("Both arguments must be tables", 2) end local result = util.deep_copy(t1) for k, v in pairs(t2) do if type(v) == 'table' and type(result[k]) == 'table' then result[k] = util.merge_tables(result[k], v) else result[k] = v end end return result end, -- String utilities string = { -- Trim whitespace trim = function(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end, -- Split string split = function(s, delimiter) delimiter = delimiter or "," local result = {} for match in (s..delimiter):gmatch("(.-)"..delimiter) do table.insert(result, match) end return result end } } -- ====================================================================== -- REGISTER MODULES GLOBALLY -- ====================================================================== _G.http = http _G.session = session _G.csrf = csrf _G.cookie = cookie _G.util = util