moonbridge
annotate moonbridge_http.lua @ 124:61a2f55b3538
Do not announce socket/listener type when communicating with the child process
(unnecessary because pointer to listener struct is passed)
(unnecessary because pointer to listener struct is passed)
author | jbe |
---|---|
date | Sun Apr 12 00:33:08 2015 +0200 (2015-04-12) |
parents | 7014436d88ea |
children | 831f2d4b2d73 8ab33bfb47e7 |
rev | line source |
---|---|
jbe@0 | 1 #!/usr/bin/env lua |
jbe@0 | 2 |
jbe@0 | 3 -- module preamble |
jbe@0 | 4 local _G, _M = _ENV, {} |
jbe@0 | 5 _ENV = setmetatable({}, { |
jbe@0 | 6 __index = function(self, key) |
jbe@0 | 7 local value = _M[key]; if value ~= nil then return value end |
jbe@0 | 8 return _G[key] |
jbe@0 | 9 end, |
jbe@63 | 10 __newindex = _M |
jbe@0 | 11 }) |
jbe@0 | 12 |
jbe@0 | 13 -- function that encodes certain HTML entities: |
jbe@0 | 14 -- (not used by the library itself) |
jbe@0 | 15 function encode_html(text) |
jbe@0 | 16 return ( |
jbe@0 | 17 string.gsub( |
jbe@0 | 18 text, '[<>&"]', |
jbe@0 | 19 function(char) |
jbe@0 | 20 if char == '<' then |
jbe@0 | 21 return "<" |
jbe@0 | 22 elseif char == '>' then |
jbe@0 | 23 return ">" |
jbe@0 | 24 elseif char == '&' then |
jbe@0 | 25 return "&" |
jbe@0 | 26 elseif char == '"' then |
jbe@0 | 27 return """ |
jbe@0 | 28 end |
jbe@0 | 29 end |
jbe@0 | 30 ) |
jbe@0 | 31 ) |
jbe@0 | 32 |
jbe@0 | 33 end |
jbe@0 | 34 |
jbe@0 | 35 -- function that encodes special characters for URIs: |
jbe@0 | 36 -- (not used by the library itself) |
jbe@0 | 37 function encode_uri(text) |
jbe@0 | 38 return ( |
jbe@0 | 39 string.gsub(text, "[^0-9A-Za-z_%.~-]", |
jbe@0 | 40 function (char) |
jbe@0 | 41 return string.format("%%%02x", string.byte(char)) |
jbe@0 | 42 end |
jbe@0 | 43 ) |
jbe@0 | 44 ) |
jbe@0 | 45 end |
jbe@0 | 46 |
jbe@0 | 47 -- function undoing URL encoding: |
jbe@0 | 48 do |
jbe@0 | 49 local b0 = string.byte("0") |
jbe@0 | 50 local b9 = string.byte("9") |
jbe@0 | 51 local bA = string.byte("A") |
jbe@0 | 52 local bF = string.byte("F") |
jbe@0 | 53 local ba = string.byte("a") |
jbe@0 | 54 local bf = string.byte("f") |
jbe@0 | 55 function decode_uri(str) |
jbe@0 | 56 return ( |
jbe@0 | 57 string.gsub( |
jbe@0 | 58 string.gsub(str, "%+", " "), |
jbe@0 | 59 "%%([0-9A-Fa-f][0-9A-Fa-f])", |
jbe@0 | 60 function(hex) |
jbe@0 | 61 local n1, n2 = string.byte(hex, 1, 2) |
jbe@0 | 62 if n1 >= b0 and n1 <= b9 then n1 = n1 - b0 |
jbe@0 | 63 elseif n1 >= bA and n1 <= bF then n1 = n1 - bA + 10 |
jbe@0 | 64 elseif n1 >= ba and n1 <= bf then n1 = n1 - ba + 10 |
jbe@0 | 65 else error("Assertion failed") end |
jbe@0 | 66 if n2 >= b0 and n2 <= b9 then n2 = n2 - b0 |
jbe@0 | 67 elseif n2 >= bA and n2 <= bF then n2 = n2 - bA + 10 |
jbe@0 | 68 elseif n2 >= ba and n2 <= bf then n2 = n2 - ba + 10 |
jbe@0 | 69 else error("Assertion failed") end |
jbe@0 | 70 return string.char(n1 * 16 + n2) |
jbe@0 | 71 end |
jbe@0 | 72 ) |
jbe@0 | 73 ) |
jbe@0 | 74 end |
jbe@0 | 75 end |
jbe@0 | 76 |
jbe@0 | 77 -- status codes that carry no response body (in addition to 1xx): |
jbe@0 | 78 -- (set to "zero_content_length" if Content-Length header is required) |
jbe@0 | 79 status_without_response_body = { |
jbe@5 | 80 ["101"] = true, -- list 101 to allow protocol switch |
jbe@0 | 81 ["204"] = true, |
jbe@0 | 82 ["205"] = "zero_content_length", |
jbe@0 | 83 ["304"] = true |
jbe@0 | 84 } |
jbe@0 | 85 |
jbe@0 | 86 -- handling of GET/POST param tables: |
jbe@0 | 87 local new_params_list -- defined later |
jbe@0 | 88 do |
jbe@0 | 89 local params_list_mapping = setmetatable({}, {__mode="k"}) |
jbe@35 | 90 local function nextnonempty(tbl, key) |
jbe@35 | 91 while true do |
jbe@35 | 92 key = next(tbl, key) |
jbe@35 | 93 if key == nil then |
jbe@35 | 94 return nil |
jbe@35 | 95 end |
jbe@35 | 96 local value = tbl[key] |
jbe@35 | 97 if #value > 0 then |
jbe@35 | 98 return key, value |
jbe@35 | 99 end |
jbe@35 | 100 end |
jbe@35 | 101 end |
jbe@35 | 102 local function nextvalue(tbl, key) |
jbe@35 | 103 key = next(tbl, key) |
jbe@35 | 104 if key == nil then |
jbe@35 | 105 return nil |
jbe@35 | 106 end |
jbe@35 | 107 return key, tbl[key][1] |
jbe@35 | 108 end |
jbe@0 | 109 local params_list_metatable = { |
jbe@0 | 110 __index = function(self, key) |
jbe@0 | 111 local tbl = {} |
jbe@0 | 112 self[key] = tbl |
jbe@0 | 113 return tbl |
jbe@35 | 114 end, |
jbe@35 | 115 __pairs = function(self) |
jbe@35 | 116 return nextnonempty, self, nil |
jbe@0 | 117 end |
jbe@0 | 118 } |
jbe@0 | 119 local params_metatable = { |
jbe@0 | 120 __index = function(self, key) |
jbe@36 | 121 return params_list_mapping[self][key][1] |
jbe@36 | 122 end, |
jbe@36 | 123 __newindex = function(self, key, value) |
jbe@36 | 124 params_list_mapping[self][key] = {value} |
jbe@35 | 125 end, |
jbe@35 | 126 __pairs = function(self) |
jbe@35 | 127 return nextvalue, params_list_mapping[self], nil |
jbe@0 | 128 end |
jbe@0 | 129 } |
jbe@0 | 130 -- returns a table to store key value-list pairs (i.e. multiple values), |
jbe@0 | 131 -- and a second table automatically mapping keys to the first value |
jbe@0 | 132 -- using the key value-list pairs in the first table: |
jbe@0 | 133 new_params_list = function() |
jbe@0 | 134 local params_list = setmetatable({}, params_list_metatable) |
jbe@0 | 135 local params = setmetatable({}, params_metatable) |
jbe@0 | 136 params_list_mapping[params] = params_list |
jbe@0 | 137 return params_list, params |
jbe@0 | 138 end |
jbe@0 | 139 end |
jbe@0 | 140 -- parses URL encoded form data and stores it in |
jbe@0 | 141 -- a key value-list pairs structure that has to be |
jbe@0 | 142 -- previously obtained by calling by new_params_list(): |
jbe@0 | 143 local function read_urlencoded_form(tbl, data) |
jbe@12 | 144 for rawkey, rawvalue in string.gmatch(data, "([^?=&]*)=([^?=&]*)") do |
jbe@0 | 145 local subtbl = tbl[decode_uri(rawkey)] |
jbe@0 | 146 subtbl[#subtbl+1] = decode_uri(rawvalue) |
jbe@0 | 147 end |
jbe@0 | 148 end |
jbe@0 | 149 |
jbe@0 | 150 -- function creating a HTTP handler: |
jbe@0 | 151 function generate_handler(handler, options) |
jbe@0 | 152 -- swap arguments if necessary (for convenience): |
jbe@0 | 153 if type(handler) ~= "function" and type(options) == "function" then |
jbe@0 | 154 handler, options = options, handler |
jbe@0 | 155 end |
jbe@0 | 156 -- process options: |
jbe@0 | 157 options = options or {} |
jbe@0 | 158 local preamble = "" -- preamble sent with every(!) HTTP response |
jbe@0 | 159 do |
jbe@0 | 160 -- named arg "static_headers" is used to create the preamble: |
jbe@0 | 161 local s = options.static_headers |
jbe@0 | 162 local t = {} |
jbe@0 | 163 if s then |
jbe@0 | 164 if type(s) == "string" then |
jbe@0 | 165 for line in string.gmatch(s, "[^\r\n]+") do |
jbe@0 | 166 t[#t+1] = line |
jbe@0 | 167 end |
jbe@0 | 168 else |
jbe@0 | 169 for i, kv in ipairs(options.static_headers) do |
jbe@0 | 170 if type(kv) == "string" then |
jbe@0 | 171 t[#t+1] = kv |
jbe@0 | 172 else |
jbe@0 | 173 t[#t+1] = kv[1] .. ": " .. kv[2] |
jbe@0 | 174 end |
jbe@0 | 175 end |
jbe@0 | 176 end |
jbe@0 | 177 end |
jbe@0 | 178 t[#t+1] = "" |
jbe@0 | 179 preamble = table.concat(t, "\r\n") |
jbe@0 | 180 end |
jbe@44 | 181 local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384 |
jbe@44 | 182 local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 |
jbe@115 | 183 local request_idle_timeout, request_header_timeout, response_timeout |
jbe@115 | 184 if options.request_idle_timeout ~= nil then |
jbe@115 | 185 request_idle_timeout = options.request_idle_timeout or 0 |
jbe@115 | 186 else |
jbe@115 | 187 request_idle_timeout = 330 |
jbe@115 | 188 end |
jbe@78 | 189 if options.request_header_timeout ~= nil then |
jbe@115 | 190 request_header_timeout = options.request_header_timeout or 0 |
jbe@66 | 191 else |
jbe@115 | 192 request_header_timeout = 30 |
jbe@53 | 193 end |
jbe@54 | 194 if options.timeout ~= nil then |
jbe@54 | 195 response_timeout = options.timeout or 0 |
jbe@54 | 196 else |
jbe@54 | 197 response_timeout = 1800 |
jbe@53 | 198 end |
jbe@0 | 199 -- return connect handler: |
jbe@0 | 200 return function(socket) |
jbe@115 | 201 local socket_set = {[socket] = true} -- used for moonbridge_io.poll(...) |
jbe@0 | 202 local survive = true -- set to false if process shall be terminated later |
jbe@31 | 203 repeat |
jbe@0 | 204 -- process named arguments "request_header_size_limit" and "request_body_size_limit": |
jbe@0 | 205 local remaining_header_size_limit = options.request_header_size_limit or 1024*1024 |
jbe@0 | 206 local remaining_body_size_limit = options.request_body_size_limit or 64*1024*1024 |
jbe@0 | 207 -- state variables for request handling: |
jbe@0 | 208 local output_state = "no_status_sent" -- one of: |
jbe@0 | 209 -- "no_status_sent" (initial state) |
jbe@0 | 210 -- "info_status_sent" (1xx status code has been sent) |
jbe@0 | 211 -- "bodyless_status_sent" (204/304 status code has been sent) |
jbe@0 | 212 -- "status_sent" (regular status code has been sent) |
jbe@0 | 213 -- "headers_sent" (headers have been terminated) |
jbe@0 | 214 -- "finished" (request has been answered completely) |
jbe@0 | 215 local content_length -- value of Content-Length header sent |
jbe@0 | 216 local bytes_sent = 0 -- number of bytes sent if Content-Length is set |
jbe@0 | 217 local chunk_parts = {} -- list of chunks to send |
jbe@0 | 218 local chunk_bytes = 0 -- sum of lengths of chunks to send |
jbe@0 | 219 local connection_close_requested = false |
jbe@0 | 220 local connection_close_responded = false |
jbe@0 | 221 local headers_value_nil = {} -- header values that are nil |
jbe@0 | 222 local request_body_content_length -- Content-Length of request body |
jbe@0 | 223 local input_state = "pending" -- one of: |
jbe@37 | 224 -- "pending" (request body has not been processed yet) |
jbe@37 | 225 -- "deferred" (request body processing is deferred) |
jbe@37 | 226 -- "inprogress" (request body is currently being read) |
jbe@37 | 227 -- "finished" (request body has been read) |
jbe@0 | 228 local streamed_post_params = {} -- mapping from POST field name to stream function |
jbe@0 | 229 local streamed_post_param_patterns = {} -- list of POST field pattern and stream function pairs |
jbe@0 | 230 -- object passed to handler (with methods, GET/POST params, etc.): |
jbe@0 | 231 local request |
jbe@50 | 232 -- handling I/O errors (including protocol violations): |
jbe@50 | 233 local socket_closed = false |
jbe@50 | 234 local function assert_output(retval, errmsg) |
jbe@50 | 235 if retval then |
jbe@50 | 236 return retval |
jbe@50 | 237 end |
jbe@50 | 238 request.faulty = true |
jbe@50 | 239 errmsg = "Could not send data to client: " .. errmsg |
jbe@50 | 240 io.stderr:write(errmsg, "\n") |
jbe@50 | 241 if not socket_closed then |
jbe@50 | 242 socket_closed = true |
jbe@88 | 243 socket:reset() |
jbe@50 | 244 end |
jbe@50 | 245 error("Could not send data to client: " .. errmsg) |
jbe@50 | 246 end |
jbe@50 | 247 local function request_error(throw_error, status, text) |
jbe@50 | 248 local errmsg = "Error while reading request from client. Error response: " .. status |
jbe@50 | 249 if text then |
jbe@50 | 250 errmsg = errmsg .. " (" .. text .. ")" |
jbe@50 | 251 end |
jbe@50 | 252 io.stderr:write(errmsg, "\n") -- needs to be written now, because of possible timeout error later |
jbe@50 | 253 if |
jbe@50 | 254 output_state == "no_status_sent" or |
jbe@50 | 255 output_state == "info_status_sent" |
jbe@50 | 256 then |
jbe@50 | 257 local error_response_status, errmsg2 = pcall(function() |
jbe@50 | 258 request:defer_reading() -- don't read request body (because of possibly invalid state) |
jbe@60 | 259 request:close_after_finish() -- send "Connection: close" header |
jbe@50 | 260 request:send_status(status) |
jbe@50 | 261 request:send_header("Content-Type", "text/plain") |
jbe@50 | 262 request:send_data(status, "\n") |
jbe@50 | 263 if text then |
jbe@50 | 264 request:send_data("\n", text, "\n") |
jbe@50 | 265 end |
jbe@50 | 266 request:finish() |
jbe@50 | 267 end) |
jbe@50 | 268 if not error_response_status and not request.faulty then |
jbe@50 | 269 request.faulty = true |
jbe@50 | 270 error("Unexpected error while sending error response: " .. errmsg2) |
jbe@50 | 271 end |
jbe@51 | 272 else |
jbe@51 | 273 if not socket_closed then |
jbe@51 | 274 socket_closed = true |
jbe@88 | 275 socket:reset() |
jbe@51 | 276 end |
jbe@50 | 277 end |
jbe@50 | 278 if throw_error then |
jbe@50 | 279 request.faulty = true |
jbe@50 | 280 error(errmsg) |
jbe@50 | 281 else |
jbe@50 | 282 return survive |
jbe@50 | 283 end |
jbe@50 | 284 end |
jbe@50 | 285 local function assert_not_faulty() |
jbe@50 | 286 assert(not request.faulty, "Tried to use faulty request handle") |
jbe@50 | 287 end |
jbe@0 | 288 -- reads a number of bytes from the socket, |
jbe@0 | 289 -- optionally feeding these bytes chunk-wise |
jbe@0 | 290 -- into a callback function: |
jbe@0 | 291 local function read_body_bytes(remaining, callback) |
jbe@0 | 292 while remaining > 0 do |
jbe@0 | 293 local limit |
jbe@0 | 294 if remaining > input_chunk_size then |
jbe@0 | 295 limit = input_chunk_size |
jbe@0 | 296 else |
jbe@0 | 297 limit = remaining |
jbe@0 | 298 end |
jbe@0 | 299 local chunk = socket:read(limit) |
jbe@0 | 300 if not chunk or #chunk ~= limit then |
jbe@50 | 301 request_error(true, "400 Bad Request", "Unexpected EOF or read error while reading chunk of request body") |
jbe@0 | 302 end |
jbe@0 | 303 remaining = remaining - limit |
jbe@0 | 304 if callback then |
jbe@0 | 305 callback(chunk) |
jbe@0 | 306 end |
jbe@0 | 307 end |
jbe@0 | 308 end |
jbe@0 | 309 -- flushes or closes the socket (depending on |
jbe@0 | 310 -- whether "Connection: close" header was sent): |
jbe@0 | 311 local function finish_response() |
jbe@0 | 312 if connection_close_responded then |
jbe@0 | 313 -- close output stream: |
jbe@88 | 314 assert_output(socket:finish()) |
jbe@0 | 315 -- wait for EOF of peer to avoid immediate TCP RST condition: |
jbe@115 | 316 do |
jbe@115 | 317 local start_time = moonbridge_io.timeref() |
jbe@115 | 318 repeat |
jbe@115 | 319 socket:drain_nb() |
jbe@115 | 320 local time_left = 2 - moonbridge_io.timeref(start_time) |
jbe@115 | 321 until time_left <= 0 or not moonbridge_io.poll(socket_set, nil, time_left) |
jbe@115 | 322 end |
jbe@0 | 323 -- fully close socket: |
jbe@45 | 324 socket_closed = true -- avoid double close on error |
jbe@50 | 325 assert_output(socket:close()) |
jbe@0 | 326 else |
jbe@50 | 327 assert_output(socket:flush()) |
jbe@0 | 328 request:stream_request_body() |
jbe@0 | 329 end |
jbe@0 | 330 end |
jbe@0 | 331 -- writes out buffered chunks (without flushing the socket): |
jbe@0 | 332 local function send_chunk() |
jbe@0 | 333 if chunk_bytes > 0 then |
jbe@50 | 334 assert_output(socket:write(string.format("%x\r\n", chunk_bytes))) |
jbe@0 | 335 for i = 1, #chunk_parts do |
jbe@50 | 336 assert_output(socket:write(chunk_parts[i])) |
jbe@0 | 337 end |
jbe@0 | 338 chunk_parts = {} |
jbe@0 | 339 chunk_bytes = 0 |
jbe@50 | 340 assert_output(socket:write("\r\n")) |
jbe@0 | 341 end |
jbe@0 | 342 end |
jbe@0 | 343 -- terminate header section in response, optionally flushing: |
jbe@0 | 344 -- (may be called multiple times unless response is finished) |
jbe@0 | 345 local function finish_headers(flush) |
jbe@0 | 346 if output_state == "no_status_sent" then |
jbe@0 | 347 error("HTTP status has not been sent yet") |
jbe@0 | 348 elseif output_state == "finished" then |
jbe@0 | 349 error("Response has already been finished") |
jbe@0 | 350 elseif output_state == "info_status_sent" then |
jbe@50 | 351 assert_output(socket:write("\r\n")) |
jbe@50 | 352 assert_output(socket:flush()) |
jbe@0 | 353 output_state = "no_status_sent" |
jbe@0 | 354 elseif output_state == "bodyless_status_sent" then |
jbe@0 | 355 if connection_close_requested and not connection_close_responded then |
jbe@0 | 356 request:send_header("Connection", "close") |
jbe@0 | 357 end |
jbe@50 | 358 assert_output(socket:write("\r\n")) |
jbe@0 | 359 finish_response() |
jbe@0 | 360 output_state = "finished" |
jbe@0 | 361 elseif output_state == "status_sent" then |
jbe@0 | 362 if not content_length then |
jbe@50 | 363 assert_output(socket:write("Transfer-Encoding: chunked\r\n")) |
jbe@0 | 364 end |
jbe@0 | 365 if connection_close_requested and not connection_close_responded then |
jbe@0 | 366 request:send_header("Connection", "close") |
jbe@0 | 367 end |
jbe@50 | 368 assert_output(socket:write("\r\n")) |
jbe@0 | 369 if request.method == "HEAD" then |
jbe@0 | 370 finish_response() |
jbe@0 | 371 elseif flush then |
jbe@50 | 372 assert_output(socket:flush()) |
jbe@0 | 373 end |
jbe@0 | 374 output_state = "headers_sent" |
jbe@0 | 375 elseif output_state ~= "headers_sent" then |
jbe@0 | 376 error("Unexpected internal status in HTTP engine") |
jbe@0 | 377 end |
jbe@0 | 378 end |
jbe@0 | 379 -- create request object and set several functions and values: |
jbe@0 | 380 request = { |
jbe@50 | 381 -- error state: |
jbe@50 | 382 faulty = false, |
jbe@0 | 383 -- allow raw socket access: |
jbe@0 | 384 socket = socket, |
jbe@0 | 385 -- parsed cookies: |
jbe@0 | 386 cookies = {}, |
jbe@0 | 387 -- send a HTTP response status (e.g. "200 OK"): |
jbe@0 | 388 send_status = function(self, value) |
jbe@50 | 389 assert_not_faulty() |
jbe@0 | 390 if input_state == "pending" then |
jbe@0 | 391 request:process_request_body() |
jbe@0 | 392 end |
jbe@0 | 393 if output_state == "info_status_sent" then |
jbe@50 | 394 assert_output(socket:write("\r\n")) |
jbe@50 | 395 assert_output(socket:flush()) |
jbe@0 | 396 elseif output_state ~= "no_status_sent" then |
jbe@0 | 397 error("HTTP status has already been sent") |
jbe@0 | 398 end |
jbe@0 | 399 local status1 = string.sub(value, 1, 1) |
jbe@0 | 400 local status3 = string.sub(value, 1, 3) |
jbe@50 | 401 assert_output(socket:write("HTTP/1.1 ", value, "\r\n", preamble)) |
jbe@0 | 402 local without_response_body = status_without_response_body[status3] |
jbe@0 | 403 if without_response_body then |
jbe@0 | 404 output_state = "bodyless_status_sent" |
jbe@0 | 405 if without_response_body == "zero_content_length" then |
jbe@0 | 406 request:send_header("Content-Length", 0) |
jbe@0 | 407 end |
jbe@0 | 408 elseif status1 == "1" then |
jbe@0 | 409 output_state = "info_status_sent" |
jbe@0 | 410 else |
jbe@0 | 411 output_state = "status_sent" |
jbe@0 | 412 end |
jbe@0 | 413 end, |
jbe@0 | 414 -- send a HTTP response header |
jbe@0 | 415 -- (key and value as separate args): |
jbe@0 | 416 send_header = function(self, key, value) |
jbe@50 | 417 assert_not_faulty() |
jbe@0 | 418 if output_state == "no_status_sent" then |
jbe@0 | 419 error("HTTP status has not been sent yet") |
jbe@0 | 420 elseif |
jbe@0 | 421 output_state ~= "info_status_sent" and |
jbe@0 | 422 output_state ~= "bodyless_status_sent" and |
jbe@0 | 423 output_state ~= "status_sent" |
jbe@0 | 424 then |
jbe@0 | 425 error("All HTTP headers have already been sent") |
jbe@0 | 426 end |
jbe@0 | 427 local key_lower = string.lower(key) |
jbe@0 | 428 if key_lower == "content-length" then |
jbe@0 | 429 if output_state == "info_status_sent" then |
jbe@0 | 430 error("Cannot set Content-Length for informational status response") |
jbe@0 | 431 end |
jbe@0 | 432 local new_content_length = assert(tonumber(value), "Invalid content-length") |
jbe@0 | 433 if content_length == nil then |
jbe@0 | 434 content_length = new_content_length |
jbe@0 | 435 elseif content_length == new_content_length then |
jbe@0 | 436 return |
jbe@0 | 437 else |
jbe@0 | 438 error("Content-Length has been set multiple times with different values") |
jbe@0 | 439 end |
jbe@0 | 440 elseif key_lower == "connection" then |
jbe@0 | 441 for entry in string.gmatch(string.lower(value), "[^,]+") do |
jbe@0 | 442 if string.match(entry, "^[ \t]*close[ \t]*$") then |
jbe@0 | 443 if output_state == "info_status_sent" then |
jbe@0 | 444 error("Cannot set \"Connection: close\" for informational status response") |
jbe@0 | 445 end |
jbe@59 | 446 connection_close_responded = true |
jbe@59 | 447 break |
jbe@0 | 448 end |
jbe@0 | 449 end |
jbe@0 | 450 end |
jbe@50 | 451 assert_output(socket:write(key, ": ", value, "\r\n")) |
jbe@0 | 452 end, |
jbe@60 | 453 -- method to announce (and enforce) connection close after sending the response: |
jbe@60 | 454 close_after_finish = function() |
jbe@60 | 455 assert_not_faulty() |
jbe@60 | 456 if |
jbe@60 | 457 output_state == "headers_sent" or |
jbe@60 | 458 output_state == "finished" |
jbe@60 | 459 then |
jbe@60 | 460 error("All HTTP headers have already been sent") |
jbe@60 | 461 end |
jbe@60 | 462 connection_close_requested = true |
jbe@60 | 463 end, |
jbe@0 | 464 -- method to finish and flush headers: |
jbe@0 | 465 finish_headers = function() |
jbe@50 | 466 assert_not_faulty() |
jbe@0 | 467 finish_headers(true) |
jbe@0 | 468 end, |
jbe@0 | 469 -- send data for response body: |
jbe@0 | 470 send_data = function(self, ...) |
jbe@50 | 471 assert_not_faulty() |
jbe@0 | 472 if output_state == "info_status_sent" then |
jbe@0 | 473 error("No (non-informational) HTTP status has been sent yet") |
jbe@0 | 474 elseif output_state == "bodyless_status_sent" then |
jbe@0 | 475 error("Cannot send response data for body-less status message") |
jbe@0 | 476 end |
jbe@0 | 477 finish_headers(false) |
jbe@0 | 478 if output_state ~= "headers_sent" then |
jbe@0 | 479 error("Unexpected internal status in HTTP engine") |
jbe@0 | 480 end |
jbe@0 | 481 if request.method == "HEAD" then |
jbe@0 | 482 return |
jbe@0 | 483 end |
jbe@0 | 484 for i = 1, select("#", ...) do |
jbe@0 | 485 local str = tostring(select(i, ...)) |
jbe@0 | 486 if #str > 0 then |
jbe@0 | 487 if content_length then |
jbe@0 | 488 local bytes_to_send = #str |
jbe@0 | 489 if bytes_sent + bytes_to_send > content_length then |
jbe@50 | 490 assert_output(socket:write(string.sub(str, 1, content_length - bytes_sent))) |
jbe@0 | 491 bytes_sent = content_length |
jbe@0 | 492 error("Content length exceeded") |
jbe@0 | 493 else |
jbe@50 | 494 assert_output(socket:write(str)) |
jbe@0 | 495 bytes_sent = bytes_sent + bytes_to_send |
jbe@0 | 496 end |
jbe@0 | 497 else |
jbe@0 | 498 chunk_bytes = chunk_bytes + #str |
jbe@0 | 499 chunk_parts[#chunk_parts+1] = str |
jbe@0 | 500 end |
jbe@0 | 501 end |
jbe@0 | 502 end |
jbe@0 | 503 if chunk_bytes >= output_chunk_size then |
jbe@0 | 504 send_chunk() |
jbe@0 | 505 end |
jbe@0 | 506 end, |
jbe@0 | 507 -- flush output buffer: |
jbe@0 | 508 flush = function(self) |
jbe@50 | 509 assert_not_faulty() |
jbe@0 | 510 send_chunk() |
jbe@50 | 511 assert_output(socket:flush()) |
jbe@0 | 512 end, |
jbe@0 | 513 -- finish response: |
jbe@0 | 514 finish = function(self) |
jbe@50 | 515 assert_not_faulty() |
jbe@0 | 516 if output_state == "finished" then |
jbe@0 | 517 return |
jbe@0 | 518 elseif output_state == "info_status_sent" then |
jbe@0 | 519 error("Informational HTTP response can be finished with :finish_headers() method") |
jbe@0 | 520 end |
jbe@0 | 521 finish_headers(false) |
jbe@0 | 522 if output_state == "headers_sent" then |
jbe@0 | 523 if request.method ~= "HEAD" then |
jbe@0 | 524 if content_length then |
jbe@0 | 525 if bytes_sent ~= content_length then |
jbe@0 | 526 error("Content length not used") |
jbe@0 | 527 end |
jbe@0 | 528 else |
jbe@0 | 529 send_chunk() |
jbe@50 | 530 assert_output(socket:write("0\r\n\r\n")) |
jbe@0 | 531 end |
jbe@0 | 532 finish_response() |
jbe@0 | 533 end |
jbe@0 | 534 output_state = "finished" |
jbe@28 | 535 elseif output_state ~= "finished" then |
jbe@0 | 536 error("Unexpected internal status in HTTP engine") |
jbe@0 | 537 end |
jbe@0 | 538 end, |
jbe@0 | 539 -- table mapping header field names to value-lists |
jbe@0 | 540 -- (raw access): |
jbe@0 | 541 headers = setmetatable({}, { |
jbe@0 | 542 __index = function(self, key) |
jbe@0 | 543 local lowerkey = string.lower(key) |
jbe@0 | 544 if lowerkey == key then |
jbe@0 | 545 return |
jbe@0 | 546 end |
jbe@0 | 547 local result = rawget(self, lowerkey) |
jbe@0 | 548 if result == nil then |
jbe@0 | 549 result = {} |
jbe@0 | 550 end |
jbe@0 | 551 self[lowerkey] = result |
jbe@0 | 552 self[key] = result |
jbe@0 | 553 return result |
jbe@0 | 554 end |
jbe@0 | 555 }), |
jbe@0 | 556 -- table mapping header field names to value-lists |
jbe@0 | 557 -- (for headers with comma separated values): |
jbe@0 | 558 headers_csv_table = setmetatable({}, { |
jbe@0 | 559 __index = function(self, key) |
jbe@0 | 560 local result = {} |
jbe@0 | 561 for i, line in ipairs(request.headers[key]) do |
jbe@0 | 562 for entry in string.gmatch(line, "[^,]+") do |
jbe@0 | 563 local value = string.match(entry, "^[ \t]*(..-)[ \t]*$") |
jbe@0 | 564 if value then |
jbe@0 | 565 result[#result+1] = value |
jbe@0 | 566 end |
jbe@0 | 567 end |
jbe@0 | 568 end |
jbe@0 | 569 self[key] = result |
jbe@0 | 570 return result |
jbe@0 | 571 end |
jbe@0 | 572 }), |
jbe@0 | 573 -- table mapping header field names to a comma separated string |
jbe@0 | 574 -- (for headers with comma separated values): |
jbe@0 | 575 headers_csv_string = setmetatable({}, { |
jbe@0 | 576 __index = function(self, key) |
jbe@0 | 577 local result = {} |
jbe@0 | 578 for i, line in ipairs(request.headers[key]) do |
jbe@0 | 579 result[#result+1] = line |
jbe@0 | 580 end |
jbe@0 | 581 result = string.concat(result, ", ") |
jbe@0 | 582 self[key] = result |
jbe@0 | 583 return result |
jbe@0 | 584 end |
jbe@0 | 585 }), |
jbe@0 | 586 -- table mapping header field names to a single string value |
jbe@0 | 587 -- (or false if header has been sent multiple times): |
jbe@0 | 588 headers_value = setmetatable({}, { |
jbe@0 | 589 __index = function(self, key) |
jbe@0 | 590 if headers_value_nil[key] then |
jbe@0 | 591 return nil |
jbe@0 | 592 end |
jbe@0 | 593 local result = nil |
jbe@0 | 594 local values = request.headers_csv_table[key] |
jbe@0 | 595 if #values == 0 then |
jbe@0 | 596 headers_value_nil[key] = true |
jbe@0 | 597 elseif #values == 1 then |
jbe@0 | 598 result = values[1] |
jbe@0 | 599 else |
jbe@0 | 600 result = false |
jbe@0 | 601 end |
jbe@0 | 602 self[key] = result |
jbe@0 | 603 return result |
jbe@0 | 604 end |
jbe@0 | 605 }), |
jbe@0 | 606 -- table mapping header field names to a flag table, |
jbe@0 | 607 -- indicating if the comma separated value contains certain entries: |
jbe@0 | 608 headers_flags = setmetatable({}, { |
jbe@0 | 609 __index = function(self, key) |
jbe@0 | 610 local result = setmetatable({}, { |
jbe@0 | 611 __index = function(self, key) |
jbe@0 | 612 local lowerkey = string.lower(key) |
jbe@0 | 613 local result = rawget(self, lowerkey) or false |
jbe@0 | 614 self[lowerkey] = result |
jbe@0 | 615 self[key] = result |
jbe@0 | 616 return result |
jbe@0 | 617 end |
jbe@0 | 618 }) |
jbe@0 | 619 for i, value in ipairs(request.headers_csv_table[key]) do |
jbe@0 | 620 result[string.lower(value)] = true |
jbe@0 | 621 end |
jbe@0 | 622 self[key] = result |
jbe@0 | 623 return result |
jbe@0 | 624 end |
jbe@0 | 625 }), |
jbe@0 | 626 -- register POST param stream handler for a single field name: |
jbe@0 | 627 stream_post_param = function(self, field_name, callback) |
jbe@50 | 628 assert_not_faulty() |
jbe@0 | 629 if input_state == "inprogress" or input_state == "finished" then |
jbe@0 | 630 error("Cannot register POST param streaming function if request body is already processed") |
jbe@0 | 631 end |
jbe@0 | 632 streamed_post_params[field_name] = callback |
jbe@0 | 633 end, |
jbe@0 | 634 -- register POST param stream handler for a field name pattern: |
jbe@0 | 635 stream_post_params = function(self, pattern, callback) |
jbe@50 | 636 assert_not_faulty() |
jbe@0 | 637 if input_state == "inprogress" or input_state == "finished" then |
jbe@0 | 638 error("Cannot register POST param streaming function if request body is already processed") |
jbe@0 | 639 end |
jbe@0 | 640 streamed_post_param_patterns[#streamed_post_param_patterns+1] = {pattern, callback} |
jbe@0 | 641 end, |
jbe@0 | 642 -- disables automatic request body processing on write |
jbe@0 | 643 -- (use with caution): |
jbe@0 | 644 defer_reading = function(self) |
jbe@50 | 645 assert_not_faulty() |
jbe@0 | 646 if input_state == "pending" then |
jbe@0 | 647 input_state = "deferred" |
jbe@0 | 648 end |
jbe@0 | 649 end, |
jbe@0 | 650 -- processes the request body and sets the request.post_params, |
jbe@0 | 651 -- request.post_params_list, request.meta_post_params, and |
jbe@0 | 652 -- request.meta_post_params_list values (can be called manually or |
jbe@0 | 653 -- automatically if post_params are accessed or data is written out) |
jbe@0 | 654 process_request_body = function(self) |
jbe@50 | 655 assert_not_faulty() |
jbe@0 | 656 if input_state == "finished" then |
jbe@0 | 657 return |
jbe@0 | 658 end |
jbe@0 | 659 local post_params_list, post_params = new_params_list() |
jbe@0 | 660 local content_type = request.headers_value["Content-Type"] |
jbe@0 | 661 if content_type then |
jbe@0 | 662 if |
jbe@0 | 663 content_type == "application/x-www-form-urlencoded" or |
jbe@0 | 664 string.match(content_type, "^application/x%-www%-form%-urlencoded *;") |
jbe@0 | 665 then |
jbe@0 | 666 read_urlencoded_form(post_params_list, request.body) |
jbe@0 | 667 else |
jbe@0 | 668 local boundary = string.match( |
jbe@0 | 669 content_type, |
jbe@0 | 670 '^multipart/form%-data[ \t]*[;,][ \t]*boundary="([^"]+)"$' |
jbe@0 | 671 ) or string.match( |
jbe@0 | 672 content_type, |
jbe@0 | 673 '^multipart/form%-data[ \t]*[;,][ \t]*boundary=([^"; \t]+)$' |
jbe@0 | 674 ) |
jbe@0 | 675 if boundary then |
jbe@0 | 676 local post_metadata_list, post_metadata = new_params_list() |
jbe@0 | 677 boundary = "--" .. boundary |
jbe@0 | 678 local headerdata = "" |
jbe@0 | 679 local streamer |
jbe@0 | 680 local field_name |
jbe@0 | 681 local metadata = {} |
jbe@0 | 682 local value_parts |
jbe@0 | 683 local function default_streamer(chunk) |
jbe@0 | 684 value_parts[#value_parts+1] = chunk |
jbe@0 | 685 end |
jbe@0 | 686 local function stream_part_finish() |
jbe@0 | 687 if streamer == default_streamer then |
jbe@0 | 688 local value = table.concat(value_parts) |
jbe@0 | 689 value_parts = nil |
jbe@0 | 690 if field_name then |
jbe@0 | 691 local values = post_params_list[field_name] |
jbe@0 | 692 values[#values+1] = value |
jbe@0 | 693 local metadata_entries = post_metadata_list[field_name] |
jbe@0 | 694 metadata_entries[#metadata_entries+1] = metadata |
jbe@0 | 695 end |
jbe@0 | 696 else |
jbe@0 | 697 streamer() |
jbe@0 | 698 end |
jbe@0 | 699 headerdata = "" |
jbe@0 | 700 streamer = nil |
jbe@0 | 701 field_name = nil |
jbe@0 | 702 metadata = {} |
jbe@0 | 703 end |
jbe@0 | 704 local function stream_part_chunk(chunk) |
jbe@0 | 705 if streamer then |
jbe@0 | 706 streamer(chunk) |
jbe@0 | 707 else |
jbe@0 | 708 headerdata = headerdata .. chunk |
jbe@0 | 709 while true do |
jbe@0 | 710 local line, remaining = string.match(headerdata, "^(.-)\r?\n(.*)$") |
jbe@0 | 711 if not line then |
jbe@0 | 712 break |
jbe@0 | 713 end |
jbe@0 | 714 if line == "" then |
jbe@0 | 715 streamer = streamed_post_params[field_name] |
jbe@0 | 716 if not streamer then |
jbe@0 | 717 for i, rule in ipairs(streamed_post_param_patterns) do |
jbe@0 | 718 if string.match(field_name, rule[1]) then |
jbe@0 | 719 streamer = rule[2] |
jbe@0 | 720 break |
jbe@0 | 721 end |
jbe@0 | 722 end |
jbe@0 | 723 end |
jbe@0 | 724 if not streamer then |
jbe@0 | 725 value_parts = {} |
jbe@0 | 726 streamer = default_streamer |
jbe@0 | 727 end |
jbe@0 | 728 streamer(remaining, field_name, metadata) |
jbe@0 | 729 return |
jbe@0 | 730 end |
jbe@0 | 731 headerdata = remaining |
jbe@0 | 732 local header_key, header_value = string.match(line, "^([^:]*):[ \t]*(.-)[ \t]*$") |
jbe@0 | 733 if not header_key then |
jbe@50 | 734 request_error(true, "400 Bad Request", "Invalid header in multipart/form-data part") |
jbe@0 | 735 end |
jbe@0 | 736 header_key = string.lower(header_key) |
jbe@0 | 737 if header_key == "content-disposition" then |
jbe@0 | 738 local escaped_header_value = string.gsub(header_value, '"[^"]*"', function(str) |
jbe@0 | 739 return string.gsub(str, "=", "==") |
jbe@0 | 740 end) |
jbe@0 | 741 field_name = string.match(escaped_header_value, ';[ \t]*name="([^"]*)"') |
jbe@0 | 742 if field_name then |
jbe@0 | 743 field_name = string.gsub(field_name, "==", "=") |
jbe@0 | 744 else |
jbe@0 | 745 field_name = string.match(header_value, ';[ \t]*name=([^"; \t]+)') |
jbe@0 | 746 end |
jbe@0 | 747 metadata.file_name = string.match(escaped_header_value, ';[ \t]*filename="([^"]*)"') |
jbe@0 | 748 if metadata.file_name then |
jbe@0 | 749 metadata.file_name = string.gsub(metadata.file_name, "==", "=") |
jbe@0 | 750 else |
jbe@0 | 751 string.match(header_value, ';[ \t]*filename=([^"; \t]+)') |
jbe@0 | 752 end |
jbe@0 | 753 elseif header_key == "content-type" then |
jbe@0 | 754 metadata.content_type = header_value |
jbe@0 | 755 elseif header_key == "content-transfer-encoding" then |
jbe@50 | 756 request_error(true, "400 Bad Request", "Content-transfer-encoding not supported by multipart/form-data parser") |
jbe@0 | 757 end |
jbe@0 | 758 end |
jbe@0 | 759 end |
jbe@0 | 760 end |
jbe@0 | 761 local skippart = true -- ignore data until first boundary |
jbe@0 | 762 local afterbound = false -- interpret 2 bytes after boundary ("\r\n" or "--") |
jbe@0 | 763 local terminated = false -- final boundary read |
jbe@0 | 764 local bigchunk = "" |
jbe@0 | 765 request:stream_request_body(function(chunk) |
jbe@0 | 766 if terminated then |
jbe@0 | 767 return |
jbe@0 | 768 end |
jbe@0 | 769 bigchunk = bigchunk .. chunk |
jbe@0 | 770 while true do |
jbe@0 | 771 if afterbound then |
jbe@0 | 772 if #bigchunk <= 2 then |
jbe@0 | 773 return |
jbe@0 | 774 end |
jbe@0 | 775 local terminator = string.sub(bigchunk, 1, 2) |
jbe@0 | 776 if terminator == "\r\n" then |
jbe@0 | 777 afterbound = false |
jbe@0 | 778 bigchunk = string.sub(bigchunk, 3) |
jbe@0 | 779 elseif terminator == "--" then |
jbe@0 | 780 terminated = true |
jbe@0 | 781 bigchunk = nil |
jbe@0 | 782 return |
jbe@0 | 783 else |
jbe@50 | 784 request_error(true, "400 Bad Request", "Error while parsing multipart body (expected CRLF or double minus)") |
jbe@0 | 785 end |
jbe@0 | 786 end |
jbe@0 | 787 local pos1, pos2 = string.find(bigchunk, boundary, 1, true) |
jbe@0 | 788 if not pos1 then |
jbe@0 | 789 if not skippart then |
jbe@0 | 790 local safe = #bigchunk-#boundary |
jbe@0 | 791 if safe > 0 then |
jbe@0 | 792 stream_part_chunk(string.sub(bigchunk, 1, safe)) |
jbe@0 | 793 bigchunk = string.sub(bigchunk, safe+1) |
jbe@0 | 794 end |
jbe@0 | 795 end |
jbe@0 | 796 return |
jbe@0 | 797 end |
jbe@0 | 798 if not skippart then |
jbe@0 | 799 stream_part_chunk(string.sub(bigchunk, 1, pos1 - 1)) |
jbe@0 | 800 stream_part_finish() |
jbe@0 | 801 else |
jbe@0 | 802 boundary = "\r\n" .. boundary |
jbe@0 | 803 skippart = false |
jbe@0 | 804 end |
jbe@0 | 805 bigchunk = string.sub(bigchunk, pos2 + 1) |
jbe@0 | 806 afterbound = true |
jbe@0 | 807 end |
jbe@0 | 808 end) |
jbe@0 | 809 if not terminated then |
jbe@50 | 810 request_error(true, "400 Bad Request", "Premature end of multipart/form-data request body") |
jbe@0 | 811 end |
jbe@0 | 812 request.post_metadata_list, request.post_metadata = post_metadata_list, post_metadata |
jbe@0 | 813 else |
jbe@50 | 814 request_error(true, "415 Unsupported Media Type", "Unknown Content-Type of request body") |
jbe@0 | 815 end |
jbe@0 | 816 end |
jbe@0 | 817 end |
jbe@0 | 818 request.post_params_list, request.post_params = post_params_list, post_params |
jbe@0 | 819 end, |
jbe@0 | 820 -- stream request body to an (optional) callback function |
jbe@0 | 821 -- without processing it otherwise: |
jbe@0 | 822 stream_request_body = function(self, callback) |
jbe@50 | 823 assert_not_faulty() |
jbe@0 | 824 if input_state ~= "pending" and input_state ~= "deferred" then |
jbe@0 | 825 if callback then |
jbe@0 | 826 if input_state == "inprogress" then |
jbe@48 | 827 error("Request body is currently being processed") |
jbe@0 | 828 else |
jbe@0 | 829 error("Request body has already been processed") |
jbe@0 | 830 end |
jbe@0 | 831 end |
jbe@0 | 832 return |
jbe@0 | 833 end |
jbe@0 | 834 input_state = "inprogress" |
jbe@0 | 835 if request.headers_flags["Expect"]["100-continue"] then |
jbe@0 | 836 request:send_status("100 Continue") |
jbe@0 | 837 request:finish_headers() |
jbe@0 | 838 end |
jbe@0 | 839 if request.headers_flags["Transfer-Encoding"]["chunked"] then |
jbe@0 | 840 while true do |
jbe@88 | 841 local line = socket:read(32 + remaining_body_size_limit, "\n") |
jbe@46 | 842 if not line then |
jbe@50 | 843 request_error(true, "400 Bad Request", "Unexpected EOF while reading next chunk of request body") |
jbe@0 | 844 end |
jbe@0 | 845 local zeros, lenstr = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)\r?\n$") |
jbe@0 | 846 local chunkext |
jbe@0 | 847 if lenstr then |
jbe@0 | 848 chunkext = "" |
jbe@0 | 849 else |
jbe@0 | 850 zeros, lenstr, chunkext = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)([ \t;].-)\r?\n$") |
jbe@0 | 851 end |
jbe@0 | 852 if not lenstr or #lenstr > 13 then |
jbe@50 | 853 request_error(true, "400 Bad Request", "Encoding error or unexpected EOF or read error while reading chunk of request body") |
jbe@0 | 854 end |
jbe@0 | 855 local len = tonumber("0x" .. lenstr) |
jbe@0 | 856 remaining_body_size_limit = remaining_body_size_limit - (#zeros + #chunkext + len) |
jbe@0 | 857 if remaining_body_size_limit < 0 then |
jbe@50 | 858 request_error(true, "413 Request Entity Too Large", "Request body size limit exceeded") |
jbe@0 | 859 end |
jbe@0 | 860 if len == 0 then break end |
jbe@0 | 861 read_body_bytes(len, callback) |
jbe@88 | 862 local term = socket:read(2, "\n") |
jbe@0 | 863 if term ~= "\r\n" and term ~= "\n" then |
jbe@50 | 864 request_error(true, "400 Bad Request", "Encoding error while reading chunk of request body") |
jbe@0 | 865 end |
jbe@0 | 866 end |
jbe@0 | 867 while true do |
jbe@88 | 868 local line = socket:read(2 + remaining_body_size_limit, "\n") |
jbe@0 | 869 if line == "\r\n" or line == "\n" then break end |
jbe@0 | 870 remaining_body_size_limit = remaining_body_size_limit - #line |
jbe@0 | 871 if remaining_body_size_limit < 0 then |
jbe@50 | 872 request_error(true, "413 Request Entity Too Large", "Request body size limit exceeded while reading trailer section of chunked request body") |
jbe@0 | 873 end |
jbe@0 | 874 end |
jbe@0 | 875 elseif request_body_content_length then |
jbe@0 | 876 read_body_bytes(request_body_content_length, callback) |
jbe@0 | 877 end |
jbe@0 | 878 input_state = "finished" |
jbe@0 | 879 end |
jbe@0 | 880 } |
jbe@0 | 881 -- initialize tables for GET params in request object: |
jbe@0 | 882 request.get_params_list, request.get_params = new_params_list() |
jbe@0 | 883 -- add meta table to request object to allow access to "body" and POST params: |
jbe@0 | 884 setmetatable(request, { |
jbe@0 | 885 __index = function(self, key) |
jbe@0 | 886 if key == "body" then |
jbe@0 | 887 local chunks = {} |
jbe@0 | 888 request:stream_request_body(function(chunk) |
jbe@0 | 889 chunks[#chunks+1] = chunk |
jbe@0 | 890 end) |
jbe@0 | 891 self.body = table.concat(chunks) |
jbe@0 | 892 return self.body |
jbe@0 | 893 elseif |
jbe@0 | 894 key == "post_params_list" or key == "post_params" or |
jbe@0 | 895 key == "post_metadata_list" or key == "post_metadata" |
jbe@0 | 896 then |
jbe@0 | 897 request:process_request_body() |
jbe@0 | 898 return request[key] |
jbe@0 | 899 end |
jbe@0 | 900 end |
jbe@0 | 901 }) |
jbe@115 | 902 -- wait for input: |
jbe@115 | 903 if not moonbridge_io.poll({[socket] = true}, nil, request_idle_timeout) then |
jbe@115 | 904 return request_error(false, "408 Request Timeout") |
jbe@115 | 905 end |
jbe@115 | 906 -- set timeout for request header processing: |
jbe@115 | 907 timeout(request_header_timeout) |
jbe@0 | 908 -- read and parse request line: |
jbe@88 | 909 local line = socket:read(remaining_header_size_limit, "\n") |
jbe@0 | 910 if not line then return survive end |
jbe@0 | 911 remaining_header_size_limit = remaining_header_size_limit - #line |
jbe@0 | 912 if remaining_header_size_limit == 0 then |
jbe@50 | 913 return request_error(false, "414 Request-URI Too Long") |
jbe@0 | 914 end |
jbe@10 | 915 local target, proto |
jbe@10 | 916 request.method, target, proto = |
jbe@0 | 917 line:match("^([^ \t\r]+)[ \t]+([^ \t\r]+)[ \t]*([^ \t\r]*)[ \t]*\r?\n$") |
jbe@0 | 918 if not request.method then |
jbe@50 | 919 return request_error(false, "400 Bad Request") |
jbe@0 | 920 elseif proto ~= "HTTP/1.1" then |
jbe@50 | 921 return request_error(false, "505 HTTP Version Not Supported") |
jbe@38 | 922 end |
jbe@38 | 923 -- read and parse headers: |
jbe@38 | 924 while true do |
jbe@88 | 925 local line = socket:read(remaining_header_size_limit, "\n"); |
jbe@38 | 926 remaining_header_size_limit = remaining_header_size_limit - #line |
jbe@38 | 927 if not line then |
jbe@50 | 928 return request_error(false, "400 Bad Request") |
jbe@38 | 929 end |
jbe@38 | 930 if line == "\r\n" or line == "\n" then |
jbe@38 | 931 break |
jbe@38 | 932 end |
jbe@38 | 933 if remaining_header_size_limit == 0 then |
jbe@50 | 934 return request_error(false, "431 Request Header Fields Too Large") |
jbe@38 | 935 end |
jbe@38 | 936 local key, value = string.match(line, "^([^ \t\r]+):[ \t]*(.-)[ \t]*\r?\n$") |
jbe@38 | 937 if not key then |
jbe@50 | 938 return request_error(false, "400 Bad Request") |
jbe@38 | 939 end |
jbe@38 | 940 local values = request.headers[key] |
jbe@38 | 941 values[#values+1] = value |
jbe@38 | 942 end |
jbe@38 | 943 -- process "Connection: close" header if existent: |
jbe@38 | 944 connection_close_requested = request.headers_flags["Connection"]["close"] |
jbe@38 | 945 -- process "Content-Length" header if existent: |
jbe@38 | 946 do |
jbe@38 | 947 local values = request.headers_csv_table["Content-Length"] |
jbe@38 | 948 if #values > 0 then |
jbe@38 | 949 request_body_content_length = tonumber(values[1]) |
jbe@38 | 950 local proper_value = tostring(request_body_content_length) |
jbe@38 | 951 for i, value in ipairs(values) do |
jbe@38 | 952 value = string.match(value, "^0*(.*)") |
jbe@38 | 953 if value ~= proper_value then |
jbe@50 | 954 return request_error(false, "400 Bad Request", "Content-Length header(s) invalid") |
jbe@38 | 955 end |
jbe@0 | 956 end |
jbe@38 | 957 if request_body_content_length > remaining_body_size_limit then |
jbe@50 | 958 return request_error(false, "413 Request Entity Too Large", "Announced request body size is too big") |
jbe@0 | 959 end |
jbe@0 | 960 end |
jbe@38 | 961 end |
jbe@38 | 962 -- process "Transfer-Encoding" header if existent: |
jbe@38 | 963 do |
jbe@38 | 964 local flag = request.headers_flags["Transfer-Encoding"]["chunked"] |
jbe@38 | 965 local list = request.headers_csv_table["Transfer-Encoding"] |
jbe@38 | 966 if (flag and #list ~= 1) or (not flag and #list ~= 0) then |
jbe@50 | 967 return request_error(false, "400 Bad Request", "Unexpected Transfer-Encoding") |
jbe@0 | 968 end |
jbe@38 | 969 end |
jbe@38 | 970 -- process "Expect" header if existent: |
jbe@38 | 971 for i, value in ipairs(request.headers_csv_table["Expect"]) do |
jbe@38 | 972 if string.lower(value) ~= "100-continue" then |
jbe@50 | 973 return request_error(false, "417 Expectation Failed", "Unexpected Expect header") |
jbe@10 | 974 end |
jbe@38 | 975 end |
jbe@38 | 976 -- get mandatory Host header according to RFC 7230: |
jbe@38 | 977 request.host = request.headers_value["Host"] |
jbe@38 | 978 if not request.host then |
jbe@50 | 979 return request_error(false, "400 Bad Request", "No valid host header") |
jbe@38 | 980 end |
jbe@38 | 981 -- parse request target: |
jbe@38 | 982 request.path, request.query = string.match(target, "^/([^?]*)(.*)$") |
jbe@38 | 983 if not request.path then |
jbe@38 | 984 local host2 |
jbe@38 | 985 host2, request.path, request.query = string.match(target, "^[Hh][Tt][Tt][Pp]://([^/?]+)/?([^?]*)(.*)$") |
jbe@38 | 986 if host2 then |
jbe@38 | 987 if request.host ~= host2 then |
jbe@50 | 988 return request_error(false, "400 Bad Request", "No valid host header") |
jbe@5 | 989 end |
jbe@38 | 990 elseif not (target == "*" and request.method == "OPTIONS") then |
jbe@50 | 991 return request_error(false, "400 Bad Request", "Invalid request target") |
jbe@5 | 992 end |
jbe@38 | 993 end |
jbe@38 | 994 -- parse GET params: |
jbe@38 | 995 if request.query then |
jbe@38 | 996 read_urlencoded_form(request.get_params_list, request.query) |
jbe@38 | 997 end |
jbe@38 | 998 -- parse cookies: |
jbe@38 | 999 for i, line in ipairs(request.headers["Cookie"]) do |
jbe@38 | 1000 for rawkey, rawvalue in |
jbe@38 | 1001 string.gmatch(line, "([^=; ]*)=([^=; ]*)") |
jbe@38 | 1002 do |
jbe@38 | 1003 request.cookies[decode_uri(rawkey)] = decode_uri(rawvalue) |
jbe@5 | 1004 end |
jbe@0 | 1005 end |
jbe@115 | 1006 -- (re)set timeout for handler: |
jbe@53 | 1007 timeout(response_timeout or 0) |
jbe@38 | 1008 -- call underlying handler and remember boolean result: |
jbe@38 | 1009 if handler(request) ~= true then survive = false end |
jbe@38 | 1010 -- finish request (unless already done by underlying handler): |
jbe@38 | 1011 request:finish() |
jbe@115 | 1012 -- stop timeout timer: |
jbe@115 | 1013 timeout(0) |
jbe@31 | 1014 until connection_close_responded |
jbe@0 | 1015 return survive |
jbe@0 | 1016 end |
jbe@0 | 1017 end |
jbe@0 | 1018 |
jbe@0 | 1019 return _M |
jbe@0 | 1020 |