diff options
Diffstat (limited to 'common/cgi.lua')
-rw-r--r-- | common/cgi.lua | 234 |
1 files changed, 234 insertions, 0 deletions
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 |