string fixes, static server improvements
This commit is contained in:
parent
4d0d5b6757
commit
da5281ba0a
@ -1,7 +1,5 @@
|
||||
package metadata
|
||||
|
||||
const (
|
||||
Version = "1.0.0"
|
||||
CommitHash = "placeholder"
|
||||
BuildDate = "25/07/2025"
|
||||
Version = "1.0.0"
|
||||
)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"Moonshark/metadata"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -49,6 +50,7 @@ func http_create_server(s *luajit.State) int {
|
||||
}
|
||||
|
||||
globalServer = &fasthttp.Server{
|
||||
Name: "Moonshark/" + metadata.Version,
|
||||
Handler: handleRequest,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
@ -174,6 +176,7 @@ func http_register_static(s *luajit.State) int {
|
||||
|
||||
urlPrefix := s.ToString(1)
|
||||
rootPath := s.ToString(2)
|
||||
noCache := s.ToBoolean(3)
|
||||
|
||||
// Ensure prefix starts with /
|
||||
if !strings.HasPrefix(urlPrefix, "/") {
|
||||
@ -188,7 +191,7 @@ func http_register_static(s *luajit.State) int {
|
||||
return 2
|
||||
}
|
||||
|
||||
RegisterStaticHandler(urlPrefix, absPath)
|
||||
RegisterStaticHandler(urlPrefix, absPath, noCache)
|
||||
s.PushBoolean(true)
|
||||
return 1
|
||||
}
|
||||
@ -293,16 +296,36 @@ func isLikelyStaticFile(path string) bool {
|
||||
}
|
||||
|
||||
// RegisterStaticHandler adds a static file handler
|
||||
func RegisterStaticHandler(urlPrefix, rootPath string) {
|
||||
func RegisterStaticHandler(urlPrefix, rootPath string, noCache bool) {
|
||||
staticMu.Lock()
|
||||
defer staticMu.Unlock()
|
||||
|
||||
var cacheDuration time.Duration
|
||||
var compress bool
|
||||
if noCache {
|
||||
cacheDuration = 0
|
||||
compress = false
|
||||
} else {
|
||||
cacheDuration = 3600 * time.Second
|
||||
compress = true
|
||||
}
|
||||
|
||||
fs := &fasthttp.FS{
|
||||
Root: rootPath,
|
||||
CompressRoot: rootPath + "/.cache",
|
||||
IndexNames: []string{"index.html"},
|
||||
GenerateIndexPages: false,
|
||||
Compress: true,
|
||||
Compress: compress,
|
||||
CompressBrotli: compress,
|
||||
CompressZstd: compress,
|
||||
CacheDuration: cacheDuration,
|
||||
AcceptByteRange: true,
|
||||
PathNotFound: func(ctx *fasthttp.RequestCtx) {
|
||||
path := ctx.Path()
|
||||
fmt.Printf("404 not found: %s\n", path)
|
||||
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
||||
ctx.SetBodyString("404 not found")
|
||||
},
|
||||
}
|
||||
|
||||
staticHandlers[urlPrefix] = fs
|
||||
|
@ -920,14 +920,16 @@ function Server:close()
|
||||
return _G.__IS_WORKER or moonshark.http_close_server()
|
||||
end
|
||||
|
||||
function Server:static(root_path, url_prefix)
|
||||
function Server:static(root_path, url_prefix, no_cache)
|
||||
if not no_cache or no_cache ~= true then no_cache = false end
|
||||
|
||||
url_prefix = url_prefix or "/"
|
||||
if not string.starts_with(url_prefix, "/") then
|
||||
url_prefix = "/" .. url_prefix
|
||||
end
|
||||
|
||||
if not _G.__IS_WORKER then
|
||||
local success, err = moonshark.http_register_static(url_prefix, root_path)
|
||||
local success, err = moonshark.http_register_static(url_prefix, root_path, no_cache)
|
||||
if not success then
|
||||
error("Failed to register static handler: " .. (err or "unknown error"))
|
||||
end
|
||||
|
@ -670,3 +670,398 @@ function string.random(length, charset)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Template processing with code execution
|
||||
function string.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 string.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()
|
||||
end
|
||||
table.insert(output, str)
|
||||
|
||||
pos = placeholder_end + 1
|
||||
end
|
||||
|
||||
return table.concat(output)
|
||||
end
|
||||
|
||||
-- Indexed placeholder processing
|
||||
function string.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()
|
||||
end
|
||||
table.insert(output, str)
|
||||
|
||||
pos = placeholder_end + 1
|
||||
value_index = value_index + 1
|
||||
end
|
||||
|
||||
return table.concat(output)
|
||||
end
|
||||
|
||||
string.special_chars_pattern = '[&<>"\']'
|
||||
string.entity_decode_pattern = '&[#%w]+;'
|
||||
|
||||
string.special_encode_map = {
|
||||
['&'] = '&',
|
||||
['<'] = '<',
|
||||
['>'] = '>',
|
||||
['"'] = '"',
|
||||
["'"] = '''
|
||||
}
|
||||
|
||||
string.special_decode_map = {
|
||||
['&'] = '&',
|
||||
['<'] = '<',
|
||||
['>'] = '>',
|
||||
['"'] = '"',
|
||||
['''] = "'",
|
||||
['''] = "'"
|
||||
}
|
||||
|
||||
string.extended_encode_map = {
|
||||
-- Special chars
|
||||
['&'] = '&',
|
||||
['<'] = '<',
|
||||
['>'] = '>',
|
||||
['"'] = '"',
|
||||
["'"] = ''',
|
||||
-- Extended characters
|
||||
[' '] = ' ',
|
||||
['¡'] = '¡',
|
||||
['¢'] = '¢',
|
||||
['£'] = '£',
|
||||
['¤'] = '¤',
|
||||
['¥'] = '¥',
|
||||
['¦'] = '¦',
|
||||
['§'] = '§',
|
||||
['¨'] = '¨',
|
||||
['©'] = '©',
|
||||
['ª'] = 'ª',
|
||||
['«'] = '«',
|
||||
['¬'] = '¬',
|
||||
['®'] = '®',
|
||||
['¯'] = '¯',
|
||||
['°'] = '°',
|
||||
['±'] = '±',
|
||||
['²'] = '²',
|
||||
['³'] = '³',
|
||||
['´'] = '´',
|
||||
['µ'] = 'µ',
|
||||
['¶'] = '¶',
|
||||
['·'] = '·',
|
||||
['¸'] = '¸',
|
||||
['¹'] = '¹',
|
||||
['º'] = 'º',
|
||||
['»'] = '»',
|
||||
['¼'] = '¼',
|
||||
['½'] = '½',
|
||||
['¾'] = '¾',
|
||||
['¿'] = '¿',
|
||||
['À'] = 'À',
|
||||
['Á'] = 'Á',
|
||||
['Â'] = 'Â',
|
||||
['Ã'] = 'Ã',
|
||||
['Ä'] = 'Ä',
|
||||
['Å'] = 'Å',
|
||||
['Æ'] = 'Æ',
|
||||
['Ç'] = 'Ç',
|
||||
['È'] = 'È',
|
||||
['É'] = 'É',
|
||||
['Ê'] = 'Ê',
|
||||
['Ë'] = 'Ë',
|
||||
['Ì'] = 'Ì',
|
||||
['Í'] = 'Í',
|
||||
['Î'] = 'Î',
|
||||
['Ï'] = 'Ï',
|
||||
['Ð'] = 'Ð',
|
||||
['Ñ'] = 'Ñ',
|
||||
['Ò'] = 'Ò',
|
||||
['Ó'] = 'Ó',
|
||||
['Ô'] = 'Ô',
|
||||
['Õ'] = 'Õ',
|
||||
['Ö'] = 'Ö',
|
||||
['×'] = '×',
|
||||
['Ø'] = 'Ø',
|
||||
['Ù'] = 'Ù',
|
||||
['Ú'] = 'Ú',
|
||||
['Û'] = 'Û',
|
||||
['Ü'] = 'Ü',
|
||||
['Ý'] = 'Ý',
|
||||
['Þ'] = 'Þ',
|
||||
['ß'] = 'ß',
|
||||
['à'] = 'à',
|
||||
['á'] = 'á',
|
||||
['â'] = 'â',
|
||||
['ã'] = 'ã',
|
||||
['ä'] = 'ä',
|
||||
['å'] = 'å',
|
||||
['æ'] = 'æ',
|
||||
['ç'] = 'ç',
|
||||
['è'] = 'è',
|
||||
['é'] = 'é',
|
||||
['ê'] = 'ê',
|
||||
['ë'] = 'ë',
|
||||
['ì'] = 'ì',
|
||||
['í'] = 'í',
|
||||
['î'] = 'î',
|
||||
['ï'] = 'ï',
|
||||
['ð'] = 'ð',
|
||||
['ñ'] = 'ñ',
|
||||
['ò'] = 'ò',
|
||||
['ó'] = 'ó',
|
||||
['ô'] = 'ô',
|
||||
['õ'] = 'õ',
|
||||
['ö'] = 'ö',
|
||||
['÷'] = '÷',
|
||||
['ø'] = 'ø',
|
||||
['ù'] = 'ù',
|
||||
['ú'] = 'ú',
|
||||
['û'] = 'û',
|
||||
['ü'] = 'ü',
|
||||
['ý'] = 'ý',
|
||||
['þ'] = 'þ',
|
||||
['ÿ'] = 'ÿ'
|
||||
}
|
||||
|
||||
string.extended_decode_map = {}
|
||||
for char, entity in pairs(string.extended_encode_map) do
|
||||
string.extended_decode_map[entity] = char
|
||||
end
|
||||
-- Add common named entities not in extended_encode_map
|
||||
string.extended_decode_map['''] = "'"
|
||||
string.extended_decode_map[' '] = ' '
|
||||
|
||||
-- Converts HTML special characters (&, <, >, ", ') to entities
|
||||
function string.html_special_chars(str)
|
||||
if not str then return nil end
|
||||
if not str:find(string.special_chars_pattern) then return str end
|
||||
return (str:gsub(string.special_chars_pattern, string.special_encode_map))
|
||||
end
|
||||
getmetatable("").__index.html_special_chars = string.html_special_chars
|
||||
|
||||
-- Decodes HTML special character entities back to characters
|
||||
function string.html_special_chars_decode(str)
|
||||
if not str then return nil end
|
||||
|
||||
return (str:gsub('&[lg]t;', string.special_decode_map)
|
||||
:gsub('"', '"')
|
||||
:gsub(''', "'")
|
||||
:gsub(''', "'")
|
||||
:gsub('&', '&')) -- Must be last to avoid double-decoding
|
||||
end
|
||||
getmetatable("").__index.html_special_chars_decode = string.html_special_chars_decode
|
||||
|
||||
-- More comprehensive HTML entity encoding
|
||||
-- Handles special chars plus extended Latin-1 characters
|
||||
function string.html_entities(str)
|
||||
if not str then return nil end
|
||||
|
||||
local result = {}
|
||||
local result_len = 0
|
||||
|
||||
for i = 1, #str do
|
||||
local char = str:sub(i, i)
|
||||
local entity = string.extended_encode_map[char]
|
||||
|
||||
if entity then
|
||||
result_len = result_len + 1
|
||||
result[result_len] = entity
|
||||
else
|
||||
local byte = string.byte(char)
|
||||
if byte > 127 then
|
||||
-- Encode high-bit characters as numeric entities
|
||||
result_len = result_len + 1
|
||||
result[result_len] = '&#' .. byte .. ';'
|
||||
else
|
||||
result_len = result_len + 1
|
||||
result[result_len] = char
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(result)
|
||||
end
|
||||
getmetatable("").__index.html_entities = string.html_entities
|
||||
|
||||
function string.html_entities_decode(str)
|
||||
if not str then return nil end
|
||||
|
||||
-- Handle numeric entities first
|
||||
local result = str:gsub('&#(%d+);', function(num)
|
||||
local n = tonumber(num)
|
||||
if n and n >= 0 and n <= 255 then
|
||||
return string.char(n)
|
||||
end
|
||||
return '&#' .. num .. ';' -- Return unchanged if invalid
|
||||
end)
|
||||
|
||||
-- Handle named entities
|
||||
result = result:gsub(string.entity_decode_pattern, function(entity)
|
||||
return string.extended_decode_map[entity] or entity
|
||||
end)
|
||||
|
||||
return result
|
||||
end
|
||||
getmetatable("").__index.html_entities_decode = string.html_entities_decode
|
||||
|
@ -16,7 +16,7 @@ import (
|
||||
|
||||
var (
|
||||
watchFlag = flag.Bool("watch", false, "Watch script files for changes and restart")
|
||||
wFlag = flag.Bool("w", false, "Watch script files for changes and restart (short)")
|
||||
wFlag = flag.Bool("w", false, "Watch script files for changes and restart")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user