Cgi Utils |
|
print"Content-type: text/html; charset=iso-8859-1\r\n\r\n"
-- Written by RiciLake. -- The author places the code into the public domain, renouncing all rights and responsibilities. -- Don't use this in a real application, see notes below. function parsecgi(str) local rv = {} for pair in str:gmatch"[^&]+" do local key, val = pair:match"([^=]*)=(.*)" if key then rv[key] = val end end return rv end
This implementation is dangerous in various ways:
=
in a segment
+
or %xx
in values (or keys, although valid keys should not need % encoding in my opinion)
So here's a slightly better one, which still doesn't handle the "search" case. You have to give it
a freshly constructed table of legal keys; if a key can have multiple values, you provide a table
as the associated value (which is filled in), and otherwise you provide the default value (or
false
). Illegal keys are either ignored or throw errors. Segments without an =
throw an error.
Errors are thrown because invalid query/post strings are most likely to be attack attempts, and ought (in my opinion)
to be rejected; consequently, you should wrap your CGI handler in a pcall
and return a 403
or 404
error to the browser.
The function deliberately does not attempt to %-decode keys, on the basis that a valid key should never need to be %-encoded and consequently a URL of that form is likely to be an obscure attempt to attack the server.
A possible enhancement would be to also check that the supplied value is numeric in the case that the initial table has a number as the default value.
-- Written by RiciLake. -- The author places the code into the public domain, renouncing all rights and responsibilities. -- Replace + with space and %xx with the corresponding character. local function cgidecode(str) return (str:gsub('+', ' '):gsub("%%(%x%x)", function(xx) return string.char(tonumber(xx, 16)) end)) end -- Main function -- Sample invocation: cgivals = parsecgi(str, {count = 10, start = 1, names = {}}) function parsecgi(str, keys, ignore_invalid) local keyfound = {} for pair in str:gmatch"[^&]+" do local key, val = pair:match"([^=]*)=(.*)" if not key then error"Invalid query string" end local default = keys[key] if default == nil then if not ignore_invalid then error"Invalid query string" end else if type(default) == "table" then default[#default+1] = cgidecode(val) elseif keyfound[key] then error"Invalid query string" else keyfound[key] = true keys[key] = cgidecode(val) end end end return keys end
str = os.getenv("QUERY_STRING") ...
-- Assigns header information to variable "l", and returns "Hello!" back as the webpage content. -- This could use some cleaning up - error checking, removal of repetition, improved scoping. -- The SCGI protocol has the HTTP requests forwarded to a specified port (default 4000), with -- headers passed directly through TCP (and an ASCII string length prefix followed by a ":", -- with the key-value pairs of the header seperated by null characters "\0") -- With no parsing, this code handles about 950 requests per second on a 2 year old laptop. local socket = require("socket") local host = host or "*" local port = port or 4000 local s = assert(socket.bind(host, port)) local i, p = s:getsockname() assert(i, p) print("Waiting on " .. i .. ":" .. p .. "...") while 1 do c = assert(s:accept()) print("Connection requested.") len = "" l, e = c:receive(1) while not e do if l == ":" then header_len = tonumber(len) ; break end len = len .. l l, e = c:receive(1) end l,e = c:receive(header_len) c:send("Status: 200 OK\r\n") c:send("Content-Type: text/plain\r\n") c:send("\r\n") c:send("Hello!") c:close() end
The following lines in httpd.conf (Apache 2) will allow this SCGI example to function - although 127.0.0.1 will need to be changed to a more suitable IP address.
LoadModule scgi_module modules/mod_scgi.so SCGIMount /dynamic 127.0.0.1:4000