| rev | line source | 
| jbe/bsw@0 | 1 #!/usr/bin/env lua | 
| jbe/bsw@0 | 2 | 
| jbe/bsw@2 | 3 local assert         = assert | 
| jbe/bsw@2 | 4 local collectgarbage = collectgarbage | 
| jbe/bsw@2 | 5 local error          = error | 
| jbe/bsw@2 | 6 local getmetatable   = getmetatable | 
| jbe/bsw@2 | 7 local ipairs         = ipairs | 
| jbe/bsw@2 | 8 local next           = next | 
| jbe/bsw@2 | 9 local pairs          = pairs | 
| jbe/bsw@2 | 10 local print          = print | 
| jbe/bsw@2 | 11 local rawequal       = rawequal | 
| jbe/bsw@2 | 12 local rawget         = rawget | 
| jbe@64 | 13 local rawlen         = rawlen | 
| jbe/bsw@2 | 14 local rawset         = rawset | 
| jbe/bsw@2 | 15 local select         = select | 
| jbe/bsw@2 | 16 local setmetatable   = setmetatable | 
| jbe/bsw@2 | 17 local tonumber       = tonumber | 
| jbe/bsw@2 | 18 local tostring       = tostring | 
| jbe/bsw@2 | 19 local type           = type | 
| jbe/bsw@0 | 20 | 
| jbe/bsw@0 | 21 local io     = io | 
| jbe/bsw@0 | 22 local math   = math | 
| jbe/bsw@0 | 23 local os     = os | 
| jbe/bsw@0 | 24 local string = string | 
| jbe/bsw@0 | 25 local table  = table | 
| jbe/bsw@0 | 26 | 
| jbe@64 | 27 local _M = {} | 
| jbe@64 | 28 if _ENV then | 
| jbe@64 | 29   _ENV = _M | 
| jbe@64 | 30 else | 
| jbe@64 | 31   _G[...] = _M | 
| jbe@64 | 32   setfenv(1, _M) | 
| jbe@64 | 33 end | 
| jbe/bsw@0 | 34 | 
| jbe/bsw@0 | 35 data_sent = false | 
| jbe/bsw@0 | 36 | 
| jbe/bsw@16 | 37 --[[-- | 
| jbe/bsw@16 | 38 rocketcgi.add_header( | 
| jbe/bsw@16 | 39   string_part1,        -- string | 
| jbe/bsw@16 | 40   string_part2,        -- optional second part of string to be concatted | 
| jbe/bsw@16 | 41   ... | 
| jbe/bsw@16 | 42 ) | 
| jbe/bsw@16 | 43 | 
| jbe/bsw@16 | 44 Sends a header line to the browser. Multiple arguments are concatted to form a single string. | 
| jbe/bsw@16 | 45 | 
| jbe/bsw@16 | 46 --]]-- | 
| jbe/bsw@0 | 47 function add_header(...) | 
| jbe/bsw@0 | 48   if data_sent then | 
| jbe/bsw@0 | 49     error("Can not add header after data has been sent.", 2) | 
| jbe/bsw@0 | 50   end | 
| jbe/bsw@0 | 51   io.stdout:write(...) | 
| jbe/bsw@0 | 52   io.stdout:write("\r\n") | 
| jbe/bsw@0 | 53 end | 
| jbe/bsw@16 | 54 --//-- | 
| jbe/bsw@0 | 55 | 
| jbe/bsw@16 | 56 --[[-- | 
| jbe/bsw@16 | 57 rocketcgi.send_data( | 
| jbe/bsw@16 | 58   string_part1,       -- string | 
| jbe/bsw@16 | 59   string_part2,       -- optional second part of string to be concatted | 
| jbe/bsw@16 | 60   ... | 
| jbe/bsw@16 | 61 ) | 
| jbe/bsw@16 | 62 | 
| jbe/bsw@16 | 63 Sends document data to the browser. Multiple arguments are concatted to form a single string. | 
| jbe/bsw@16 | 64 | 
| jbe/bsw@16 | 65 --]]-- | 
| jbe/bsw@0 | 66 function send_data(...) | 
| jbe/bsw@0 | 67   if not data_sent then | 
| jbe/bsw@0 | 68     io.stdout:write("\r\n") | 
| jbe/bsw@0 | 69     data_sent = true | 
| jbe/bsw@0 | 70   end | 
| jbe/bsw@0 | 71   io.stdout:write(...) | 
| jbe/bsw@0 | 72 end | 
| jbe/bsw@16 | 73 --//-- | 
| jbe/bsw@0 | 74 | 
| jbe/bsw@16 | 75 --[[-- | 
| jbe/bsw@16 | 76 rocketcgi.set_status( | 
| jbe/bsw@16 | 77   status               -- Status code and description, e.g. "404 Not Found" | 
| jbe/bsw@16 | 78 ) | 
| jbe/bsw@16 | 79 | 
| jbe/bsw@16 | 80 Sends a header line to the browser, indicating a given HTTP status. | 
| jbe/bsw@16 | 81 | 
| jbe/bsw@16 | 82 --]]-- | 
| jbe/bsw@0 | 83 function set_status(status) | 
| jbe/bsw@0 | 84   add_header("Status: ", status) | 
| jbe/bsw@0 | 85 end | 
| jbe/bsw@16 | 86 --//-- | 
| jbe/bsw@0 | 87 | 
| jbe/bsw@16 | 88 --[[-- | 
| jbe/bsw@16 | 89 rocketcgi.redirect( | 
| jbe/bsw@16 | 90   status             -- Absolute URL to redirect the browser to | 
| jbe/bsw@16 | 91 ) | 
| jbe/bsw@16 | 92 | 
| jbe/bsw@16 | 93 Redirects the browser to the given absolute URL, using a 303 Redirect. | 
| jbe/bsw@16 | 94 | 
| jbe/bsw@16 | 95 --]]-- | 
| jbe/bsw@0 | 96 function redirect(location) | 
| jbe/bsw@0 | 97   set_status("303 See Other") | 
| jbe/bsw@0 | 98   add_header("Location: ", location) | 
| jbe/bsw@0 | 99 end | 
| jbe/bsw@16 | 100 --//-- | 
| jbe/bsw@0 | 101 | 
| jbe/bsw@16 | 102 --[[-- | 
| bsw@18 | 103 rocketcgi.set_content_type( | 
| bsw@18 | 104   content_type               -- MIME content type | 
| jbe/bsw@16 | 105 ) | 
| jbe/bsw@16 | 106 | 
| jbe/bsw@16 | 107 Sends a header line specifying the content-type to the browser. | 
| jbe/bsw@16 | 108 | 
| jbe/bsw@16 | 109 --]]-- | 
| jbe/bsw@0 | 110 function set_content_type(content_type) | 
| jbe/bsw@0 | 111   add_header("Content-Type: ", content_type) | 
| jbe/bsw@0 | 112 end | 
| jbe/bsw@16 | 113 --//-- | 
| jbe/bsw@16 | 114 | 
| jbe/bsw@16 | 115 --[[-- | 
| jbe/bsw@16 | 116 rocketcgi.set_cookie{ | 
| jbe/bsw@16 | 117   name   = name,       -- name of cookie | 
| jbe/bsw@16 | 118   value  = value,      -- value of cookie | 
| jbe/bsw@16 | 119   domain = domain,     -- domain where cookie is transmitted | 
| jbe/bsw@16 | 120   path   = path,       -- path where cookie is transmitted | 
| jbe/bsw@16 | 121   secure = secure      -- boolean, indicating if cookie should only be transmitted over HTTPS | 
| jbe/bsw@16 | 122 } | 
| jbe/bsw@16 | 123 | 
| jbe/bsw@16 | 124 Sends a header line setting a cookie. NOTE: Currently only session cookies are supported. | 
| jbe/bsw@16 | 125 | 
| jbe/bsw@16 | 126 --]]-- | 
| jbe/bsw@16 | 127 function set_cookie(args) | 
| jbe/bsw@16 | 128   assert(string.find(args.name, "^[0-9A-Za-z%%._~-]+$"), "Illegal cookie name") | 
| jbe/bsw@16 | 129   assert(string.find(args.value, "^[0-9A-Za-z%%._~-]+$"), "Illegal cookie value") | 
| jbe/bsw@16 | 130   local parts = {"Set-Cookie: " .. args.name .. "=" .. args.value} | 
| jbe/bsw@16 | 131   if args.domain then | 
| jbe/bsw@16 | 132     assert( | 
| jbe/bsw@16 | 133       string.find(args.path, "^[0-9A-Za-z%%/._~-]+$"), | 
| jbe/bsw@16 | 134       "Illegal cookie domain" | 
| jbe/bsw@16 | 135     ) | 
| jbe/bsw@16 | 136     parts[#parts+1] = "domain=" .. args.domain | 
| jbe/bsw@16 | 137   end | 
| jbe/bsw@16 | 138   if args.path then | 
| jbe/bsw@16 | 139     assert( | 
| jbe/bsw@16 | 140       string.find(args.path, "^[0-9A-Za-z%%/._~-]+$"), | 
| jbe/bsw@16 | 141       "Illegal cookie path" | 
| jbe/bsw@16 | 142     ) | 
| jbe/bsw@16 | 143     parts[#parts+1] = "path=" .. args.path | 
| jbe/bsw@16 | 144   end | 
| jbe/bsw@16 | 145   if args.secure then | 
| jbe/bsw@16 | 146     parts[#parts+1] = "secure" | 
| jbe/bsw@16 | 147   end | 
| jbe/bsw@16 | 148   add_header(table.concat(parts, "; ")) | 
| jbe/bsw@16 | 149 end | 
| jbe/bsw@16 | 150 --//-- | 
| jbe/bsw@0 | 151 | 
| jbe/bsw@0 | 152 method = os.getenv("REQUEST_METHOD") or false | 
| jbe/bsw@0 | 153 query = os.getenv("QUERY_STRING") or false | 
| jbe/bsw@0 | 154 cookie_data = os.getenv("HTTP_COOKIE") or false | 
| jbe/bsw@0 | 155 post_data = io.stdin:read("*a") or false | 
| jbe/bsw@0 | 156 post_contenttype = os.getenv("CONTENT_TYPE") or false | 
| jbe/bsw@0 | 157 params = {} | 
| jbe/bsw@0 | 158 get_params = {} | 
| jbe/bsw@0 | 159 cookies = {} | 
| jbe/bsw@0 | 160 post_params = {} | 
| jbe/bsw@0 | 161 post_filenames = {} | 
| jbe/bsw@0 | 162 post_types = {} | 
| jbe/bsw@0 | 163 | 
| jbe/bsw@0 | 164 local urldecode | 
| jbe/bsw@0 | 165 do | 
| jbe/bsw@0 | 166   local b0 = string.byte("0") | 
| jbe/bsw@0 | 167   local b9 = string.byte("9") | 
| jbe/bsw@0 | 168   local bA = string.byte("A") | 
| jbe/bsw@0 | 169   local bF = string.byte("F") | 
| jbe/bsw@0 | 170   local ba = string.byte("a") | 
| jbe/bsw@0 | 171   local bf = string.byte("f") | 
| jbe/bsw@0 | 172   function urldecode(str) | 
| jbe/bsw@0 | 173     return ( | 
| jbe/bsw@0 | 174       string.gsub( | 
| jbe/bsw@0 | 175         string.gsub(str, "%+", " "), | 
| jbe/bsw@0 | 176         "%%([0-9A-Fa-f][0-9A-Fa-f])", | 
| jbe/bsw@0 | 177         function(hex) | 
| jbe/bsw@0 | 178           local n1, n2 = string.byte(hex, 1, 2) | 
| jbe/bsw@0 | 179           if n1 >= b0 and n1 <= b9 then n1 = n1 - b0 | 
| jbe/bsw@0 | 180           elseif n1 >= bA and n1 <= bF then n1 = n1 - bA + 10 | 
| jbe/bsw@0 | 181           elseif n1 >= ba and n1 <= bf then n1 = n1 - ba + 10 | 
| jbe/bsw@0 | 182           else return end | 
| jbe/bsw@0 | 183           if n2 >= b0 and n2 <= b9 then n2 = n2 - b0 | 
| jbe/bsw@0 | 184           elseif n2 >= bA and n2 <= bF then n2 = n2 - bA + 10 | 
| jbe/bsw@0 | 185           elseif n2 >= ba and n2 <= bf then n2 = n2 - ba + 10 | 
| jbe/bsw@0 | 186           else return end | 
| jbe/bsw@0 | 187           return string.char(n1 * 16 + n2) | 
| jbe/bsw@0 | 188         end | 
| jbe/bsw@0 | 189       ) | 
| jbe/bsw@0 | 190     ) | 
| jbe/bsw@0 | 191   end | 
| jbe/bsw@0 | 192 end | 
| jbe/bsw@0 | 193 | 
| jbe/bsw@0 | 194 local function proc_param(tbl, key, value) | 
| jbe/bsw@0 | 195   if string.find(key, "%[%]$") then | 
| jbe/bsw@0 | 196     if tbl[key] then | 
| jbe/bsw@0 | 197       table.insert(tbl[key], value) | 
| jbe/bsw@0 | 198     else | 
| jbe/bsw@0 | 199       local list = { value } | 
| jbe/bsw@0 | 200       params[key] = list | 
| jbe/bsw@0 | 201       tbl[key] = list | 
| jbe/bsw@0 | 202     end | 
| jbe/bsw@0 | 203   else | 
| jbe/bsw@0 | 204     params[key] = value | 
| jbe/bsw@0 | 205     tbl[key] = value | 
| jbe/bsw@0 | 206   end | 
| jbe/bsw@0 | 207 end | 
| jbe/bsw@0 | 208 | 
| jbe/bsw@0 | 209 local function read_urlencoded_form(tbl, data) | 
| jbe/bsw@0 | 210   for rawkey, rawvalue in string.gmatch(data, "([^=&]*)=([^=&]*)") do | 
| jbe/bsw@0 | 211     proc_param(tbl, urldecode(rawkey), urldecode(rawvalue)) | 
| jbe/bsw@0 | 212   end | 
| jbe/bsw@0 | 213 end | 
| jbe/bsw@0 | 214 | 
| jbe/bsw@0 | 215 if query then | 
| jbe/bsw@0 | 216   read_urlencoded_form(get_params, query) | 
| jbe/bsw@0 | 217 end | 
| jbe/bsw@0 | 218 | 
| jbe/bsw@0 | 219 if cookie_data then | 
| jbe/bsw@0 | 220   for rawkey, rawvalue in string.gmatch(cookie_data, "([^=; ]*)=([^=; ]*)") do | 
| jbe/bsw@0 | 221     cookies[urldecode(rawkey)] = urldecode(rawvalue) | 
| jbe/bsw@0 | 222   end | 
| jbe/bsw@0 | 223 end | 
| jbe/bsw@0 | 224 | 
| jbe/bsw@11 | 225 if post_contenttype and ( | 
| jbe/bsw@11 | 226   post_contenttype == "application/x-www-form-urlencoded" or | 
| jbe/bsw@11 | 227   string.match(post_contenttype, "^application/x%-www%-form%-urlencoded[ ;]") | 
| jbe/bsw@11 | 228 ) then | 
| jbe/bsw@0 | 229   read_urlencoded_form(post_params, post_data) | 
| jbe/bsw@0 | 230 elseif post_contenttype then | 
| jbe/bsw@0 | 231   local boundary = string.match( | 
| jbe/bsw@0 | 232     post_contenttype, | 
| jbe/bsw@0 | 233     '^multipart/form%-data[ \t]*;[ \t]*boundary="([^"]+)"' | 
| jbe/bsw@0 | 234   ) or string.match( | 
| jbe/bsw@0 | 235       post_contenttype, | 
| jbe/bsw@0 | 236       '^multipart/form%-data[ \t]*;[ \t]*boundary=([^"; \t]+)' | 
| jbe/bsw@0 | 237   ) | 
| jbe/bsw@0 | 238   if boundary then | 
| jbe/bsw@0 | 239     local parts = {} | 
| jbe/bsw@0 | 240     do | 
| jbe/bsw@0 | 241       local boundary = "\r\n--" .. boundary | 
| jbe/bsw@0 | 242       local post_data = "\r\n" .. post_data | 
| jbe/bsw@0 | 243       local pos1, pos2 = string.find(post_data, boundary, 1, true) | 
| jbe/bsw@0 | 244       while true do | 
| jbe/bsw@0 | 245         local ind = string.sub(post_data, pos2 + 1, pos2 + 2) | 
| jbe/bsw@0 | 246         if ind == "\r\n" then | 
| jbe/bsw@0 | 247           local pos3, pos4 = string.find(post_data, boundary, pos2 + 1, true) | 
| jbe/bsw@0 | 248           if pos3 then | 
| jbe/bsw@0 | 249             parts[#parts + 1] = string.sub(post_data, pos2 + 3, pos3 - 1) | 
| jbe/bsw@0 | 250           else | 
| jbe/bsw@0 | 251             error("Illegal POST data.") | 
| jbe/bsw@0 | 252           end | 
| jbe/bsw@0 | 253           pos1, pos2 = pos3, pos4 | 
| jbe/bsw@0 | 254         elseif ind == "--" then | 
| jbe/bsw@0 | 255           break | 
| jbe/bsw@0 | 256         else | 
| jbe/bsw@0 | 257           error("Illegal POST data.") | 
| jbe/bsw@0 | 258         end | 
| jbe/bsw@0 | 259       end | 
| jbe/bsw@0 | 260     end | 
| jbe/bsw@0 | 261     for i, part in ipairs(parts) do | 
| jbe/bsw@0 | 262       local pos = 1 | 
| jbe/bsw@0 | 263       local name, filename, contenttype | 
| jbe/bsw@0 | 264       while true do | 
| jbe/bsw@0 | 265         local header | 
| jbe/bsw@0 | 266         do | 
| jbe/bsw@0 | 267           local oldpos = pos | 
| jbe/bsw@0 | 268           pos = string.find(part, "\r\n", oldpos, true) | 
| jbe/bsw@0 | 269           if not pos then | 
| jbe/bsw@0 | 270             error("Illegal POST data.") | 
| jbe/bsw@0 | 271           end | 
| jbe/bsw@0 | 272           if pos == oldpos then break end | 
| jbe/bsw@0 | 273           header = string.sub(part, oldpos, pos - 1) | 
| jbe/bsw@0 | 274           pos = pos + 2 | 
| jbe/bsw@0 | 275         end | 
| jbe/bsw@0 | 276         if string.find( | 
| jbe/bsw@0 | 277           string.lower(header), | 
| jbe/bsw@0 | 278           "^content%-disposition:[ \t]*form%-data[ \t]*;" | 
| jbe/bsw@0 | 279         ) then | 
| jbe/bsw@0 | 280           -- TODO: handle all cases correctly | 
| jbe/bsw@0 | 281           name = string.match(header, ';[ \t]*name="([^"]*)"') or | 
| jbe/bsw@0 | 282             string.match(header, ';[ \t]*name=([^"; \t]+)') | 
| jbe/bsw@0 | 283           filename = string.match(header, ';[ \t]*filename="([^"]*)"') or | 
| jbe/bsw@0 | 284             string.match(header, ';[ \t]*filename=([^"; \t]+)') | 
| jbe/bsw@0 | 285         else | 
| jbe/bsw@0 | 286           local dummy, subpos = string.find( | 
| jbe/bsw@0 | 287             string.lower(header), | 
| jbe/bsw@0 | 288             "^content%-type:[ \t]*" | 
| jbe/bsw@0 | 289           ) | 
| jbe/bsw@0 | 290           if subpos then | 
| jbe/bsw@0 | 291             contenttype = string.sub(header, subpos + 1, #header) | 
| jbe/bsw@0 | 292           end | 
| jbe/bsw@0 | 293         end | 
| jbe/bsw@0 | 294       end | 
| jbe/bsw@0 | 295       local content = string.sub(part, pos + 2, #part) | 
| jbe/bsw@0 | 296       if not name then | 
| jbe/bsw@0 | 297         error("Illegal POST data.") | 
| jbe/bsw@0 | 298       end | 
| jbe/bsw@0 | 299       proc_param(post_params, name, content) | 
| jbe/bsw@0 | 300       post_filenames[name] = filename | 
| jbe/bsw@0 | 301       post_types[name] = contenttype | 
| jbe/bsw@0 | 302     end | 
| jbe/bsw@0 | 303   end | 
| jbe/bsw@0 | 304 end | 
| jbe/bsw@0 | 305 | 
| jbe/bsw@0 | 306 if post_data and #post_data > 262144 then | 
| jbe/bsw@0 | 307   post_data = nil | 
| jbe/bsw@0 | 308   collectgarbage("collect") | 
| jbe/bsw@0 | 309 else | 
| jbe/bsw@0 | 310   post_data = nil | 
| jbe/bsw@0 | 311 end | 
| jbe@64 | 312 | 
| jbe@64 | 313 return _M |