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