summaryrefslogtreecommitdiff
path: root/cgi.lua
diff options
context:
space:
mode:
Diffstat (limited to 'cgi.lua')
-rw-r--r--cgi.lua162
1 files changed, 162 insertions, 0 deletions
diff --git a/cgi.lua b/cgi.lua
new file mode 100644
index 0000000..942b26f
--- /dev/null
+++ b/cgi.lua
@@ -0,0 +1,162 @@
+local html = require 'html'
+local md5 = require 'md5'
+
+local M = {}
+
+M.env = os.getenv
+
+local header_written
+local function header(h, v)
+ header_written = true
+ assert(not h:match "[: \n]")
+ assert(h:lower() == h)
+ assert(not (tostring(v)):match "\n")
+ print(("%s: %s"):format(h, v))
+end
+
+local function serve(headers, body)
+ assert(not header_written)
+ if type(headers) == 'string' then
+ headers = {content_type = headers}
+ end
+ headers.etag = ('"%s"'):format(md5.sumhexa(body))
+ headers.content_length = #body
+ for h, v in pairs(headers) do
+ if type(v) == 'table' then
+ for _, v in ipairs(v) do
+ header(h:gsub("_", "-"), v)
+ end
+ else
+ header(h:gsub("_", "-"), v)
+ end
+ end
+ print()
+ io.stdout:write(body)
+end
+
+function M.abort(...)
+ coroutine.yield(M.abort, ...)
+end
+
+function M.redirect(...)
+ coroutine.yield(M.redirect, ...)
+end
+
+function M.decode_path(path)
+ local p = {}
+ for location in path:gmatch '([^/]+)' do
+ table.insert(p, html.url_decode(location))
+ end
+ return p
+end
+
+function M.parse_qs(str,sep)
+ sep = sep or '&'
+
+ local values = {}
+ for key, val in str:gmatch(('([^%q=]+)(=*[^%q=]*)'):format(sep, sep)) do
+ local key = html.url_decode(key)
+
+ local keys = {}
+ key = key:gsub('%[([^%]]*)%]', function(v)
+ -- extract keys between balanced brackets
+ if string.find(v, "^-?%d+$") then
+ v = tonumber(v)
+ else
+ v = html.url_decode(v)
+ end
+ table.insert(keys, v)
+ return "="
+ end)
+ key = key:gsub('=+.*$', "")
+ key = key:gsub('%s', "_") -- remove spaces in parameter name
+ val = val:gsub('^=+', "")
+
+ if not values[key] then
+ values[key] = {}
+ end
+ if #keys > 0 and type(values[key]) ~= 'table' then
+ values[key] = {}
+ elseif #keys == 0 and type(values[key]) == 'table' then
+ values[key] = html.url_decode(val)
+ end
+
+ local t = values[key]
+ for i,k in ipairs(keys) do
+ if type(t) ~= 'table' then
+ t = {}
+ end
+ if k == "" then
+ k = #t+1
+ end
+ if not t[k] then
+ t[k] = {}
+ end
+ if i == #keys then
+ t[k] = html.url_decode(val)
+ end
+ t = t[k]
+ end
+ end
+ return values
+end
+
+local redir_statuses = {
+ [301] = "301 Moved Permanently",
+ [301] = "302 Found",
+ [302] = "302 See Other",
+ [307] = "307 Temporary Redirect",
+ [308] = "308 Permanent Redirect",
+}
+
+local function redirect_page(code, url)
+ return {
+ content = 'text/html', status = redir_statuses[code] or code,
+ location = url
+ }, html.render_doc(function()
+ html.h1 "redirect"
+ html.p(function()
+ html.a({href = url}, url)
+ end)
+ end)
+end
+
+function M.serve(methods, error_page)
+ local co = coroutine.create(function()
+ local path = M.env 'PATH_INFO'
+ local query = M.parse_qs(M.env 'QUERY_STRING')
+ local form
+ local content_type = M.env 'CONTENT_TYPE'
+ if content_type == 'application/x-www-form-urlencoded' then
+ form = M.parse_qs(io.read 'a')
+ end
+ local cookie = {}
+ if M.env 'HTTP_COOKIE' then
+ cookie = M.parse_qs(M.env 'HTTP_COOKIE', '; ')
+ end
+
+ if methods[M.env 'REQUEST_METHOD'] then
+ local info = {query = query, form = form, cookie = cookie}
+ serve(methods[M.env 'REQUEST_METHOD'](info, path))
+ else
+ M.abort(405)
+ end
+ end)
+
+ local ok, err, code, arg = coroutine.resume(co)
+ if ok and coroutine.status(co) ~= "dead" then
+ if err == M.abort then
+ serve(error_page(code, arg))
+ elseif err == M.redirect then
+ serve(redirect_page(code, arg))
+ else
+ ok = false err = "invalid yield"
+ end
+ end
+ if not ok then
+ io.stderr:write('\n'..debug.traceback(co, err)..'\n')
+ serve(error_page(500))
+ end
+end
+
+return M