jbe/bsw@0: #!/usr/bin/env lua jbe/bsw@0: jbe/bsw@2: local assert = assert jbe/bsw@2: local collectgarbage = collectgarbage jbe/bsw@2: local error = error jbe/bsw@2: local getfenv = getfenv jbe/bsw@2: local getmetatable = getmetatable jbe/bsw@2: local ipairs = ipairs jbe/bsw@2: local next = next jbe/bsw@2: local pairs = pairs jbe/bsw@2: local pcall = pcall jbe/bsw@2: local print = print jbe/bsw@2: local rawequal = rawequal jbe/bsw@2: local rawget = rawget jbe/bsw@2: local rawset = rawset jbe/bsw@2: local select = select jbe/bsw@2: local setfenv = setfenv jbe/bsw@2: local setmetatable = setmetatable jbe/bsw@2: local tonumber = tonumber jbe/bsw@2: local tostring = tostring jbe/bsw@2: local type = type jbe/bsw@2: local unpack = unpack jbe/bsw@2: local xpcall = xpcall jbe/bsw@0: jbe/bsw@0: local io = io jbe/bsw@0: local math = math jbe/bsw@0: local os = os jbe/bsw@0: local string = string jbe/bsw@0: local table = table jbe/bsw@0: jbe/bsw@0: module(...) jbe/bsw@0: jbe/bsw@0: data_sent = false jbe/bsw@0: jbe/bsw@0: function add_header(...) jbe/bsw@0: if data_sent then jbe/bsw@0: error("Can not add header after data has been sent.", 2) jbe/bsw@0: end jbe/bsw@0: io.stdout:write(...) jbe/bsw@0: io.stdout:write("\r\n") jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function send_data(...) jbe/bsw@0: if not data_sent then jbe/bsw@0: io.stdout:write("\r\n") jbe/bsw@0: data_sent = true jbe/bsw@0: end jbe/bsw@0: io.stdout:write(...) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function set_status(status) jbe/bsw@0: add_header("Status: ", status) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function redirect(location) jbe/bsw@0: set_status("303 See Other") jbe/bsw@0: add_header("Location: ", location) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function set_content_type(content_type) jbe/bsw@0: add_header("Content-Type: ", content_type) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: method = os.getenv("REQUEST_METHOD") or false jbe/bsw@0: query = os.getenv("QUERY_STRING") or false jbe/bsw@0: cookie_data = os.getenv("HTTP_COOKIE") or false jbe/bsw@0: post_data = io.stdin:read("*a") or false jbe/bsw@0: post_contenttype = os.getenv("CONTENT_TYPE") or false jbe/bsw@0: params = {} jbe/bsw@0: get_params = {} jbe/bsw@0: cookies = {} jbe/bsw@0: post_params = {} jbe/bsw@0: post_filenames = {} jbe/bsw@0: post_types = {} jbe/bsw@0: jbe/bsw@0: local urldecode jbe/bsw@0: do jbe/bsw@0: local b0 = string.byte("0") jbe/bsw@0: local b9 = string.byte("9") jbe/bsw@0: local bA = string.byte("A") jbe/bsw@0: local bF = string.byte("F") jbe/bsw@0: local ba = string.byte("a") jbe/bsw@0: local bf = string.byte("f") jbe/bsw@0: function urldecode(str) jbe/bsw@0: return ( jbe/bsw@0: string.gsub( jbe/bsw@0: string.gsub(str, "%+", " "), jbe/bsw@0: "%%([0-9A-Fa-f][0-9A-Fa-f])", jbe/bsw@0: function(hex) jbe/bsw@0: local n1, n2 = string.byte(hex, 1, 2) jbe/bsw@0: if n1 >= b0 and n1 <= b9 then n1 = n1 - b0 jbe/bsw@0: elseif n1 >= bA and n1 <= bF then n1 = n1 - bA + 10 jbe/bsw@0: elseif n1 >= ba and n1 <= bf then n1 = n1 - ba + 10 jbe/bsw@0: else return end jbe/bsw@0: if n2 >= b0 and n2 <= b9 then n2 = n2 - b0 jbe/bsw@0: elseif n2 >= bA and n2 <= bF then n2 = n2 - bA + 10 jbe/bsw@0: elseif n2 >= ba and n2 <= bf then n2 = n2 - ba + 10 jbe/bsw@0: else return end jbe/bsw@0: return string.char(n1 * 16 + n2) jbe/bsw@0: end jbe/bsw@0: ) jbe/bsw@0: ) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: local function proc_param(tbl, key, value) jbe/bsw@0: if string.find(key, "%[%]$") then jbe/bsw@0: if tbl[key] then jbe/bsw@0: table.insert(tbl[key], value) jbe/bsw@0: else jbe/bsw@0: local list = { value } jbe/bsw@0: params[key] = list jbe/bsw@0: tbl[key] = list jbe/bsw@0: end jbe/bsw@0: else jbe/bsw@0: params[key] = value jbe/bsw@0: tbl[key] = value jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: local function read_urlencoded_form(tbl, data) jbe/bsw@0: for rawkey, rawvalue in string.gmatch(data, "([^=&]*)=([^=&]*)") do jbe/bsw@0: proc_param(tbl, urldecode(rawkey), urldecode(rawvalue)) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: if query then jbe/bsw@0: read_urlencoded_form(get_params, query) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: if cookie_data then jbe/bsw@0: for rawkey, rawvalue in string.gmatch(cookie_data, "([^=; ]*)=([^=; ]*)") do jbe/bsw@0: cookies[urldecode(rawkey)] = urldecode(rawvalue) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: if post_contenttype == "application/x-www-form-urlencoded" then jbe/bsw@0: read_urlencoded_form(post_params, post_data) jbe/bsw@0: elseif post_contenttype then jbe/bsw@0: local boundary = string.match( jbe/bsw@0: post_contenttype, jbe/bsw@0: '^multipart/form%-data[ \t]*;[ \t]*boundary="([^"]+)"' jbe/bsw@0: ) or string.match( jbe/bsw@0: post_contenttype, jbe/bsw@0: '^multipart/form%-data[ \t]*;[ \t]*boundary=([^"; \t]+)' jbe/bsw@0: ) jbe/bsw@0: if boundary then jbe/bsw@0: local parts = {} jbe/bsw@0: do jbe/bsw@0: local boundary = "\r\n--" .. boundary jbe/bsw@0: local post_data = "\r\n" .. post_data jbe/bsw@0: local pos1, pos2 = string.find(post_data, boundary, 1, true) jbe/bsw@0: while true do jbe/bsw@0: local ind = string.sub(post_data, pos2 + 1, pos2 + 2) jbe/bsw@0: if ind == "\r\n" then jbe/bsw@0: local pos3, pos4 = string.find(post_data, boundary, pos2 + 1, true) jbe/bsw@0: if pos3 then jbe/bsw@0: parts[#parts + 1] = string.sub(post_data, pos2 + 3, pos3 - 1) jbe/bsw@0: else jbe/bsw@0: error("Illegal POST data.") jbe/bsw@0: end jbe/bsw@0: pos1, pos2 = pos3, pos4 jbe/bsw@0: elseif ind == "--" then jbe/bsw@0: break jbe/bsw@0: else jbe/bsw@0: error("Illegal POST data.") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: for i, part in ipairs(parts) do jbe/bsw@0: local pos = 1 jbe/bsw@0: local name, filename, contenttype jbe/bsw@0: while true do jbe/bsw@0: local header jbe/bsw@0: do jbe/bsw@0: local oldpos = pos jbe/bsw@0: pos = string.find(part, "\r\n", oldpos, true) jbe/bsw@0: if not pos then jbe/bsw@0: error("Illegal POST data.") jbe/bsw@0: end jbe/bsw@0: if pos == oldpos then break end jbe/bsw@0: header = string.sub(part, oldpos, pos - 1) jbe/bsw@0: pos = pos + 2 jbe/bsw@0: end jbe/bsw@0: if string.find( jbe/bsw@0: string.lower(header), jbe/bsw@0: "^content%-disposition:[ \t]*form%-data[ \t]*;" jbe/bsw@0: ) then jbe/bsw@0: -- TODO: handle all cases correctly jbe/bsw@0: name = string.match(header, ';[ \t]*name="([^"]*)"') or jbe/bsw@0: string.match(header, ';[ \t]*name=([^"; \t]+)') jbe/bsw@0: filename = string.match(header, ';[ \t]*filename="([^"]*)"') or jbe/bsw@0: string.match(header, ';[ \t]*filename=([^"; \t]+)') jbe/bsw@0: else jbe/bsw@0: local dummy, subpos = string.find( jbe/bsw@0: string.lower(header), jbe/bsw@0: "^content%-type:[ \t]*" jbe/bsw@0: ) jbe/bsw@0: if subpos then jbe/bsw@0: contenttype = string.sub(header, subpos + 1, #header) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: local content = string.sub(part, pos + 2, #part) jbe/bsw@0: if not name then jbe/bsw@0: error("Illegal POST data.") jbe/bsw@0: end jbe/bsw@0: proc_param(post_params, name, content) jbe/bsw@0: post_filenames[name] = filename jbe/bsw@0: post_types[name] = contenttype jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: if post_data and #post_data > 262144 then jbe/bsw@0: post_data = nil jbe/bsw@0: collectgarbage("collect") jbe/bsw@0: else jbe/bsw@0: post_data = nil jbe/bsw@0: end