# HG changeset patch # User jbe # Date 1434565784 -7200 # Node ID 6e80bcf89bd514ced7b15bb106c607c14d1d096f # Parent fb54c76e1484e4fb4092eddeb87a152f87a56981 Added missing header reading/parsing for new HTTP module implementation diff -r fb54c76e1484 -r 6e80bcf89bd5 moonbridge_http.lua --- a/moonbridge_http.lua Tue Jun 16 21:44:32 2015 +0200 +++ b/moonbridge_http.lua Wed Jun 17 20:29:44 2015 +0200 @@ -200,10 +200,10 @@ local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 local header_size_limit = options.header_size_limit or 1024*1024 local body_size_limit = options.body_size_limit or 64*1024*1024 - local request_idle_timeout = default("request_idle_timeout", 330) - local request_header_timeout = default("request_header_timeout", 30) - local request_body_timeout = default("request_body_timeout", 60) - local request_response_timeout = default("request_response_timeout", 1800) + local request_idle_timeout = default("request_idle_timeout", 330) + local request_header_timeout = default("request_header_timeout", 30) + local request_body_timeout = default("request_body_timeout", 60) + local response_timeout = default("response_timeout", 1800) local poll = options.poll_function or moonbridge_io.poll -- return socket handler: return function(socket) @@ -446,7 +446,7 @@ return survive end end - -- read function + -- read functions local function read(...) local data, status = socket:read_yield(...) if data == nil then @@ -457,6 +457,20 @@ end return data end + local function read_eof(...) + local data, status = socket:read_yield(...) + if data == nil then + request_error(true, "400 Bad Request", "Read error") + end + if status == "eof" then + if data == "" then + return nil + else + request_error(true, "400 Bad Request", "Unexpected EOF") + end + end + return data + end -- reads a number of bytes from the socket, -- optionally feeding these bytes chunk-wise -- into a callback function: @@ -953,10 +967,142 @@ return self[key] end end + -- coroutine for reading headers: + local function read_headers() + -- read and parse request line: + local line = read_eof(remaining_header_size_limit, "\n") + if not line then + return false, survive + end + remaining_header_size_limit = remaining_header_size_limit - #line + if remaining_header_size_limit == 0 then + return false, request_error(false, "414 Request-URI Too Long") + end + local target, proto + request.method, target, proto = + line:match("^([^ \t\r]+)[ \t]+([^ \t\r]+)[ \t]*([^ \t\r]*)[ \t]*\r?\n$") + if not request.method then + return false, request_error(false, "400 Bad Request") + elseif proto ~= "HTTP/1.1" then + return false, request_error(false, "505 HTTP Version Not Supported") + end + -- read and parse headers: + while true do + local line = read(remaining_header_size_limit, "\n"); + remaining_header_size_limit = remaining_header_size_limit - #line + if line == "\r\n" or line == "\n" then + break + end + if remaining_header_size_limit == 0 then + return false, request_error(false, "431 Request Header Fields Too Large") + end + local key, value = string.match(line, "^([^ \t\r]+):[ \t]*(.-)[ \t]*\r?\n$") + if not key then + return false, request_error(false, "400 Bad Request") + end + local values = request.headers[key] + values[#values+1] = value + end + return true -- success + end -- wait for input: if not poll(socket_set, nil, request_idle_timeout) then return request_error(false, "408 Request Timeout", "Idle connection timed out") end + -- read headers (with timeout): + do + local coro = coroutine.wrap(read_headers) + local starttime = request_header_timeout and moonbridge_io.timeref() + while true do + local status, retval = coro() + if status == nil then + local remaining + if request_header_timeout then + remaining = request_header_timeout - moonbridge_io.timeref(starttime) + end + if not poll(socket_set, nil, remaining) then + return request_error(false, "408 Request Timeout", "Timeout while receiving headers") + end + elseif status == false then + return retval + elseif status == true then + break + else + error("Unexpected yield value") + end + end + end + -- process "Connection: close" header if existent: + connection_close_requested = request.headers_flags["Connection"]["close"] + -- process "Content-Length" header if existent: + do + local values = request.headers_csv_table["Content-Length"] + if #values > 0 then + request_body_content_length = tonumber(values[1]) + local proper_value = tostring(request_body_content_length) + for i, value in ipairs(values) do + value = string.match(value, "^0*(.*)") + if value ~= proper_value then + return request_error(false, "400 Bad Request", "Content-Length header(s) invalid") + end + end + if request_body_content_length > remaining_body_size_limit then + return request_error(false, "413 Request Entity Too Large", "Announced request body size is too big") + end + end + end + -- process "Transfer-Encoding" header if existent: + do + local flag = request.headers_flags["Transfer-Encoding"]["chunked"] + local list = request.headers_csv_table["Transfer-Encoding"] + if (flag and #list ~= 1) or (not flag and #list ~= 0) then + return request_error(false, "400 Bad Request", "Unexpected Transfer-Encoding") + end + end + -- process "Expect" header if existent: + for i, value in ipairs(request.headers_csv_table["Expect"]) do + if string.lower(value) ~= "100-continue" then + return request_error(false, "417 Expectation Failed", "Unexpected Expect header") + end + end + -- get mandatory Host header according to RFC 7230: + request.host = request.headers_value["Host"] + if not request.host then + return request_error(false, "400 Bad Request", "No valid host header") + end + -- parse request target: + request.path, request.query = string.match(target, "^/([^?]*)(.*)$") + if not request.path then + local host2 + host2, request.path, request.query = string.match(target, "^[Hh][Tt][Tt][Pp]://([^/?]+)/?([^?]*)(.*)$") + if host2 then + if request.host ~= host2 then + return request_error(false, "400 Bad Request", "No valid host header") + end + elseif not (target == "*" and request.method == "OPTIONS") then + return request_error(false, "400 Bad Request", "Invalid request target") + end + end + -- parse GET params: + if request.query then + read_urlencoded_form(request.get_params_list, request.query) + end + -- parse cookies: + for i, line in ipairs(request.headers["Cookie"]) do + for rawkey, rawvalue in + string.gmatch(line, "([^=; ]*)=([^=; ]*)") + do + request.cookies[decode_uri(rawkey)] = decode_uri(rawvalue) + end + end + -- (re)set timeout for handler: + timeout(response_timeout or 0) + -- call underlying handler and remember boolean result: + if handler(request) ~= true then survive = false end + -- finish request (unless already done by underlying handler): + request:finish() + -- stop timeout timer: + timeout(0) until close_responded return survive end