webmcp
annotate libraries/rocketcgi/rocketcgi.lua @ 68:ebdc20b7048b
Lua 5.2 compatibility:
Make function load accepting strings as first argument
Make function load accepting strings as first argument
| author | jbe |
|---|---|
| date | Tue Apr 17 16:26:32 2012 +0200 (2012-04-17) |
| parents | 3d43a5cf17c1 |
| children | 6c4a5b136074 |
| 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 |