diff --git a/README.md b/README.md index 086b4e7..722e743 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,6 @@ ```bash git submodule update --init --recursive git submodule update --remote --recursive + +go build -trimpath -ldflags="-s -w" -o moonshark . ``` diff --git a/runner/lua/sandbox.lua b/runner/lua/sandbox.lua index a526ae3..c7fae30 100644 --- a/runner/lua/sandbox.lua +++ b/runner/lua/sandbox.lua @@ -418,22 +418,38 @@ local csrf = { } -- ====================================================================== --- TEMPLATE RENDER FUNCTION +-- TEMPLATE RENDER FUNCTIONS -- ====================================================================== -_G.render = function(template_str, env) - local OPEN_TAG, CLOSE_TAG = "" - - -- Helper functions - local function escape_html(s) +-- Shared utilities +local __template_util = { + escape_html = function(s) local entities = {['&']='&', ['<']='<', ['>']='>', ['"']='"', ["'"]='''} return (s:gsub([=[["><'&]]=], entities)) - end + end, + build_output = function(chunks) + return table.concat(chunks) + end, + + add_text = function(output, text) + if text and #text > 0 then + table.insert(output, text) + end + end, + + add_value = function(output, value, escaped) + local str = tostring(value or "") + table.insert(output, escaped and __template_util.escape_html(str) or str) + end +} + +-- Template processing with code execution +_G.render = function(template_str, env) local function get_line(s, ln) for line in s:gmatch("([^\n]*)\n?") do - if ln == 1 then return line end - ln = ln - 1 + if ln == 1 then return line end + ln = ln - 1 end end @@ -443,11 +459,20 @@ _G.render = function(template_str, env) return line end - -- Parse template local pos, chunks = 1, {} while pos <= #template_str do - local start, stop = template_str:find(OPEN_TAG, pos, true) - if not start then + local escaped_start = template_str:find("<%?", pos, true) + local unescaped_start = template_str:find("" or 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 @@ -472,7 +495,7 @@ _G.render = function(template_str, env) end local code = template_str:sub(pos, close_start-1) - table.insert(chunks, {modifier or "code", code, pos}) + table.insert(chunks, {tag_type, code, pos}) pos = close_stop + 1 if trim_newline and template_str:sub(pos, pos) == "\n" then @@ -480,7 +503,6 @@ _G.render = function(template_str, env) end end - -- Compile chunks to Lua code local buffer = {"local _tostring, _escape, _b, _b_i = ...\n"} for _, chunk in ipairs(chunks) do local t = type(chunk) @@ -504,21 +526,73 @@ _G.render = function(template_str, env) end table.insert(buffer, "return _b") - -- Load the compiled code local fn, err = loadstring(table.concat(buffer)) if not fn then error(err) end - -- Create execution environment 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, escape_html, output_buffer, 0) + fn(tostring, __template_util.escape_html, output_buffer, 0) return table.concat(output_buffer) end +-- Named placeholder processing +_G.parse = function(template_str, env) + local pos, output = 1, {} + env = env or {} + + while pos <= #template_str do + local escaped_start, escaped_end, escaped_name = template_str:find("<%?%s*([%w_]+)%s*%?>", pos) + local unescaped_start, unescaped_end, unescaped_name = template_str:find("", pos) + + local next_pos, placeholder_end, name, escaped + if escaped_start and (not unescaped_start or escaped_start < unescaped_start) then + next_pos, placeholder_end, name, escaped = escaped_start, escaped_end, escaped_name, true + elseif unescaped_start then + next_pos, placeholder_end, name, escaped = unescaped_start, unescaped_end, unescaped_name, false + else + __template_util.add_text(output, template_str:sub(pos)) + break + end + + __template_util.add_text(output, template_str:sub(pos, next_pos - 1)) + __template_util.add_value(output, env[name], escaped) + pos = placeholder_end + 1 + end + + return __template_util.build_output(output) +end + +-- Indexed placeholder processing +_G.iparse = function(template_str, values) + local pos, output, value_index = 1, {}, 1 + values = values or {} + + while pos <= #template_str do + local escaped_start, escaped_end = template_str:find("<%?>", pos, true) + local unescaped_start, unescaped_end = template_str:find("", pos, true) + + local next_pos, placeholder_end, escaped + if escaped_start and (not unescaped_start or escaped_start < unescaped_start) then + next_pos, placeholder_end, escaped = escaped_start, escaped_end, true + elseif unescaped_start then + next_pos, placeholder_end, escaped = unescaped_start, unescaped_end, false + else + __template_util.add_text(output, template_str:sub(pos)) + break + end + + __template_util.add_text(output, template_str:sub(pos, next_pos - 1)) + __template_util.add_value(output, values[value_index], escaped) + pos = placeholder_end + 1 + value_index = value_index + 1 + end + + return __template_util.build_output(output) +end + -- ====================================================================== -- PASSWORD MODULE -- ======================================================================