moonbridge

changeset 4:583e2ad140dc

Renamed "http" module to "moonbridge_http"
author jbe
date Fri Jan 09 22:51:25 2015 +0100 (2015-01-09)
parents 3b3f0ecc4ac4
children 4b6d7ca25381
files example_application.lua http.lua moonbridge_http.lua
line diff
     1.1 --- a/example_application.lua	Thu Jan 08 20:55:56 2015 +0100
     1.2 +++ b/example_application.lua	Fri Jan 09 22:51:25 2015 +0100
     1.3 @@ -1,7 +1,7 @@
     1.4  -- Moonbridge example application
     1.5  -- invoke with ./moonbridge example_application.lua
     1.6  
     1.7 -local http = require "http"
     1.8 +local http = require "moonbridge_http"
     1.9  
    1.10  local documents = {"example_webpage.html", "example_webpage.css"}
    1.11  
     2.1 --- a/http.lua	Thu Jan 08 20:55:56 2015 +0100
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,883 +0,0 @@
     2.4 -#!/usr/bin/env lua
     2.5 -
     2.6 --- module preamble
     2.7 -local _G, _M = _ENV, {}
     2.8 -_ENV = setmetatable({}, {
     2.9 -  __index = function(self, key)
    2.10 -    local value = _M[key]; if value ~= nil then return value end
    2.11 -    return _G[key]
    2.12 -  end,
    2.13 -  __newindex = function(self, key, value) _M[key] = value end
    2.14 -})
    2.15 -
    2.16 --- function that encodes certain HTML entities:
    2.17 --- (not used by the library itself)
    2.18 -function encode_html(text)
    2.19 -  return (
    2.20 -    string.gsub(
    2.21 -      text, '[<>&"]',
    2.22 -      function(char)
    2.23 -        if char == '<' then
    2.24 -          return "&lt;"
    2.25 -        elseif char == '>' then
    2.26 -          return "&gt;"
    2.27 -        elseif char == '&' then
    2.28 -          return "&amp;"
    2.29 -        elseif char == '"' then
    2.30 -          return "&quot;"
    2.31 -        end
    2.32 -      end
    2.33 -    )
    2.34 -  )
    2.35 -
    2.36 -end
    2.37 -
    2.38 --- function that encodes special characters for URIs:
    2.39 --- (not used by the library itself)
    2.40 -function encode_uri(text)
    2.41 -  return (
    2.42 -    string.gsub(text, "[^0-9A-Za-z_%.~-]",
    2.43 -      function (char)
    2.44 -        return string.format("%%%02x", string.byte(char))
    2.45 -      end
    2.46 -    )
    2.47 -  )
    2.48 -end
    2.49 -
    2.50 --- function undoing URL encoding:
    2.51 -do
    2.52 -  local b0 = string.byte("0")
    2.53 -  local b9 = string.byte("9")
    2.54 -  local bA = string.byte("A")
    2.55 -  local bF = string.byte("F")
    2.56 -  local ba = string.byte("a")
    2.57 -  local bf = string.byte("f")
    2.58 -  function decode_uri(str)
    2.59 -    return (
    2.60 -      string.gsub(
    2.61 -        string.gsub(str, "%+", " "),
    2.62 -        "%%([0-9A-Fa-f][0-9A-Fa-f])",
    2.63 -        function(hex)
    2.64 -          local n1, n2 = string.byte(hex, 1, 2)
    2.65 -          if n1 >= b0 and n1 <= b9 then n1 = n1 - b0
    2.66 -          elseif n1 >= bA and n1 <= bF then n1 = n1 - bA + 10
    2.67 -          elseif n1 >= ba and n1 <= bf then n1 = n1 - ba + 10
    2.68 -          else error("Assertion failed") end
    2.69 -          if n2 >= b0 and n2 <= b9 then n2 = n2 - b0
    2.70 -          elseif n2 >= bA and n2 <= bF then n2 = n2 - bA + 10
    2.71 -          elseif n2 >= ba and n2 <= bf then n2 = n2 - ba + 10
    2.72 -          else error("Assertion failed") end
    2.73 -          return string.char(n1 * 16 + n2)
    2.74 -        end
    2.75 -      )
    2.76 -    )
    2.77 -  end
    2.78 -end
    2.79 -
    2.80 --- status codes that carry no response body (in addition to 1xx):
    2.81 --- (set to "zero_content_length" if Content-Length header is required)
    2.82 -status_without_response_body = {
    2.83 -  ["101"] = true,
    2.84 -  ["204"] = true,
    2.85 -  ["205"] = "zero_content_length",
    2.86 -  ["304"] = true
    2.87 -}
    2.88 -
    2.89 --- handling of GET/POST param tables:
    2.90 -local new_params_list  -- defined later
    2.91 -do
    2.92 -  local params_list_mapping = setmetatable({}, {__mode="k"})
    2.93 -  local params_list_metatable = {
    2.94 -    __index = function(self, key)
    2.95 -      local tbl = {}
    2.96 -      self[key] = tbl
    2.97 -      return tbl
    2.98 -    end
    2.99 -  }
   2.100 -  local params_metatable = {
   2.101 -    __index = function(self, key)
   2.102 -      local value = params_list_mapping[self][key][1]
   2.103 -      self[key] = value
   2.104 -      return value
   2.105 -    end
   2.106 -  }
   2.107 -  -- returns a table to store key value-list pairs (i.e. multiple values),
   2.108 -  -- and a second table automatically mapping keys to the first value
   2.109 -  -- using the key value-list pairs in the first table:
   2.110 -  new_params_list = function()
   2.111 -    local params_list = setmetatable({}, params_list_metatable)
   2.112 -    local params = setmetatable({}, params_metatable)
   2.113 -    params_list_mapping[params] = params_list
   2.114 -    return params_list, params
   2.115 -  end
   2.116 -end
   2.117 --- parses URL encoded form data and stores it in
   2.118 --- a key value-list pairs structure that has to be
   2.119 --- previously obtained by calling by new_params_list():
   2.120 -local function read_urlencoded_form(tbl, data)
   2.121 -  for rawkey, rawvalue in string.gmatch(data, "([^=&]*)=([^=&]*)") do
   2.122 -    local subtbl = tbl[decode_uri(rawkey)]
   2.123 -    subtbl[#subtbl+1] = decode_uri(rawvalue)
   2.124 -  end
   2.125 -end
   2.126 -
   2.127 --- function creating a HTTP handler:
   2.128 -function generate_handler(handler, options)
   2.129 -  -- swap arguments if necessary (for convenience):
   2.130 -  if type(handler) ~= "function" and type(options) == "function" then
   2.131 -    handler, options = options, handler
   2.132 -  end
   2.133 -  -- process options:
   2.134 -  options = options or {}
   2.135 -  local preamble = ""  -- preamble sent with every(!) HTTP response
   2.136 -  do
   2.137 -    -- named arg "static_headers" is used to create the preamble:
   2.138 -    local s = options.static_headers
   2.139 -    local t = {}
   2.140 -    if s then
   2.141 -      if type(s) == "string" then
   2.142 -        for line in string.gmatch(s, "[^\r\n]+") do
   2.143 -          t[#t+1] = line
   2.144 -        end
   2.145 -      else
   2.146 -        for i, kv in ipairs(options.static_headers) do
   2.147 -          if type(kv) == "string" then
   2.148 -            t[#t+1] = kv
   2.149 -          else
   2.150 -            t[#t+1] = kv[1] .. ": " .. kv[2]
   2.151 -          end
   2.152 -        end
   2.153 -      end
   2.154 -    end
   2.155 -    t[#t+1] = ""
   2.156 -    preamble = table.concat(t, "\r\n")
   2.157 -  end
   2.158 -  -- return connect handler:
   2.159 -  return function(socket)
   2.160 -    local survive = true  -- set to false if process shall be terminated later
   2.161 -    while true do
   2.162 -      -- desired chunk sizes:
   2.163 -      local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384
   2.164 -      local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024
   2.165 -      -- process named arguments "request_header_size_limit" and "request_body_size_limit":
   2.166 -      local remaining_header_size_limit = options.request_header_size_limit or 1024*1024
   2.167 -      local remaining_body_size_limit = options.request_body_size_limit or 64*1024*1024
   2.168 -      -- state variables for request handling:
   2.169 -      local output_state = "no_status_sent"  -- one of:
   2.170 -      --  "no_status_sent"        (initial state)
   2.171 -      --  "info_status_sent"      (1xx status code has been sent)
   2.172 -      --  "bodyless_status_sent"  (204/304 status code has been sent)
   2.173 -      --  "status_sent"           (regular status code has been sent)
   2.174 -      --  "headers_sent"          (headers have been terminated)
   2.175 -      --  "finished"              (request has been answered completely)
   2.176 -      local content_length    -- value of Content-Length header sent
   2.177 -      local bytes_sent = 0    -- number of bytes sent if Content-Length is set
   2.178 -      local chunk_parts = {}  -- list of chunks to send
   2.179 -      local chunk_bytes = 0   -- sum of lengths of chunks to send
   2.180 -      local connection_close_requested = false
   2.181 -      local connection_close_responded = false
   2.182 -      local headers_value_nil = {}          -- header values that are nil
   2.183 -      local request_body_content_length     -- Content-Length of request body
   2.184 -      local input_state = "pending"  -- one of:
   2.185 -      -- "pending"   (request body has not been processed yet)
   2.186 -      -- "deferred"  (request body processing is deferred)
   2.187 -      -- "reading"   (request body is currently being read)
   2.188 -      -- "finished"  (request body has been read)
   2.189 -      local streamed_post_params         = {}  -- mapping from POST field name to stream function
   2.190 -      local streamed_post_param_patterns = {}  -- list of POST field pattern and stream function pairs
   2.191 -      -- object passed to handler (with methods, GET/POST params, etc.):
   2.192 -      local request
   2.193 -      -- reads a number of bytes from the socket,
   2.194 -      -- optionally feeding these bytes chunk-wise
   2.195 -      -- into a callback function:
   2.196 -      local function read_body_bytes(remaining, callback)
   2.197 -        while remaining > 0 do
   2.198 -          local limit
   2.199 -          if remaining > input_chunk_size then
   2.200 -            limit = input_chunk_size
   2.201 -          else
   2.202 -            limit = remaining
   2.203 -          end
   2.204 -          local chunk = socket:read(limit)
   2.205 -          if not chunk or #chunk ~= limit then
   2.206 -            error("Unexpected EOF while reading chunk of request body")
   2.207 -          end
   2.208 -          remaining = remaining - limit
   2.209 -          if callback then
   2.210 -            callback(chunk)
   2.211 -          end
   2.212 -        end
   2.213 -      end
   2.214 -      -- flushes or closes the socket (depending on
   2.215 -      -- whether "Connection: close" header was sent):
   2.216 -      local function finish_response()
   2.217 -        if connection_close_responded then
   2.218 -          -- close output stream:
   2.219 -          socket.output:close()
   2.220 -          -- wait for EOF of peer to avoid immediate TCP RST condition:
   2.221 -          timeout(2, function()
   2.222 -            while socket.input:read(input_chunk_size) do end
   2.223 -          end)
   2.224 -          -- fully close socket:
   2.225 -          socket:close()
   2.226 -        else
   2.227 -          socket:flush()
   2.228 -          request:stream_request_body()
   2.229 -        end
   2.230 -      end
   2.231 -      -- writes out buffered chunks (without flushing the socket):
   2.232 -      local function send_chunk()
   2.233 -        if chunk_bytes > 0 then
   2.234 -          socket:write(string.format("%x\r\n", chunk_bytes))
   2.235 -          for i = 1, #chunk_parts do
   2.236 -            socket:write(chunk_parts[i])
   2.237 -          end
   2.238 -          chunk_parts = {}
   2.239 -          chunk_bytes = 0
   2.240 -          socket:write("\r\n")
   2.241 -        end
   2.242 -      end
   2.243 -      -- terminate header section in response, optionally flushing:
   2.244 -      -- (may be called multiple times unless response is finished)
   2.245 -      local function finish_headers(flush)
   2.246 -        if output_state == "no_status_sent" then
   2.247 -          error("HTTP status has not been sent yet")
   2.248 -        elseif output_state == "finished" then
   2.249 -          error("Response has already been finished")
   2.250 -        elseif output_state == "info_status_sent" then
   2.251 -          socket:write("\r\n")
   2.252 -          socket:flush()
   2.253 -          output_state = "no_status_sent"
   2.254 -        elseif output_state == "bodyless_status_sent" then
   2.255 -          if connection_close_requested and not connection_close_responded then
   2.256 -            request:send_header("Connection", "close")
   2.257 -          end
   2.258 -          socket:write("\r\n")
   2.259 -          finish_response()
   2.260 -          output_state = "finished"
   2.261 -        elseif output_state == "status_sent" then
   2.262 -          if not content_length then
   2.263 -            socket:write("Transfer-Encoding: chunked\r\n")
   2.264 -          end
   2.265 -          if connection_close_requested and not connection_close_responded then
   2.266 -            request:send_header("Connection", "close")
   2.267 -          end
   2.268 -          socket:write("\r\n")
   2.269 -          if request.method == "HEAD" then
   2.270 -            finish_response()
   2.271 -          elseif flush then
   2.272 -            socket:flush()
   2.273 -          end
   2.274 -          output_state = "headers_sent"
   2.275 -        elseif output_state ~= "headers_sent" then
   2.276 -          error("Unexpected internal status in HTTP engine")
   2.277 -        end
   2.278 -      end
   2.279 -      -- create request object and set several functions and values:
   2.280 -      request = {
   2.281 -        -- allow raw socket access:
   2.282 -        socket = socket,
   2.283 -        -- parsed cookies:
   2.284 -        cookies = {},
   2.285 -        -- send a HTTP response status (e.g. "200 OK"):
   2.286 -        send_status = function(self, value)
   2.287 -          if input_state == "pending" then
   2.288 -            request:process_request_body()
   2.289 -          end
   2.290 -          if output_state == "info_status_sent" then
   2.291 -            socket:write("\r\n")
   2.292 -            socket:flush()
   2.293 -          elseif output_state ~= "no_status_sent" then
   2.294 -            error("HTTP status has already been sent")
   2.295 -          end
   2.296 -          local status1 = string.sub(value, 1, 1)
   2.297 -          local status3 = string.sub(value, 1, 3)
   2.298 -          socket:write("HTTP/1.1 ", value, "\r\n", preamble)
   2.299 -          local without_response_body = status_without_response_body[status3]
   2.300 -          if without_response_body then
   2.301 -            output_state = "bodyless_status_sent"
   2.302 -            if without_response_body == "zero_content_length" then
   2.303 -              request:send_header("Content-Length", 0)
   2.304 -            end
   2.305 -          elseif status1 == "1" then
   2.306 -            output_state = "info_status_sent"
   2.307 -          else
   2.308 -            output_state = "status_sent"
   2.309 -          end
   2.310 -        end,
   2.311 -        -- send a HTTP response header
   2.312 -        -- (key and value as separate args):
   2.313 -        send_header = function(self, key, value)
   2.314 -          if output_state == "no_status_sent" then
   2.315 -            error("HTTP status has not been sent yet")
   2.316 -          elseif
   2.317 -            output_state ~= "info_status_sent" and
   2.318 -            output_state ~= "bodyless_status_sent" and
   2.319 -            output_state ~= "status_sent"
   2.320 -          then
   2.321 -            error("All HTTP headers have already been sent")
   2.322 -          end
   2.323 -          local key_lower = string.lower(key)
   2.324 -          if key_lower == "content-length" then
   2.325 -            if output_state == "info_status_sent" then
   2.326 -              error("Cannot set Content-Length for informational status response")
   2.327 -            end
   2.328 -            local new_content_length = assert(tonumber(value), "Invalid content-length")
   2.329 -            if content_length == nil then
   2.330 -              content_length = new_content_length
   2.331 -            elseif content_length == new_content_length then
   2.332 -              return
   2.333 -            else
   2.334 -              error("Content-Length has been set multiple times with different values")
   2.335 -            end
   2.336 -          elseif key_lower == "connection" then
   2.337 -            for entry in string.gmatch(string.lower(value), "[^,]+") do
   2.338 -              if string.match(entry, "^[ \t]*close[ \t]*$") then
   2.339 -                if output_state == "info_status_sent" then
   2.340 -                  error("Cannot set \"Connection: close\" for informational status response")
   2.341 -                end
   2.342 -                if connection_close_responded then
   2.343 -                  return
   2.344 -                else
   2.345 -                  connection_close_responded = true
   2.346 -                  break
   2.347 -                end
   2.348 -              end
   2.349 -            end
   2.350 -          end
   2.351 -          socket:write(key, ": ", value, "\r\n")
   2.352 -        end,
   2.353 -        -- method to finish and flush headers:
   2.354 -        finish_headers = function()
   2.355 -          finish_headers(true)
   2.356 -        end,
   2.357 -        -- send data for response body:
   2.358 -        send_data = function(self, ...)
   2.359 -          if output_state == "info_status_sent" then
   2.360 -            error("No (non-informational) HTTP status has been sent yet")
   2.361 -          elseif output_state == "bodyless_status_sent" then
   2.362 -            error("Cannot send response data for body-less status message")
   2.363 -          end
   2.364 -          finish_headers(false)
   2.365 -          if output_state ~= "headers_sent" then
   2.366 -            error("Unexpected internal status in HTTP engine")
   2.367 -          end
   2.368 -          if request.method == "HEAD" then
   2.369 -            return
   2.370 -          end
   2.371 -          for i = 1, select("#", ...) do
   2.372 -            local str = tostring(select(i, ...))
   2.373 -            if #str > 0 then
   2.374 -              if content_length then
   2.375 -                local bytes_to_send = #str
   2.376 -                if bytes_sent + bytes_to_send > content_length then
   2.377 -                  socket:write(string.sub(str, 1, content_length - bytes_sent))
   2.378 -                  bytes_sent = content_length
   2.379 -                  error("Content length exceeded")
   2.380 -                else
   2.381 -                  socket:write(str)
   2.382 -                  bytes_sent = bytes_sent + bytes_to_send
   2.383 -                end
   2.384 -              else
   2.385 -                chunk_bytes = chunk_bytes + #str
   2.386 -                chunk_parts[#chunk_parts+1] = str
   2.387 -              end
   2.388 -            end
   2.389 -          end
   2.390 -          if chunk_bytes >= output_chunk_size then
   2.391 -            send_chunk()
   2.392 -          end
   2.393 -        end,
   2.394 -        -- flush output buffer:
   2.395 -        flush = function(self)
   2.396 -          send_chunk()
   2.397 -          socket:flush()
   2.398 -        end,
   2.399 -        -- finish response:
   2.400 -        finish = function(self)
   2.401 -          if output_state == "finished" then
   2.402 -            return
   2.403 -          elseif output_state == "info_status_sent" then
   2.404 -            error("Informational HTTP response can be finished with :finish_headers() method")
   2.405 -          end
   2.406 -          finish_headers(false)
   2.407 -          if output_state == "headers_sent" then
   2.408 -            if request.method ~= "HEAD" then
   2.409 -              if content_length then
   2.410 -                if bytes_sent ~= content_length then
   2.411 -                  error("Content length not used")
   2.412 -                end
   2.413 -              else
   2.414 -                send_chunk()
   2.415 -                socket:write("0\r\n\r\n")
   2.416 -              end
   2.417 -              finish_response()
   2.418 -            end
   2.419 -            output_state = "finished"
   2.420 -          elseif output_state ~= finished then
   2.421 -            error("Unexpected internal status in HTTP engine")
   2.422 -          end
   2.423 -        end,
   2.424 -        -- table mapping header field names to value-lists
   2.425 -        -- (raw access):
   2.426 -        headers = setmetatable({}, {
   2.427 -          __index = function(self, key)
   2.428 -            local lowerkey = string.lower(key)
   2.429 -            if lowerkey == key then
   2.430 -              return
   2.431 -            end
   2.432 -            local result = rawget(self, lowerkey)
   2.433 -            if result == nil then
   2.434 -              result = {}
   2.435 -            end
   2.436 -            self[lowerkey] = result
   2.437 -            self[key] = result
   2.438 -            return result
   2.439 -          end
   2.440 -        }),
   2.441 -        -- table mapping header field names to value-lists
   2.442 -        -- (for headers with comma separated values):
   2.443 -        headers_csv_table = setmetatable({}, {
   2.444 -          __index = function(self, key)
   2.445 -            local result = {}
   2.446 -            for i, line in ipairs(request.headers[key]) do
   2.447 -              for entry in string.gmatch(line, "[^,]+") do
   2.448 -                local value = string.match(entry, "^[ \t]*(..-)[ \t]*$")
   2.449 -                if value then
   2.450 -                  result[#result+1] = value
   2.451 -                end
   2.452 -              end
   2.453 -            end
   2.454 -            self[key] = result
   2.455 -            return result
   2.456 -          end
   2.457 -        }),
   2.458 -        -- table mapping header field names to a comma separated string
   2.459 -        -- (for headers with comma separated values):
   2.460 -        headers_csv_string = setmetatable({}, {
   2.461 -          __index = function(self, key)
   2.462 -            local result = {}
   2.463 -            for i, line in ipairs(request.headers[key]) do
   2.464 -              result[#result+1] = line
   2.465 -            end
   2.466 -            result = string.concat(result, ", ")
   2.467 -            self[key] = result
   2.468 -            return result
   2.469 -          end
   2.470 -        }),
   2.471 -        -- table mapping header field names to a single string value
   2.472 -        -- (or false if header has been sent multiple times):
   2.473 -        headers_value = setmetatable({}, {
   2.474 -          __index = function(self, key)
   2.475 -            if headers_value_nil[key] then
   2.476 -              return nil
   2.477 -            end
   2.478 -            local result = nil
   2.479 -            local values = request.headers_csv_table[key]
   2.480 -            if #values == 0 then
   2.481 -              headers_value_nil[key] = true
   2.482 -            elseif #values == 1 then
   2.483 -              result = values[1]
   2.484 -            else
   2.485 -              result = false
   2.486 -            end
   2.487 -            self[key] = result
   2.488 -            return result
   2.489 -          end
   2.490 -        }),
   2.491 -        -- table mapping header field names to a flag table,
   2.492 -        -- indicating if the comma separated value contains certain entries:
   2.493 -        headers_flags = setmetatable({}, {
   2.494 -          __index = function(self, key)
   2.495 -            local result = setmetatable({}, {
   2.496 -              __index = function(self, key)
   2.497 -                local lowerkey = string.lower(key)
   2.498 -                local result = rawget(self, lowerkey) or false
   2.499 -                self[lowerkey] = result
   2.500 -                self[key] = result
   2.501 -                return result
   2.502 -              end
   2.503 -            })
   2.504 -            for i, value in ipairs(request.headers_csv_table[key]) do
   2.505 -              result[string.lower(value)] = true
   2.506 -            end
   2.507 -            self[key] = result
   2.508 -            return result
   2.509 -          end
   2.510 -        }),
   2.511 -        -- register POST param stream handler for a single field name:
   2.512 -        stream_post_param = function(self, field_name, callback)
   2.513 -          if input_state == "inprogress" or input_state == "finished" then
   2.514 -            error("Cannot register POST param streaming function if request body is already processed")
   2.515 -          end
   2.516 -          streamed_post_params[field_name] = callback
   2.517 -        end,
   2.518 -        -- register POST param stream handler for a field name pattern:
   2.519 -        stream_post_params = function(self, pattern, callback)
   2.520 -          if input_state == "inprogress" or input_state == "finished" then
   2.521 -            error("Cannot register POST param streaming function if request body is already processed")
   2.522 -          end
   2.523 -          streamed_post_param_patterns[#streamed_post_param_patterns+1] = {pattern, callback}
   2.524 -        end,
   2.525 -        -- disables automatic request body processing on write
   2.526 -        -- (use with caution):
   2.527 -        defer_reading = function(self)
   2.528 -          if input_state == "pending" then
   2.529 -            input_state = "deferred"
   2.530 -          end
   2.531 -        end,
   2.532 -        -- processes the request body and sets the request.post_params,
   2.533 -        -- request.post_params_list, request.meta_post_params, and
   2.534 -        -- request.meta_post_params_list values (can be called manually or
   2.535 -        -- automatically if post_params are accessed or data is written out)
   2.536 -        process_request_body = function(self)
   2.537 -          if input_state == "finished" then
   2.538 -            return
   2.539 -          end
   2.540 -          local post_params_list, post_params = new_params_list()
   2.541 -          local content_type = request.headers_value["Content-Type"]
   2.542 -          if content_type then
   2.543 -            if
   2.544 -              content_type == "application/x-www-form-urlencoded" or
   2.545 -              string.match(content_type, "^application/x%-www%-form%-urlencoded *;")
   2.546 -            then
   2.547 -              read_urlencoded_form(post_params_list, request.body)
   2.548 -            else
   2.549 -              local boundary = string.match(
   2.550 -                content_type,
   2.551 -                '^multipart/form%-data[ \t]*[;,][ \t]*boundary="([^"]+)"$'
   2.552 -              ) or string.match(
   2.553 -                content_type,
   2.554 -                '^multipart/form%-data[ \t]*[;,][ \t]*boundary=([^"; \t]+)$'
   2.555 -              )
   2.556 -              if boundary then
   2.557 -                local post_metadata_list, post_metadata = new_params_list()
   2.558 -                boundary = "--" .. boundary
   2.559 -                local headerdata = ""
   2.560 -                local streamer
   2.561 -                local field_name
   2.562 -                local metadata = {}
   2.563 -                local value_parts
   2.564 -                local function default_streamer(chunk)
   2.565 -                  value_parts[#value_parts+1] = chunk
   2.566 -                end
   2.567 -                local function stream_part_finish()
   2.568 -                  if streamer == default_streamer then
   2.569 -                    local value = table.concat(value_parts)
   2.570 -                    value_parts = nil
   2.571 -                    if field_name then
   2.572 -                      local values = post_params_list[field_name]
   2.573 -                      values[#values+1] = value
   2.574 -                      local metadata_entries = post_metadata_list[field_name]
   2.575 -                      metadata_entries[#metadata_entries+1] = metadata
   2.576 -                    end
   2.577 -                  else
   2.578 -                    streamer()
   2.579 -                  end
   2.580 -                  headerdata   = ""
   2.581 -                  streamer     = nil
   2.582 -                  field_name   = nil
   2.583 -                  metadata     = {}
   2.584 -                end
   2.585 -                local function stream_part_chunk(chunk)
   2.586 -                  if streamer then
   2.587 -                    streamer(chunk)
   2.588 -                  else
   2.589 -                    headerdata = headerdata .. chunk
   2.590 -                    while true do
   2.591 -                      local line, remaining = string.match(headerdata, "^(.-)\r?\n(.*)$")
   2.592 -                      if not line then
   2.593 -                        break
   2.594 -                      end
   2.595 -                      if line == "" then
   2.596 -                        streamer = streamed_post_params[field_name]
   2.597 -                        if not streamer then
   2.598 -                          for i, rule in ipairs(streamed_post_param_patterns) do
   2.599 -                            if string.match(field_name, rule[1]) then
   2.600 -                              streamer = rule[2]
   2.601 -                              break
   2.602 -                            end
   2.603 -                          end
   2.604 -                        end
   2.605 -                        if not streamer then
   2.606 -                          value_parts = {}
   2.607 -                          streamer = default_streamer
   2.608 -                        end
   2.609 -                        streamer(remaining, field_name, metadata)
   2.610 -                        return
   2.611 -                      end
   2.612 -                      headerdata = remaining
   2.613 -                      local header_key, header_value = string.match(line, "^([^:]*):[ \t]*(.-)[ \t]*$")
   2.614 -                      if not header_key then
   2.615 -                        error("Invalid header in multipart/form-data part")
   2.616 -                      end
   2.617 -                      header_key = string.lower(header_key)
   2.618 -                      if header_key == "content-disposition" then
   2.619 -                        local escaped_header_value = string.gsub(header_value, '"[^"]*"', function(str)
   2.620 -                          return string.gsub(str, "=", "==")
   2.621 -                        end)
   2.622 -                        field_name = string.match(escaped_header_value, ';[ \t]*name="([^"]*)"')
   2.623 -                        if field_name then
   2.624 -                          field_name = string.gsub(field_name, "==", "=")
   2.625 -                        else
   2.626 -                          field_name = string.match(header_value, ';[ \t]*name=([^"; \t]+)')
   2.627 -                        end
   2.628 -                        metadata.file_name = string.match(escaped_header_value, ';[ \t]*filename="([^"]*)"')
   2.629 -                        if metadata.file_name then
   2.630 -                          metadata.file_name = string.gsub(metadata.file_name, "==", "=")
   2.631 -                        else
   2.632 -                          string.match(header_value, ';[ \t]*filename=([^"; \t]+)')
   2.633 -                        end
   2.634 -                      elseif header_key == "content-type" then
   2.635 -                        metadata.content_type = header_value
   2.636 -                      elseif header_key == "content-transfer-encoding" then
   2.637 -                        error("Content-transfer-encoding not supported by multipart/form-data parser")
   2.638 -                      end
   2.639 -                    end
   2.640 -                  end
   2.641 -                end
   2.642 -                local skippart   = true   -- ignore data until first boundary
   2.643 -                local afterbound = false  -- interpret 2 bytes after boundary ("\r\n" or "--")
   2.644 -                local terminated = false  -- final boundary read
   2.645 -                local bigchunk = ""
   2.646 -                request:stream_request_body(function(chunk)
   2.647 -                  if terminated then
   2.648 -                    return
   2.649 -                  end
   2.650 -                  bigchunk = bigchunk .. chunk
   2.651 -                  while true do
   2.652 -                    if afterbound then
   2.653 -                      if #bigchunk <= 2 then
   2.654 -                        return
   2.655 -                      end
   2.656 -                      local terminator = string.sub(bigchunk, 1, 2)
   2.657 -                      if terminator == "\r\n" then
   2.658 -                        afterbound = false
   2.659 -                        bigchunk = string.sub(bigchunk, 3)
   2.660 -                      elseif terminator == "--" then
   2.661 -                        terminated = true
   2.662 -                        bigchunk = nil
   2.663 -                        return
   2.664 -                      else
   2.665 -                        error("Error while parsing multipart body (expected CRLF or double minus)")
   2.666 -                      end
   2.667 -                    end
   2.668 -                    local pos1, pos2 = string.find(bigchunk, boundary, 1, true)
   2.669 -                    if not pos1 then
   2.670 -                      if not skippart then
   2.671 -                        local safe = #bigchunk-#boundary
   2.672 -                        if safe > 0 then
   2.673 -                          stream_part_chunk(string.sub(bigchunk, 1, safe))
   2.674 -                          bigchunk = string.sub(bigchunk, safe+1)
   2.675 -                        end
   2.676 -                      end
   2.677 -                      return
   2.678 -                    end
   2.679 -                    if not skippart then
   2.680 -                      stream_part_chunk(string.sub(bigchunk, 1, pos1 - 1))
   2.681 -                      stream_part_finish()
   2.682 -                    else
   2.683 -                      boundary = "\r\n" .. boundary
   2.684 -                      skippart = false
   2.685 -                    end
   2.686 -                    bigchunk = string.sub(bigchunk, pos2 + 1)
   2.687 -                    afterbound = true
   2.688 -                  end
   2.689 -                end)
   2.690 -                if not terminated then
   2.691 -                  error("Premature end of multipart/form-data request body")
   2.692 -                end
   2.693 -                request.post_metadata_list, request.post_metadata = post_metadata_list, post_metadata
   2.694 -              else
   2.695 -                error("Unknown Content-Type of request body")
   2.696 -              end
   2.697 -            end
   2.698 -          end
   2.699 -          request.post_params_list, request.post_params = post_params_list, post_params
   2.700 -        end,
   2.701 -        -- stream request body to an (optional) callback function
   2.702 -        -- without processing it otherwise:
   2.703 -        stream_request_body = function(self, callback)
   2.704 -          if input_state ~= "pending" and input_state ~= "deferred" then
   2.705 -            if callback then
   2.706 -              if input_state == "inprogress" then
   2.707 -                error("Request body is already being processed")
   2.708 -              else
   2.709 -                error("Request body has already been processed")
   2.710 -              end
   2.711 -            end
   2.712 -            return
   2.713 -          end
   2.714 -          input_state = "inprogress"
   2.715 -          if request.headers_flags["Expect"]["100-continue"] then
   2.716 -            request:send_status("100 Continue")
   2.717 -            request:finish_headers()
   2.718 -          end
   2.719 -          if request.headers_flags["Transfer-Encoding"]["chunked"] then
   2.720 -            while true do
   2.721 -              local line = socket:readuntil("\n", 32 + remaining_body_size_limit)
   2.722 -              if not line then
   2.723 -                error("Unexpected EOF while reading next chunk of request body")
   2.724 -              end
   2.725 -              local zeros, lenstr = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)\r?\n$")
   2.726 -              local chunkext
   2.727 -              if lenstr then
   2.728 -                chunkext = ""
   2.729 -              else
   2.730 -                zeros, lenstr, chunkext = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)([ \t;].-)\r?\n$")
   2.731 -              end
   2.732 -              if not lenstr or #lenstr > 13 then
   2.733 -                error("Encoding error or unexpected EOF while reading chunk of request body")
   2.734 -              end
   2.735 -              local len = tonumber("0x" .. lenstr)
   2.736 -              remaining_body_size_limit = remaining_body_size_limit - (#zeros + #chunkext + len)
   2.737 -              if remaining_body_size_limit < 0 then
   2.738 -                error("Request body size limit exceeded")
   2.739 -              end
   2.740 -              if len == 0 then break end
   2.741 -              read_body_bytes(len, callback)
   2.742 -              local term = socket:readuntil("\n", 2)
   2.743 -              if term ~= "\r\n" and term ~= "\n" then
   2.744 -                error("Encoding error while reading chunk of request body")
   2.745 -              end
   2.746 -            end
   2.747 -            while true do
   2.748 -              local line = socket:readuntil("\n", 2 + remaining_body_size_limit)
   2.749 -              if line == "\r\n" or line == "\n" then break end
   2.750 -              remaining_body_size_limit = remaining_body_size_limit - #line
   2.751 -              if remaining_body_size_limit < 0 then
   2.752 -                error("Request body size limit exceeded while reading trailer section of chunked request body")
   2.753 -              end
   2.754 -            end
   2.755 -          elseif request_body_content_length then
   2.756 -            read_body_bytes(request_body_content_length, callback)
   2.757 -          end
   2.758 -          input_state = "finished"
   2.759 -        end
   2.760 -      }
   2.761 -      -- initialize tables for GET params in request object:
   2.762 -      request.get_params_list, request.get_params = new_params_list()
   2.763 -      -- add meta table to request object to allow access to "body" and POST params:
   2.764 -      setmetatable(request, {
   2.765 -        __index = function(self, key)
   2.766 -          if key == "body" then
   2.767 -            local chunks = {}
   2.768 -            request:stream_request_body(function(chunk)
   2.769 -              chunks[#chunks+1] = chunk
   2.770 -            end)
   2.771 -            self.body = table.concat(chunks)
   2.772 -            return self.body
   2.773 -          elseif
   2.774 -            key == "post_params_list" or key == "post_params" or
   2.775 -            key == "post_metadata_list" or key == "post_metadata"
   2.776 -          then
   2.777 -            request:process_request_body()
   2.778 -            return request[key]
   2.779 -          end
   2.780 -        end
   2.781 -      })
   2.782 -      -- low level HTTP error response (for malformed requests, etc.):
   2.783 -      local function error_response(status, text)
   2.784 -        request:send_status(status)
   2.785 -        request:send_header("Content-Type", "text/plain")
   2.786 -        if not connection_close_responded then
   2.787 -          request:send_header("Connection", "close")
   2.788 -        end
   2.789 -        request:send_data(status, "\n")
   2.790 -        if text then
   2.791 -          request:send_data("\n", text, "\n")
   2.792 -        end
   2.793 -        request:finish()
   2.794 -        return survive
   2.795 -      end
   2.796 -      -- read and parse request line:
   2.797 -      local line = socket:readuntil("\n", remaining_header_size_limit)
   2.798 -      if not line then return survive end
   2.799 -      remaining_header_size_limit = remaining_header_size_limit - #line
   2.800 -      if remaining_header_size_limit == 0 then
   2.801 -        return error_response("413 Request Entity Too Large", "Request line too long")
   2.802 -      end
   2.803 -      local proto
   2.804 -      request.method, request.url, proto =
   2.805 -        line:match("^([^ \t\r]+)[ \t]+([^ \t\r]+)[ \t]*([^ \t\r]*)[ \t]*\r?\n$")
   2.806 -      if not request.method then
   2.807 -        return error_response("400 Bad Request")
   2.808 -      elseif proto ~= "HTTP/1.1" then
   2.809 -        return error_response("505 HTTP Version Not Supported")
   2.810 -      else
   2.811 -        -- read and parse headers:
   2.812 -        while true do
   2.813 -          local line = socket:readuntil("\n", remaining_header_size_limit);
   2.814 -          remaining_header_size_limit = remaining_header_size_limit - #line
   2.815 -          if not line then
   2.816 -            return error_response("400 Bad Request")
   2.817 -          end
   2.818 -          if line == "\r\n" or line == "\n" then
   2.819 -            break
   2.820 -          end
   2.821 -          if remaining_header_size_limit == 0 then
   2.822 -            return error_response("413 Request Entity Too Large", "Headers too long")
   2.823 -          end
   2.824 -          local key, value = string.match(line, "^([^ \t\r]+):[ \t]*(.-)[ \t]*\r?\n$")
   2.825 -          if not key then
   2.826 -            return error_response("400 Bad Request")
   2.827 -          end
   2.828 -          local values = request.headers[key]
   2.829 -          values[#values+1] = value
   2.830 -        end
   2.831 -        -- process "Connection: close" header if existent:
   2.832 -        connection_close_requested = request.headers_flags["Connection"]["close"]
   2.833 -        -- process "Content-Length" header if existent:
   2.834 -        do
   2.835 -          local values = request.headers_csv_table["Content-Length"]
   2.836 -          if #values > 0 then
   2.837 -            request_body_content_length = tonumber(values[1])
   2.838 -            local proper_value = tostring(request_body_content_length)
   2.839 -            for i, value in ipairs(values) do
   2.840 -              value = string.match(value, "^0*(.*)")
   2.841 -              if value ~= proper_value then
   2.842 -                return error_response("400 Bad Request", "Content-Length header(s) invalid")
   2.843 -              end
   2.844 -            end
   2.845 -            if request_body_content_length > remaining_body_size_limit then
   2.846 -              return error_response("413 Request Entity Too Large", "Request body too big")
   2.847 -            end
   2.848 -          end
   2.849 -        end
   2.850 -        -- process "Transfer-Encoding" header if existent:
   2.851 -        do
   2.852 -          local flag = request.headers_flags["Transfer-Encoding"]["chunked"]
   2.853 -          local list = request.headers_csv_table["Transfer-Encoding"]
   2.854 -          if (flag and #list ~= 1) or (not flag and #list ~= 0) then
   2.855 -            return error_response("400 Bad Request", "Unexpected Transfer-Encoding")
   2.856 -          end
   2.857 -        end
   2.858 -        -- process "Expect" header if existent:
   2.859 -        for i, value in ipairs(request.headers_csv_table["Expect"]) do
   2.860 -          if string.lower(value) ~= "100-continue" then
   2.861 -            return error_response("417 Expectation Failed", "Unexpected Expect header")
   2.862 -          end
   2.863 -        end
   2.864 -        -- parse GET params:
   2.865 -        request.path, request.query = string.match(request.url, "^([^?]*)%??(.*)$")
   2.866 -        read_urlencoded_form(request.get_params_list, request.query)
   2.867 -        -- parse cookies:
   2.868 -        for i, line in ipairs(request.headers["Cookie"]) do
   2.869 -          for rawkey, rawvalue in
   2.870 -            string.gmatch(line, "([^=; ]*)=([^=; ]*)")
   2.871 -          do
   2.872 -            request.cookies[decode_uri(rawkey)] = decode_uri(rawvalue)
   2.873 -          end
   2.874 -        end
   2.875 -        -- call underlying handler and remember boolean result:
   2.876 -        if handler(request) ~= true then survive = false end
   2.877 -        -- finish request (unless already done by underlying handler):
   2.878 -        request:finish()
   2.879 -      end
   2.880 -    end
   2.881 -    return survive
   2.882 -  end
   2.883 -end
   2.884 -
   2.885 -return _M
   2.886 -
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/moonbridge_http.lua	Fri Jan 09 22:51:25 2015 +0100
     3.3 @@ -0,0 +1,883 @@
     3.4 +#!/usr/bin/env lua
     3.5 +
     3.6 +-- module preamble
     3.7 +local _G, _M = _ENV, {}
     3.8 +_ENV = setmetatable({}, {
     3.9 +  __index = function(self, key)
    3.10 +    local value = _M[key]; if value ~= nil then return value end
    3.11 +    return _G[key]
    3.12 +  end,
    3.13 +  __newindex = function(self, key, value) _M[key] = value end
    3.14 +})
    3.15 +
    3.16 +-- function that encodes certain HTML entities:
    3.17 +-- (not used by the library itself)
    3.18 +function encode_html(text)
    3.19 +  return (
    3.20 +    string.gsub(
    3.21 +      text, '[<>&"]',
    3.22 +      function(char)
    3.23 +        if char == '<' then
    3.24 +          return "&lt;"
    3.25 +        elseif char == '>' then
    3.26 +          return "&gt;"
    3.27 +        elseif char == '&' then
    3.28 +          return "&amp;"
    3.29 +        elseif char == '"' then
    3.30 +          return "&quot;"
    3.31 +        end
    3.32 +      end
    3.33 +    )
    3.34 +  )
    3.35 +
    3.36 +end
    3.37 +
    3.38 +-- function that encodes special characters for URIs:
    3.39 +-- (not used by the library itself)
    3.40 +function encode_uri(text)
    3.41 +  return (
    3.42 +    string.gsub(text, "[^0-9A-Za-z_%.~-]",
    3.43 +      function (char)
    3.44 +        return string.format("%%%02x", string.byte(char))
    3.45 +      end
    3.46 +    )
    3.47 +  )
    3.48 +end
    3.49 +
    3.50 +-- function undoing URL encoding:
    3.51 +do
    3.52 +  local b0 = string.byte("0")
    3.53 +  local b9 = string.byte("9")
    3.54 +  local bA = string.byte("A")
    3.55 +  local bF = string.byte("F")
    3.56 +  local ba = string.byte("a")
    3.57 +  local bf = string.byte("f")
    3.58 +  function decode_uri(str)
    3.59 +    return (
    3.60 +      string.gsub(
    3.61 +        string.gsub(str, "%+", " "),
    3.62 +        "%%([0-9A-Fa-f][0-9A-Fa-f])",
    3.63 +        function(hex)
    3.64 +          local n1, n2 = string.byte(hex, 1, 2)
    3.65 +          if n1 >= b0 and n1 <= b9 then n1 = n1 - b0
    3.66 +          elseif n1 >= bA and n1 <= bF then n1 = n1 - bA + 10
    3.67 +          elseif n1 >= ba and n1 <= bf then n1 = n1 - ba + 10
    3.68 +          else error("Assertion failed") end
    3.69 +          if n2 >= b0 and n2 <= b9 then n2 = n2 - b0
    3.70 +          elseif n2 >= bA and n2 <= bF then n2 = n2 - bA + 10
    3.71 +          elseif n2 >= ba and n2 <= bf then n2 = n2 - ba + 10
    3.72 +          else error("Assertion failed") end
    3.73 +          return string.char(n1 * 16 + n2)
    3.74 +        end
    3.75 +      )
    3.76 +    )
    3.77 +  end
    3.78 +end
    3.79 +
    3.80 +-- status codes that carry no response body (in addition to 1xx):
    3.81 +-- (set to "zero_content_length" if Content-Length header is required)
    3.82 +status_without_response_body = {
    3.83 +  ["101"] = true,
    3.84 +  ["204"] = true,
    3.85 +  ["205"] = "zero_content_length",
    3.86 +  ["304"] = true
    3.87 +}
    3.88 +
    3.89 +-- handling of GET/POST param tables:
    3.90 +local new_params_list  -- defined later
    3.91 +do
    3.92 +  local params_list_mapping = setmetatable({}, {__mode="k"})
    3.93 +  local params_list_metatable = {
    3.94 +    __index = function(self, key)
    3.95 +      local tbl = {}
    3.96 +      self[key] = tbl
    3.97 +      return tbl
    3.98 +    end
    3.99 +  }
   3.100 +  local params_metatable = {
   3.101 +    __index = function(self, key)
   3.102 +      local value = params_list_mapping[self][key][1]
   3.103 +      self[key] = value
   3.104 +      return value
   3.105 +    end
   3.106 +  }
   3.107 +  -- returns a table to store key value-list pairs (i.e. multiple values),
   3.108 +  -- and a second table automatically mapping keys to the first value
   3.109 +  -- using the key value-list pairs in the first table:
   3.110 +  new_params_list = function()
   3.111 +    local params_list = setmetatable({}, params_list_metatable)
   3.112 +    local params = setmetatable({}, params_metatable)
   3.113 +    params_list_mapping[params] = params_list
   3.114 +    return params_list, params
   3.115 +  end
   3.116 +end
   3.117 +-- parses URL encoded form data and stores it in
   3.118 +-- a key value-list pairs structure that has to be
   3.119 +-- previously obtained by calling by new_params_list():
   3.120 +local function read_urlencoded_form(tbl, data)
   3.121 +  for rawkey, rawvalue in string.gmatch(data, "([^=&]*)=([^=&]*)") do
   3.122 +    local subtbl = tbl[decode_uri(rawkey)]
   3.123 +    subtbl[#subtbl+1] = decode_uri(rawvalue)
   3.124 +  end
   3.125 +end
   3.126 +
   3.127 +-- function creating a HTTP handler:
   3.128 +function generate_handler(handler, options)
   3.129 +  -- swap arguments if necessary (for convenience):
   3.130 +  if type(handler) ~= "function" and type(options) == "function" then
   3.131 +    handler, options = options, handler
   3.132 +  end
   3.133 +  -- process options:
   3.134 +  options = options or {}
   3.135 +  local preamble = ""  -- preamble sent with every(!) HTTP response
   3.136 +  do
   3.137 +    -- named arg "static_headers" is used to create the preamble:
   3.138 +    local s = options.static_headers
   3.139 +    local t = {}
   3.140 +    if s then
   3.141 +      if type(s) == "string" then
   3.142 +        for line in string.gmatch(s, "[^\r\n]+") do
   3.143 +          t[#t+1] = line
   3.144 +        end
   3.145 +      else
   3.146 +        for i, kv in ipairs(options.static_headers) do
   3.147 +          if type(kv) == "string" then
   3.148 +            t[#t+1] = kv
   3.149 +          else
   3.150 +            t[#t+1] = kv[1] .. ": " .. kv[2]
   3.151 +          end
   3.152 +        end
   3.153 +      end
   3.154 +    end
   3.155 +    t[#t+1] = ""
   3.156 +    preamble = table.concat(t, "\r\n")
   3.157 +  end
   3.158 +  -- return connect handler:
   3.159 +  return function(socket)
   3.160 +    local survive = true  -- set to false if process shall be terminated later
   3.161 +    while true do
   3.162 +      -- desired chunk sizes:
   3.163 +      local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384
   3.164 +      local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024
   3.165 +      -- process named arguments "request_header_size_limit" and "request_body_size_limit":
   3.166 +      local remaining_header_size_limit = options.request_header_size_limit or 1024*1024
   3.167 +      local remaining_body_size_limit = options.request_body_size_limit or 64*1024*1024
   3.168 +      -- state variables for request handling:
   3.169 +      local output_state = "no_status_sent"  -- one of:
   3.170 +      --  "no_status_sent"        (initial state)
   3.171 +      --  "info_status_sent"      (1xx status code has been sent)
   3.172 +      --  "bodyless_status_sent"  (204/304 status code has been sent)
   3.173 +      --  "status_sent"           (regular status code has been sent)
   3.174 +      --  "headers_sent"          (headers have been terminated)
   3.175 +      --  "finished"              (request has been answered completely)
   3.176 +      local content_length    -- value of Content-Length header sent
   3.177 +      local bytes_sent = 0    -- number of bytes sent if Content-Length is set
   3.178 +      local chunk_parts = {}  -- list of chunks to send
   3.179 +      local chunk_bytes = 0   -- sum of lengths of chunks to send
   3.180 +      local connection_close_requested = false
   3.181 +      local connection_close_responded = false
   3.182 +      local headers_value_nil = {}          -- header values that are nil
   3.183 +      local request_body_content_length     -- Content-Length of request body
   3.184 +      local input_state = "pending"  -- one of:
   3.185 +      -- "pending"   (request body has not been processed yet)
   3.186 +      -- "deferred"  (request body processing is deferred)
   3.187 +      -- "reading"   (request body is currently being read)
   3.188 +      -- "finished"  (request body has been read)
   3.189 +      local streamed_post_params         = {}  -- mapping from POST field name to stream function
   3.190 +      local streamed_post_param_patterns = {}  -- list of POST field pattern and stream function pairs
   3.191 +      -- object passed to handler (with methods, GET/POST params, etc.):
   3.192 +      local request
   3.193 +      -- reads a number of bytes from the socket,
   3.194 +      -- optionally feeding these bytes chunk-wise
   3.195 +      -- into a callback function:
   3.196 +      local function read_body_bytes(remaining, callback)
   3.197 +        while remaining > 0 do
   3.198 +          local limit
   3.199 +          if remaining > input_chunk_size then
   3.200 +            limit = input_chunk_size
   3.201 +          else
   3.202 +            limit = remaining
   3.203 +          end
   3.204 +          local chunk = socket:read(limit)
   3.205 +          if not chunk or #chunk ~= limit then
   3.206 +            error("Unexpected EOF while reading chunk of request body")
   3.207 +          end
   3.208 +          remaining = remaining - limit
   3.209 +          if callback then
   3.210 +            callback(chunk)
   3.211 +          end
   3.212 +        end
   3.213 +      end
   3.214 +      -- flushes or closes the socket (depending on
   3.215 +      -- whether "Connection: close" header was sent):
   3.216 +      local function finish_response()
   3.217 +        if connection_close_responded then
   3.218 +          -- close output stream:
   3.219 +          socket.output:close()
   3.220 +          -- wait for EOF of peer to avoid immediate TCP RST condition:
   3.221 +          timeout(2, function()
   3.222 +            while socket.input:read(input_chunk_size) do end
   3.223 +          end)
   3.224 +          -- fully close socket:
   3.225 +          socket:close()
   3.226 +        else
   3.227 +          socket:flush()
   3.228 +          request:stream_request_body()
   3.229 +        end
   3.230 +      end
   3.231 +      -- writes out buffered chunks (without flushing the socket):
   3.232 +      local function send_chunk()
   3.233 +        if chunk_bytes > 0 then
   3.234 +          socket:write(string.format("%x\r\n", chunk_bytes))
   3.235 +          for i = 1, #chunk_parts do
   3.236 +            socket:write(chunk_parts[i])
   3.237 +          end
   3.238 +          chunk_parts = {}
   3.239 +          chunk_bytes = 0
   3.240 +          socket:write("\r\n")
   3.241 +        end
   3.242 +      end
   3.243 +      -- terminate header section in response, optionally flushing:
   3.244 +      -- (may be called multiple times unless response is finished)
   3.245 +      local function finish_headers(flush)
   3.246 +        if output_state == "no_status_sent" then
   3.247 +          error("HTTP status has not been sent yet")
   3.248 +        elseif output_state == "finished" then
   3.249 +          error("Response has already been finished")
   3.250 +        elseif output_state == "info_status_sent" then
   3.251 +          socket:write("\r\n")
   3.252 +          socket:flush()
   3.253 +          output_state = "no_status_sent"
   3.254 +        elseif output_state == "bodyless_status_sent" then
   3.255 +          if connection_close_requested and not connection_close_responded then
   3.256 +            request:send_header("Connection", "close")
   3.257 +          end
   3.258 +          socket:write("\r\n")
   3.259 +          finish_response()
   3.260 +          output_state = "finished"
   3.261 +        elseif output_state == "status_sent" then
   3.262 +          if not content_length then
   3.263 +            socket:write("Transfer-Encoding: chunked\r\n")
   3.264 +          end
   3.265 +          if connection_close_requested and not connection_close_responded then
   3.266 +            request:send_header("Connection", "close")
   3.267 +          end
   3.268 +          socket:write("\r\n")
   3.269 +          if request.method == "HEAD" then
   3.270 +            finish_response()
   3.271 +          elseif flush then
   3.272 +            socket:flush()
   3.273 +          end
   3.274 +          output_state = "headers_sent"
   3.275 +        elseif output_state ~= "headers_sent" then
   3.276 +          error("Unexpected internal status in HTTP engine")
   3.277 +        end
   3.278 +      end
   3.279 +      -- create request object and set several functions and values:
   3.280 +      request = {
   3.281 +        -- allow raw socket access:
   3.282 +        socket = socket,
   3.283 +        -- parsed cookies:
   3.284 +        cookies = {},
   3.285 +        -- send a HTTP response status (e.g. "200 OK"):
   3.286 +        send_status = function(self, value)
   3.287 +          if input_state == "pending" then
   3.288 +            request:process_request_body()
   3.289 +          end
   3.290 +          if output_state == "info_status_sent" then
   3.291 +            socket:write("\r\n")
   3.292 +            socket:flush()
   3.293 +          elseif output_state ~= "no_status_sent" then
   3.294 +            error("HTTP status has already been sent")
   3.295 +          end
   3.296 +          local status1 = string.sub(value, 1, 1)
   3.297 +          local status3 = string.sub(value, 1, 3)
   3.298 +          socket:write("HTTP/1.1 ", value, "\r\n", preamble)
   3.299 +          local without_response_body = status_without_response_body[status3]
   3.300 +          if without_response_body then
   3.301 +            output_state = "bodyless_status_sent"
   3.302 +            if without_response_body == "zero_content_length" then
   3.303 +              request:send_header("Content-Length", 0)
   3.304 +            end
   3.305 +          elseif status1 == "1" then
   3.306 +            output_state = "info_status_sent"
   3.307 +          else
   3.308 +            output_state = "status_sent"
   3.309 +          end
   3.310 +        end,
   3.311 +        -- send a HTTP response header
   3.312 +        -- (key and value as separate args):
   3.313 +        send_header = function(self, key, value)
   3.314 +          if output_state == "no_status_sent" then
   3.315 +            error("HTTP status has not been sent yet")
   3.316 +          elseif
   3.317 +            output_state ~= "info_status_sent" and
   3.318 +            output_state ~= "bodyless_status_sent" and
   3.319 +            output_state ~= "status_sent"
   3.320 +          then
   3.321 +            error("All HTTP headers have already been sent")
   3.322 +          end
   3.323 +          local key_lower = string.lower(key)
   3.324 +          if key_lower == "content-length" then
   3.325 +            if output_state == "info_status_sent" then
   3.326 +              error("Cannot set Content-Length for informational status response")
   3.327 +            end
   3.328 +            local new_content_length = assert(tonumber(value), "Invalid content-length")
   3.329 +            if content_length == nil then
   3.330 +              content_length = new_content_length
   3.331 +            elseif content_length == new_content_length then
   3.332 +              return
   3.333 +            else
   3.334 +              error("Content-Length has been set multiple times with different values")
   3.335 +            end
   3.336 +          elseif key_lower == "connection" then
   3.337 +            for entry in string.gmatch(string.lower(value), "[^,]+") do
   3.338 +              if string.match(entry, "^[ \t]*close[ \t]*$") then
   3.339 +                if output_state == "info_status_sent" then
   3.340 +                  error("Cannot set \"Connection: close\" for informational status response")
   3.341 +                end
   3.342 +                if connection_close_responded then
   3.343 +                  return
   3.344 +                else
   3.345 +                  connection_close_responded = true
   3.346 +                  break
   3.347 +                end
   3.348 +              end
   3.349 +            end
   3.350 +          end
   3.351 +          socket:write(key, ": ", value, "\r\n")
   3.352 +        end,
   3.353 +        -- method to finish and flush headers:
   3.354 +        finish_headers = function()
   3.355 +          finish_headers(true)
   3.356 +        end,
   3.357 +        -- send data for response body:
   3.358 +        send_data = function(self, ...)
   3.359 +          if output_state == "info_status_sent" then
   3.360 +            error("No (non-informational) HTTP status has been sent yet")
   3.361 +          elseif output_state == "bodyless_status_sent" then
   3.362 +            error("Cannot send response data for body-less status message")
   3.363 +          end
   3.364 +          finish_headers(false)
   3.365 +          if output_state ~= "headers_sent" then
   3.366 +            error("Unexpected internal status in HTTP engine")
   3.367 +          end
   3.368 +          if request.method == "HEAD" then
   3.369 +            return
   3.370 +          end
   3.371 +          for i = 1, select("#", ...) do
   3.372 +            local str = tostring(select(i, ...))
   3.373 +            if #str > 0 then
   3.374 +              if content_length then
   3.375 +                local bytes_to_send = #str
   3.376 +                if bytes_sent + bytes_to_send > content_length then
   3.377 +                  socket:write(string.sub(str, 1, content_length - bytes_sent))
   3.378 +                  bytes_sent = content_length
   3.379 +                  error("Content length exceeded")
   3.380 +                else
   3.381 +                  socket:write(str)
   3.382 +                  bytes_sent = bytes_sent + bytes_to_send
   3.383 +                end
   3.384 +              else
   3.385 +                chunk_bytes = chunk_bytes + #str
   3.386 +                chunk_parts[#chunk_parts+1] = str
   3.387 +              end
   3.388 +            end
   3.389 +          end
   3.390 +          if chunk_bytes >= output_chunk_size then
   3.391 +            send_chunk()
   3.392 +          end
   3.393 +        end,
   3.394 +        -- flush output buffer:
   3.395 +        flush = function(self)
   3.396 +          send_chunk()
   3.397 +          socket:flush()
   3.398 +        end,
   3.399 +        -- finish response:
   3.400 +        finish = function(self)
   3.401 +          if output_state == "finished" then
   3.402 +            return
   3.403 +          elseif output_state == "info_status_sent" then
   3.404 +            error("Informational HTTP response can be finished with :finish_headers() method")
   3.405 +          end
   3.406 +          finish_headers(false)
   3.407 +          if output_state == "headers_sent" then
   3.408 +            if request.method ~= "HEAD" then
   3.409 +              if content_length then
   3.410 +                if bytes_sent ~= content_length then
   3.411 +                  error("Content length not used")
   3.412 +                end
   3.413 +              else
   3.414 +                send_chunk()
   3.415 +                socket:write("0\r\n\r\n")
   3.416 +              end
   3.417 +              finish_response()
   3.418 +            end
   3.419 +            output_state = "finished"
   3.420 +          elseif output_state ~= finished then
   3.421 +            error("Unexpected internal status in HTTP engine")
   3.422 +          end
   3.423 +        end,
   3.424 +        -- table mapping header field names to value-lists
   3.425 +        -- (raw access):
   3.426 +        headers = setmetatable({}, {
   3.427 +          __index = function(self, key)
   3.428 +            local lowerkey = string.lower(key)
   3.429 +            if lowerkey == key then
   3.430 +              return
   3.431 +            end
   3.432 +            local result = rawget(self, lowerkey)
   3.433 +            if result == nil then
   3.434 +              result = {}
   3.435 +            end
   3.436 +            self[lowerkey] = result
   3.437 +            self[key] = result
   3.438 +            return result
   3.439 +          end
   3.440 +        }),
   3.441 +        -- table mapping header field names to value-lists
   3.442 +        -- (for headers with comma separated values):
   3.443 +        headers_csv_table = setmetatable({}, {
   3.444 +          __index = function(self, key)
   3.445 +            local result = {}
   3.446 +            for i, line in ipairs(request.headers[key]) do
   3.447 +              for entry in string.gmatch(line, "[^,]+") do
   3.448 +                local value = string.match(entry, "^[ \t]*(..-)[ \t]*$")
   3.449 +                if value then
   3.450 +                  result[#result+1] = value
   3.451 +                end
   3.452 +              end
   3.453 +            end
   3.454 +            self[key] = result
   3.455 +            return result
   3.456 +          end
   3.457 +        }),
   3.458 +        -- table mapping header field names to a comma separated string
   3.459 +        -- (for headers with comma separated values):
   3.460 +        headers_csv_string = setmetatable({}, {
   3.461 +          __index = function(self, key)
   3.462 +            local result = {}
   3.463 +            for i, line in ipairs(request.headers[key]) do
   3.464 +              result[#result+1] = line
   3.465 +            end
   3.466 +            result = string.concat(result, ", ")
   3.467 +            self[key] = result
   3.468 +            return result
   3.469 +          end
   3.470 +        }),
   3.471 +        -- table mapping header field names to a single string value
   3.472 +        -- (or false if header has been sent multiple times):
   3.473 +        headers_value = setmetatable({}, {
   3.474 +          __index = function(self, key)
   3.475 +            if headers_value_nil[key] then
   3.476 +              return nil
   3.477 +            end
   3.478 +            local result = nil
   3.479 +            local values = request.headers_csv_table[key]
   3.480 +            if #values == 0 then
   3.481 +              headers_value_nil[key] = true
   3.482 +            elseif #values == 1 then
   3.483 +              result = values[1]
   3.484 +            else
   3.485 +              result = false
   3.486 +            end
   3.487 +            self[key] = result
   3.488 +            return result
   3.489 +          end
   3.490 +        }),
   3.491 +        -- table mapping header field names to a flag table,
   3.492 +        -- indicating if the comma separated value contains certain entries:
   3.493 +        headers_flags = setmetatable({}, {
   3.494 +          __index = function(self, key)
   3.495 +            local result = setmetatable({}, {
   3.496 +              __index = function(self, key)
   3.497 +                local lowerkey = string.lower(key)
   3.498 +                local result = rawget(self, lowerkey) or false
   3.499 +                self[lowerkey] = result
   3.500 +                self[key] = result
   3.501 +                return result
   3.502 +              end
   3.503 +            })
   3.504 +            for i, value in ipairs(request.headers_csv_table[key]) do
   3.505 +              result[string.lower(value)] = true
   3.506 +            end
   3.507 +            self[key] = result
   3.508 +            return result
   3.509 +          end
   3.510 +        }),
   3.511 +        -- register POST param stream handler for a single field name:
   3.512 +        stream_post_param = function(self, field_name, callback)
   3.513 +          if input_state == "inprogress" or input_state == "finished" then
   3.514 +            error("Cannot register POST param streaming function if request body is already processed")
   3.515 +          end
   3.516 +          streamed_post_params[field_name] = callback
   3.517 +        end,
   3.518 +        -- register POST param stream handler for a field name pattern:
   3.519 +        stream_post_params = function(self, pattern, callback)
   3.520 +          if input_state == "inprogress" or input_state == "finished" then
   3.521 +            error("Cannot register POST param streaming function if request body is already processed")
   3.522 +          end
   3.523 +          streamed_post_param_patterns[#streamed_post_param_patterns+1] = {pattern, callback}
   3.524 +        end,
   3.525 +        -- disables automatic request body processing on write
   3.526 +        -- (use with caution):
   3.527 +        defer_reading = function(self)
   3.528 +          if input_state == "pending" then
   3.529 +            input_state = "deferred"
   3.530 +          end
   3.531 +        end,
   3.532 +        -- processes the request body and sets the request.post_params,
   3.533 +        -- request.post_params_list, request.meta_post_params, and
   3.534 +        -- request.meta_post_params_list values (can be called manually or
   3.535 +        -- automatically if post_params are accessed or data is written out)
   3.536 +        process_request_body = function(self)
   3.537 +          if input_state == "finished" then
   3.538 +            return
   3.539 +          end
   3.540 +          local post_params_list, post_params = new_params_list()
   3.541 +          local content_type = request.headers_value["Content-Type"]
   3.542 +          if content_type then
   3.543 +            if
   3.544 +              content_type == "application/x-www-form-urlencoded" or
   3.545 +              string.match(content_type, "^application/x%-www%-form%-urlencoded *;")
   3.546 +            then
   3.547 +              read_urlencoded_form(post_params_list, request.body)
   3.548 +            else
   3.549 +              local boundary = string.match(
   3.550 +                content_type,
   3.551 +                '^multipart/form%-data[ \t]*[;,][ \t]*boundary="([^"]+)"$'
   3.552 +              ) or string.match(
   3.553 +                content_type,
   3.554 +                '^multipart/form%-data[ \t]*[;,][ \t]*boundary=([^"; \t]+)$'
   3.555 +              )
   3.556 +              if boundary then
   3.557 +                local post_metadata_list, post_metadata = new_params_list()
   3.558 +                boundary = "--" .. boundary
   3.559 +                local headerdata = ""
   3.560 +                local streamer
   3.561 +                local field_name
   3.562 +                local metadata = {}
   3.563 +                local value_parts
   3.564 +                local function default_streamer(chunk)
   3.565 +                  value_parts[#value_parts+1] = chunk
   3.566 +                end
   3.567 +                local function stream_part_finish()
   3.568 +                  if streamer == default_streamer then
   3.569 +                    local value = table.concat(value_parts)
   3.570 +                    value_parts = nil
   3.571 +                    if field_name then
   3.572 +                      local values = post_params_list[field_name]
   3.573 +                      values[#values+1] = value
   3.574 +                      local metadata_entries = post_metadata_list[field_name]
   3.575 +                      metadata_entries[#metadata_entries+1] = metadata
   3.576 +                    end
   3.577 +                  else
   3.578 +                    streamer()
   3.579 +                  end
   3.580 +                  headerdata   = ""
   3.581 +                  streamer     = nil
   3.582 +                  field_name   = nil
   3.583 +                  metadata     = {}
   3.584 +                end
   3.585 +                local function stream_part_chunk(chunk)
   3.586 +                  if streamer then
   3.587 +                    streamer(chunk)
   3.588 +                  else
   3.589 +                    headerdata = headerdata .. chunk
   3.590 +                    while true do
   3.591 +                      local line, remaining = string.match(headerdata, "^(.-)\r?\n(.*)$")
   3.592 +                      if not line then
   3.593 +                        break
   3.594 +                      end
   3.595 +                      if line == "" then
   3.596 +                        streamer = streamed_post_params[field_name]
   3.597 +                        if not streamer then
   3.598 +                          for i, rule in ipairs(streamed_post_param_patterns) do
   3.599 +                            if string.match(field_name, rule[1]) then
   3.600 +                              streamer = rule[2]
   3.601 +                              break
   3.602 +                            end
   3.603 +                          end
   3.604 +                        end
   3.605 +                        if not streamer then
   3.606 +                          value_parts = {}
   3.607 +                          streamer = default_streamer
   3.608 +                        end
   3.609 +                        streamer(remaining, field_name, metadata)
   3.610 +                        return
   3.611 +                      end
   3.612 +                      headerdata = remaining
   3.613 +                      local header_key, header_value = string.match(line, "^([^:]*):[ \t]*(.-)[ \t]*$")
   3.614 +                      if not header_key then
   3.615 +                        error("Invalid header in multipart/form-data part")
   3.616 +                      end
   3.617 +                      header_key = string.lower(header_key)
   3.618 +                      if header_key == "content-disposition" then
   3.619 +                        local escaped_header_value = string.gsub(header_value, '"[^"]*"', function(str)
   3.620 +                          return string.gsub(str, "=", "==")
   3.621 +                        end)
   3.622 +                        field_name = string.match(escaped_header_value, ';[ \t]*name="([^"]*)"')
   3.623 +                        if field_name then
   3.624 +                          field_name = string.gsub(field_name, "==", "=")
   3.625 +                        else
   3.626 +                          field_name = string.match(header_value, ';[ \t]*name=([^"; \t]+)')
   3.627 +                        end
   3.628 +                        metadata.file_name = string.match(escaped_header_value, ';[ \t]*filename="([^"]*)"')
   3.629 +                        if metadata.file_name then
   3.630 +                          metadata.file_name = string.gsub(metadata.file_name, "==", "=")
   3.631 +                        else
   3.632 +                          string.match(header_value, ';[ \t]*filename=([^"; \t]+)')
   3.633 +                        end
   3.634 +                      elseif header_key == "content-type" then
   3.635 +                        metadata.content_type = header_value
   3.636 +                      elseif header_key == "content-transfer-encoding" then
   3.637 +                        error("Content-transfer-encoding not supported by multipart/form-data parser")
   3.638 +                      end
   3.639 +                    end
   3.640 +                  end
   3.641 +                end
   3.642 +                local skippart   = true   -- ignore data until first boundary
   3.643 +                local afterbound = false  -- interpret 2 bytes after boundary ("\r\n" or "--")
   3.644 +                local terminated = false  -- final boundary read
   3.645 +                local bigchunk = ""
   3.646 +                request:stream_request_body(function(chunk)
   3.647 +                  if terminated then
   3.648 +                    return
   3.649 +                  end
   3.650 +                  bigchunk = bigchunk .. chunk
   3.651 +                  while true do
   3.652 +                    if afterbound then
   3.653 +                      if #bigchunk <= 2 then
   3.654 +                        return
   3.655 +                      end
   3.656 +                      local terminator = string.sub(bigchunk, 1, 2)
   3.657 +                      if terminator == "\r\n" then
   3.658 +                        afterbound = false
   3.659 +                        bigchunk = string.sub(bigchunk, 3)
   3.660 +                      elseif terminator == "--" then
   3.661 +                        terminated = true
   3.662 +                        bigchunk = nil
   3.663 +                        return
   3.664 +                      else
   3.665 +                        error("Error while parsing multipart body (expected CRLF or double minus)")
   3.666 +                      end
   3.667 +                    end
   3.668 +                    local pos1, pos2 = string.find(bigchunk, boundary, 1, true)
   3.669 +                    if not pos1 then
   3.670 +                      if not skippart then
   3.671 +                        local safe = #bigchunk-#boundary
   3.672 +                        if safe > 0 then
   3.673 +                          stream_part_chunk(string.sub(bigchunk, 1, safe))
   3.674 +                          bigchunk = string.sub(bigchunk, safe+1)
   3.675 +                        end
   3.676 +                      end
   3.677 +                      return
   3.678 +                    end
   3.679 +                    if not skippart then
   3.680 +                      stream_part_chunk(string.sub(bigchunk, 1, pos1 - 1))
   3.681 +                      stream_part_finish()
   3.682 +                    else
   3.683 +                      boundary = "\r\n" .. boundary
   3.684 +                      skippart = false
   3.685 +                    end
   3.686 +                    bigchunk = string.sub(bigchunk, pos2 + 1)
   3.687 +                    afterbound = true
   3.688 +                  end
   3.689 +                end)
   3.690 +                if not terminated then
   3.691 +                  error("Premature end of multipart/form-data request body")
   3.692 +                end
   3.693 +                request.post_metadata_list, request.post_metadata = post_metadata_list, post_metadata
   3.694 +              else
   3.695 +                error("Unknown Content-Type of request body")
   3.696 +              end
   3.697 +            end
   3.698 +          end
   3.699 +          request.post_params_list, request.post_params = post_params_list, post_params
   3.700 +        end,
   3.701 +        -- stream request body to an (optional) callback function
   3.702 +        -- without processing it otherwise:
   3.703 +        stream_request_body = function(self, callback)
   3.704 +          if input_state ~= "pending" and input_state ~= "deferred" then
   3.705 +            if callback then
   3.706 +              if input_state == "inprogress" then
   3.707 +                error("Request body is already being processed")
   3.708 +              else
   3.709 +                error("Request body has already been processed")
   3.710 +              end
   3.711 +            end
   3.712 +            return
   3.713 +          end
   3.714 +          input_state = "inprogress"
   3.715 +          if request.headers_flags["Expect"]["100-continue"] then
   3.716 +            request:send_status("100 Continue")
   3.717 +            request:finish_headers()
   3.718 +          end
   3.719 +          if request.headers_flags["Transfer-Encoding"]["chunked"] then
   3.720 +            while true do
   3.721 +              local line = socket:readuntil("\n", 32 + remaining_body_size_limit)
   3.722 +              if not line then
   3.723 +                error("Unexpected EOF while reading next chunk of request body")
   3.724 +              end
   3.725 +              local zeros, lenstr = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)\r?\n$")
   3.726 +              local chunkext
   3.727 +              if lenstr then
   3.728 +                chunkext = ""
   3.729 +              else
   3.730 +                zeros, lenstr, chunkext = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)([ \t;].-)\r?\n$")
   3.731 +              end
   3.732 +              if not lenstr or #lenstr > 13 then
   3.733 +                error("Encoding error or unexpected EOF while reading chunk of request body")
   3.734 +              end
   3.735 +              local len = tonumber("0x" .. lenstr)
   3.736 +              remaining_body_size_limit = remaining_body_size_limit - (#zeros + #chunkext + len)
   3.737 +              if remaining_body_size_limit < 0 then
   3.738 +                error("Request body size limit exceeded")
   3.739 +              end
   3.740 +              if len == 0 then break end
   3.741 +              read_body_bytes(len, callback)
   3.742 +              local term = socket:readuntil("\n", 2)
   3.743 +              if term ~= "\r\n" and term ~= "\n" then
   3.744 +                error("Encoding error while reading chunk of request body")
   3.745 +              end
   3.746 +            end
   3.747 +            while true do
   3.748 +              local line = socket:readuntil("\n", 2 + remaining_body_size_limit)
   3.749 +              if line == "\r\n" or line == "\n" then break end
   3.750 +              remaining_body_size_limit = remaining_body_size_limit - #line
   3.751 +              if remaining_body_size_limit < 0 then
   3.752 +                error("Request body size limit exceeded while reading trailer section of chunked request body")
   3.753 +              end
   3.754 +            end
   3.755 +          elseif request_body_content_length then
   3.756 +            read_body_bytes(request_body_content_length, callback)
   3.757 +          end
   3.758 +          input_state = "finished"
   3.759 +        end
   3.760 +      }
   3.761 +      -- initialize tables for GET params in request object:
   3.762 +      request.get_params_list, request.get_params = new_params_list()
   3.763 +      -- add meta table to request object to allow access to "body" and POST params:
   3.764 +      setmetatable(request, {
   3.765 +        __index = function(self, key)
   3.766 +          if key == "body" then
   3.767 +            local chunks = {}
   3.768 +            request:stream_request_body(function(chunk)
   3.769 +              chunks[#chunks+1] = chunk
   3.770 +            end)
   3.771 +            self.body = table.concat(chunks)
   3.772 +            return self.body
   3.773 +          elseif
   3.774 +            key == "post_params_list" or key == "post_params" or
   3.775 +            key == "post_metadata_list" or key == "post_metadata"
   3.776 +          then
   3.777 +            request:process_request_body()
   3.778 +            return request[key]
   3.779 +          end
   3.780 +        end
   3.781 +      })
   3.782 +      -- low level HTTP error response (for malformed requests, etc.):
   3.783 +      local function error_response(status, text)
   3.784 +        request:send_status(status)
   3.785 +        request:send_header("Content-Type", "text/plain")
   3.786 +        if not connection_close_responded then
   3.787 +          request:send_header("Connection", "close")
   3.788 +        end
   3.789 +        request:send_data(status, "\n")
   3.790 +        if text then
   3.791 +          request:send_data("\n", text, "\n")
   3.792 +        end
   3.793 +        request:finish()
   3.794 +        return survive
   3.795 +      end
   3.796 +      -- read and parse request line:
   3.797 +      local line = socket:readuntil("\n", remaining_header_size_limit)
   3.798 +      if not line then return survive end
   3.799 +      remaining_header_size_limit = remaining_header_size_limit - #line
   3.800 +      if remaining_header_size_limit == 0 then
   3.801 +        return error_response("413 Request Entity Too Large", "Request line too long")
   3.802 +      end
   3.803 +      local proto
   3.804 +      request.method, request.url, proto =
   3.805 +        line:match("^([^ \t\r]+)[ \t]+([^ \t\r]+)[ \t]*([^ \t\r]*)[ \t]*\r?\n$")
   3.806 +      if not request.method then
   3.807 +        return error_response("400 Bad Request")
   3.808 +      elseif proto ~= "HTTP/1.1" then
   3.809 +        return error_response("505 HTTP Version Not Supported")
   3.810 +      else
   3.811 +        -- read and parse headers:
   3.812 +        while true do
   3.813 +          local line = socket:readuntil("\n", remaining_header_size_limit);
   3.814 +          remaining_header_size_limit = remaining_header_size_limit - #line
   3.815 +          if not line then
   3.816 +            return error_response("400 Bad Request")
   3.817 +          end
   3.818 +          if line == "\r\n" or line == "\n" then
   3.819 +            break
   3.820 +          end
   3.821 +          if remaining_header_size_limit == 0 then
   3.822 +            return error_response("413 Request Entity Too Large", "Headers too long")
   3.823 +          end
   3.824 +          local key, value = string.match(line, "^([^ \t\r]+):[ \t]*(.-)[ \t]*\r?\n$")
   3.825 +          if not key then
   3.826 +            return error_response("400 Bad Request")
   3.827 +          end
   3.828 +          local values = request.headers[key]
   3.829 +          values[#values+1] = value
   3.830 +        end
   3.831 +        -- process "Connection: close" header if existent:
   3.832 +        connection_close_requested = request.headers_flags["Connection"]["close"]
   3.833 +        -- process "Content-Length" header if existent:
   3.834 +        do
   3.835 +          local values = request.headers_csv_table["Content-Length"]
   3.836 +          if #values > 0 then
   3.837 +            request_body_content_length = tonumber(values[1])
   3.838 +            local proper_value = tostring(request_body_content_length)
   3.839 +            for i, value in ipairs(values) do
   3.840 +              value = string.match(value, "^0*(.*)")
   3.841 +              if value ~= proper_value then
   3.842 +                return error_response("400 Bad Request", "Content-Length header(s) invalid")
   3.843 +              end
   3.844 +            end
   3.845 +            if request_body_content_length > remaining_body_size_limit then
   3.846 +              return error_response("413 Request Entity Too Large", "Request body too big")
   3.847 +            end
   3.848 +          end
   3.849 +        end
   3.850 +        -- process "Transfer-Encoding" header if existent:
   3.851 +        do
   3.852 +          local flag = request.headers_flags["Transfer-Encoding"]["chunked"]
   3.853 +          local list = request.headers_csv_table["Transfer-Encoding"]
   3.854 +          if (flag and #list ~= 1) or (not flag and #list ~= 0) then
   3.855 +            return error_response("400 Bad Request", "Unexpected Transfer-Encoding")
   3.856 +          end
   3.857 +        end
   3.858 +        -- process "Expect" header if existent:
   3.859 +        for i, value in ipairs(request.headers_csv_table["Expect"]) do
   3.860 +          if string.lower(value) ~= "100-continue" then
   3.861 +            return error_response("417 Expectation Failed", "Unexpected Expect header")
   3.862 +          end
   3.863 +        end
   3.864 +        -- parse GET params:
   3.865 +        request.path, request.query = string.match(request.url, "^([^?]*)%??(.*)$")
   3.866 +        read_urlencoded_form(request.get_params_list, request.query)
   3.867 +        -- parse cookies:
   3.868 +        for i, line in ipairs(request.headers["Cookie"]) do
   3.869 +          for rawkey, rawvalue in
   3.870 +            string.gmatch(line, "([^=; ]*)=([^=; ]*)")
   3.871 +          do
   3.872 +            request.cookies[decode_uri(rawkey)] = decode_uri(rawvalue)
   3.873 +          end
   3.874 +        end
   3.875 +        -- call underlying handler and remember boolean result:
   3.876 +        if handler(request) ~= true then survive = false end
   3.877 +        -- finish request (unless already done by underlying handler):
   3.878 +        request:finish()
   3.879 +      end
   3.880 +    end
   3.881 +    return survive
   3.882 +  end
   3.883 +end
   3.884 +
   3.885 +return _M
   3.886 +

Impressum / About Us