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