diff options
-rw-r--r-- | Dockerfile | 1 | ||||
-rw-r--r-- | common/cgi.lua | 234 | ||||
-rw-r--r-- | common/file.lua | 66 |
3 files changed, 301 insertions, 0 deletions
@@ -6,3 +6,4 @@ RUN apt update && apt install -y lua5.4 pandoc COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf COPY ./cgi-bin/ /usr/local/apache2/cgi-bin +COPY ./common/* /usr/local/share/lua/5.4/ diff --git a/common/cgi.lua b/common/cgi.lua new file mode 100644 index 0000000..8f2acef --- /dev/null +++ b/common/cgi.lua @@ -0,0 +1,234 @@ +cgi = {} + +cgi.none = "None" +cgi.lax = "Lax" +cgi.strict = "Strict" + +-- global helpers +function string.split(self, sep) + if not sep then sep = "%s" end + + local t = {} + for match in (self .. sep):gmatch("(.-)" .. sep) do + table.insert(t, match) + end + + return t +end + +function string.fromhex(self) + return (self:gsub("..", function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(self) + return (self:gsub(".", function(c) + return string.format("%02x", string.byte(c)) + end)) +end + +function cgi.encode_uri(str) + if str then + str = str:gsub("\n", "\r\n") + str = str:gsub("([^%w _ %- . ~])", + function(c) return string.format("%%%02X", string.byte(c)) end) + str = str:gsub(" ", "+") + end + + return str +end + +function cgi.decode_uri(str) + if str then + str = str:gsub("+", " ") + str = str:gsub("%%(%x%x)", + function(hex) return string.char(tonumber(hex, 16)) end) + end + + return str +end + +local function parse_params(dst, src) + for _, pair in ipairs(src:split("&")) do + local kv = pair:split("=") + local key = kv[1] + local value = kv[2] + + dst[key] = cgi.decode_uri(value) + end +end + +local function params_tostring(src) + local dst = "" + for k, v in pairs(src) do + local kv = k .. "=" .. cgi.encode_uri(v) + dst = dst .. kv .. "&" + end + + return dst:sub(1, #dst - 1) +end + +local function parse_cookies(dst, src) + for _, pair in ipairs(src:split("; ")) do + local kv = pair:split("=") + local key = kv[1] + local value = kv[2] + + dst[key] = cgi.decode_uri(value) + end +end + +-- method +cgi.method = os.getenv("REQUEST_METHOD") + +-- GET/POST params +cgi.get = {} +cgi.post = {} + +if cgi.method == "GET" then + local get_params = os.getenv("QUERY_STRING") + if get_params then + parse_params(cgi.get, get_params) + end +elseif cgi.method == "POST" then + local post_params = io.read("*a") + if post_params then + parse_params(cgi.post, post_params) + end +end + +-- cookies +local cookies = {} + +local cookie = os.getenv("HTTP_COOKIE") +if cookie then + local c = {} + parse_cookies(c, cookie) + + for k, v in pairs(c) do + cookies[k] = {value = v} + end +end + +function cgi.get_cookie(name) + if not cookies[name] then return nil end + return cookies[name].value +end + +function cgi.set_cookie(name, cookie) + cookie._modified = true + cookies[name] = cookie +end + +-- custom headers +function cgi.request_header(name) + return os.getenv("HTTP_" .. name:upper()) +end + +local resp_headers = {} +local headers_complete = false +function cgi.header(key, value) + if not value then + return false + end + + if not headers_complete then + resp_headers[key] = value + print(key .. ": " .. value) + end + + return not headers_complete +end + +-- set cookies on exit +local function set_cookies() + for name, c in pairs(cookies) do + if c._modified then + local cookie = name .. "=" .. cgi.encode_uri(c.value or "") + + if c.domain then + cookie = cookie .. "; Domain=" .. c.domain + end + if c.path then + cookie = cookie .. "; Path=" .. c.path + end + if c.expires then + cookie = cookie .. "; Expires=" .. c.expires + end + if c.http_only then + cookie = cookie .. "; HttpOnly" + end + if c.https_only then + cookie = cookie .. "; Secure" + end + if c.same_site then + cookie = cookie .. "; SameSite=" .. c.same_site + end + + cgi.header("Set-Cookie", cookie) + end + end +end + +-- special date format +function cgi.date(t) + return os.date("!%a, %d %b %Y %H:%M:%S GMT", t) +end + +-- (custom) content +function cgi.content(data) + if not headers_complete then + set_cookies() + print() + headers_complete = true + end + + if data then + print(data) + end + + return not not data +end + +function cgi.serve(data) + if data then + cgi.content(data) + else + cgi.status(500) + end + + cgi.done() +end + +-- finish response +function cgi.done() + if not headers_complete then + local body + + local status = tonumber(resp_headers["Status"]) + if status and status >= 400 and status < 600 then + -- error but no content: display error page + local f = io.open("/var/www/html/error/" .. tostring(status) .. ".html") + if f then + body = f:read("*a") + f:close() + end + end + + cgi.content(body) + end + + os.exit(0) +end + +-- frequently used header utilities +function cgi.content_type(type) + return cgi.header("Content-Type", type) +end + +function cgi.status(code) + return cgi.header("Status", tostring(code)) +end + +return cgi diff --git a/common/file.lua b/common/file.lua new file mode 100644 index 0000000..90c8ea2 --- /dev/null +++ b/common/file.lua @@ -0,0 +1,66 @@ +file = {} + +function string.split(self, sep) + if not sep then sep = "%s" end + + local t = {} + for match in (self .. sep):gmatch("(.-)" .. sep) do + table.insert(t, match) + end + + return t +end + +function file.read(path) + local f = io.open(path, "r") + if not f then + return nil + end + + local contents = f:read("*a") + f:close() + + return contents +end + +function file.write(path, contents) + local f = io.open(path, "w") + if not f then + return false + end + + f:write(contents) + f:close() + + return true +end + +function file.process(uri, templates, params) + path = "/var/www/md" .. uri + + local contents = file.read(path) + if not contents then + return nil + end + + if templates then + for template, value in pairs(templates) do + contents = contents:gsub("%${" .. template .. "}", value) + end + end + + local filename = os.tmpname() + file.write(filename, contents) + + params = (params or ""):match("^([%a%d-=]*)$") or "" + + local static_params = '--css /common.css -f markdown --standalone ' + local cmd = 'pandoc ' .. params .. ' ' .. static_params .. ' ' .. filename + local handle = io.popen(cmd) + local html = handle:read("*a") + handle:close() + + return html +end + +return file |