* luci/libs/http: implement multi-value support in http.protocol
[project/luci.git] / libs / http / luasrc / http / protocol.lua
1 --[[
2
3 HTTP protocol implementation for LuCI
4 (c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 $Id$
13
14 ]]--
15
16 module("luci.http.protocol", package.seeall)
17
18 local ltn12 = require("luci.ltn12")
19
20 HTTP_MAX_CONTENT = 1024*4 -- 4 kB maximum content size
21 HTTP_URLENC_MAXKEYLEN = 1024 -- maximum allowd size of urlencoded parameter names
22
23
24 -- Decode an urlencoded string.
25 -- Returns the decoded value.
26 function urldecode( str, no_plus )
27
28 local function __chrdec( hex )
29 return string.char( tonumber( hex, 16 ) )
30 end
31
32 if type(str) == "string" then
33 if not no_plus then
34 str = str:gsub( "+", " " )
35 end
36
37 str = str:gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec )
38 end
39
40 return str
41 end
42
43
44 -- Extract and split urlencoded data pairs, separated bei either "&" or ";" from given url.
45 -- Returns a table value with urldecoded values.
46 function urldecode_params( url, tbl )
47
48 local params = tbl or { }
49
50 if url:find("?") then
51 url = url:gsub( "^.+%?([^?]+)", "%1" )
52 end
53
54 for pair in url:gmatch( "[^&;]+" ) do
55
56 -- find key and value
57 local key = urldecode( pair:match("^([^=]+)") )
58 local val = urldecode( pair:match("^[^=]+=(.+)$") )
59
60 -- store
61 if type(key) == "string" and key:len() > 0 then
62 if type(val) ~= "string" then val = "" end
63
64 if not params[key] then
65 params[key] = val
66 elseif type(params[key]) ~= "table" then
67 params[key] = { params[key], val }
68 else
69 table.insert( params[key], val )
70 end
71 end
72 end
73
74 return params
75 end
76
77
78 -- Encode given string in urlencoded format.
79 -- Returns the encoded string.
80 function urlencode( str )
81
82 local function __chrenc( chr )
83 return string.format(
84 "%%%02x", string.byte( chr )
85 )
86 end
87
88 if type(str) == "string" then
89 str = str:gsub(
90 "([^a-zA-Z0-9$_%-%.%+!*'(),])",
91 __chrenc
92 )
93 end
94
95 return str
96 end
97
98
99 -- Encode given table to urlencoded string.
100 -- Returns the encoded string.
101 function urlencode_params( tbl )
102 local enc = ""
103
104 for k, v in pairs(tbl) do
105 enc = enc .. ( enc and "&" or "" ) ..
106 urlencode(k) .. "=" ..
107 urlencode(v)
108 end
109
110 return enc
111 end
112
113
114 -- Parameter helper
115 local function __initval( tbl, key )
116 local multival = ( key:sub( #key - 1, #key ) == "[]" )
117
118 if multival then
119 if type(tbl[key]) == "table" then
120 table.insert( tbl[key], "" )
121 else
122 tbl[key] = { "" }
123 end
124 else
125 tbl[key] = ""
126 end
127
128 return multival
129 end
130
131 local function __appendval( tbl, key, multival, chunk )
132 if multival then
133 tbl[key][#tbl[key]] = tbl[key][#tbl[key]] .. chunk
134 else
135 tbl[key] = tbl[key] .. chunk
136 end
137 end
138
139 local function __finishval( tbl, key, multival, handler )
140 if handler then
141 if multival then
142 tbl[key][#tbl[key]] = handler( tbl[key][#tbl[key]] )
143 else
144 tbl[key] = handler( tbl[key] )
145 end
146 end
147 end
148
149
150 -- Table of our process states
151 local process_states = { }
152
153 -- Extract "magic", the first line of a http message.
154 -- Extracts the message type ("get", "post" or "response"), the requested uri
155 -- or the status code if the line descripes a http response.
156 process_states['magic'] = function( msg, chunk, err )
157
158 if chunk ~= nil then
159 -- ignore empty lines before request
160 if #chunk == 0 then
161 return true, nil
162 end
163
164 -- Is it a request?
165 local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
166
167 -- Yup, it is
168 if method then
169
170 msg.type = "request"
171 msg.request_method = method:lower()
172 msg.request_uri = uri
173 msg.http_version = tonumber( http_ver )
174 msg.headers = { }
175
176 -- We're done, next state is header parsing
177 return true, function( chunk )
178 return process_states['headers']( msg, chunk )
179 end
180
181 -- Is it a response?
182 else
183
184 local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
185
186 -- Is a response
187 if code then
188
189 msg.type = "response"
190 msg.status_code = code
191 msg.status_message = message
192 msg.http_version = tonumber( http_ver )
193 msg.headers = { }
194
195 -- We're done, next state is header parsing
196 return true, function( chunk )
197 return process_states['headers']( msg, chunk )
198 end
199 end
200 end
201 end
202
203 -- Can't handle it
204 return nil, "Invalid HTTP message magic"
205 end
206
207
208 -- Extract headers from given string.
209 process_states['headers'] = function( msg, chunk )
210
211 if chunk ~= nil then
212
213 -- Look for a valid header format
214 local hdr, val = chunk:match( "^([A-Z][A-Za-z0-9%-_]+): +(.+)$" )
215
216 if type(hdr) == "string" and hdr:len() > 0 and
217 type(val) == "string" and val:len() > 0
218 then
219 msg.headers[hdr] = val
220
221 -- Valid header line, proceed
222 return true, nil
223
224 elseif #chunk == 0 then
225 -- Empty line, we won't accept data anymore
226 return false, nil
227 else
228 -- Junk data
229 return nil, "Invalid HTTP header received"
230 end
231 else
232 return nil, "Unexpected EOF"
233 end
234 end
235
236
237 -- Find first MIME boundary
238 process_states['mime-init'] = function( msg, chunk, filecb )
239
240 if chunk ~= nil then
241 if #chunk >= #msg.mime_boundary + 2 then
242 local boundary = chunk:sub( 1, #msg.mime_boundary + 4 )
243
244 if boundary == "--" .. msg.mime_boundary .. "\r\n" then
245
246 -- Store remaining data in buffer
247 msg._mimebuffer = chunk:sub( #msg.mime_boundary + 5, #chunk )
248
249 -- Switch to header processing state
250 return true, function( chunk )
251 return process_states['mime-headers']( msg, chunk, filecb )
252 end
253 else
254 return nil, "Invalid MIME boundary"
255 end
256 else
257 return true
258 end
259 else
260 return nil, "Unexpected EOF"
261 end
262 end
263
264
265 -- Read MIME part headers
266 process_states['mime-headers'] = function( msg, chunk, filecb )
267
268 if chunk ~= nil then
269
270 -- Combine look-behind buffer with current chunk
271 chunk = msg._mimebuffer .. chunk
272
273 if not msg._mimeheaders then
274 msg._mimeheaders = { }
275 end
276
277 local function __storehdr( k, v )
278 msg._mimeheaders[k] = v
279 return ""
280 end
281
282 -- Read all header lines
283 local ok, count = 1, 0
284 while ok > 0 do
285 chunk, ok = chunk:gsub( "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n", __storehdr )
286 count = count + ok
287 end
288
289 -- Headers processed, check for empty line
290 chunk, ok = chunk:gsub( "^\r\n", "" )
291
292 -- Store remaining buffer contents
293 msg._mimebuffer = chunk
294
295 -- End of headers
296 if ok > 0 then
297
298 -- When no Content-Type header is given assume text/plain
299 if not msg._mimeheaders['Content-Type'] then
300 msg._mimeheaders['Content-Type'] = 'text/plain'
301 end
302
303 -- Check Content-Disposition
304 if msg._mimeheaders['Content-Disposition'] then
305 -- Check for "form-data" token
306 if msg._mimeheaders['Content-Disposition']:match("^form%-data; ") then
307 -- Check for field name, filename
308 local field = msg._mimeheaders['Content-Disposition']:match('name="(.-)"')
309 local file = msg._mimeheaders['Content-Disposition']:match('filename="(.+)"$')
310
311 -- Is a file field and we have a callback
312 if file and filecb then
313 msg.params[field] = file
314 msg._mimecallback = function(chunk,eof)
315 filecb( {
316 name = field;
317 file = file;
318 headers = msg._mimeheaders
319 }, chunk, eof )
320 end
321
322 -- Treat as form field
323 else
324 local mv = __initval( msg.params, field )
325 msg._mimecallback = function(chunk,eof)
326 __appendval( msg.params, field, mv, chunk )
327 end
328 end
329
330 -- Header was valid, continue with mime-data
331 return true, function( chunk )
332 return process_states['mime-data']( msg, chunk, filecb )
333 end
334 else
335 -- Unknown Content-Disposition, abort
336 return nil, "Unexpected Content-Disposition MIME section header"
337 end
338 else
339 -- Content-Disposition is required, abort without
340 return nil, "Missing Content-Disposition MIME section header"
341 end
342
343 -- We parsed no headers yet and buffer is almost empty
344 elseif count > 0 or #chunk < 128 then
345 -- Keep feeding me with chunks
346 return true, nil
347 end
348
349 -- Buffer looks like garbage
350 return nil, "Malformed MIME section header"
351 else
352 return nil, "Unexpected EOF"
353 end
354 end
355
356
357 -- Read MIME part data
358 process_states['mime-data'] = function( msg, chunk, filecb )
359
360 if chunk ~= nil then
361
362 -- Combine look-behind buffer with current chunk
363 local buffer = msg._mimebuffer .. chunk
364
365 -- Look for MIME boundary
366 local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
367
368 if spos then
369 -- Content data
370 msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
371
372 -- Store remainder
373 msg._mimebuffer = buffer:sub( epos + 1, #buffer )
374
375 -- Next state is mime-header processing
376 return true, function( chunk )
377 return process_states['mime-headers']( msg, chunk, filecb )
378 end
379 else
380 -- Look for EOF?
381 local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
382
383 if spos then
384 -- Content data
385 msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
386
387 -- We processed the final MIME boundary, cleanup
388 msg._mimebuffer = nil
389 msg._mimeheaders = nil
390 msg._mimecallback = nil
391
392 -- We won't accept data anymore
393 return false
394 else
395 -- We're somewhere within a data section and our buffer is full
396 if #buffer > #chunk then
397 -- Flush buffered data
398 msg._mimecallback( buffer:sub( 1, #buffer - #chunk ), false )
399
400 -- Store new data
401 msg._mimebuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
402
403 -- Buffer is not full yet, append new data
404 else
405 msg._mimebuffer = buffer
406 end
407
408 -- Keep feeding me
409 return true
410 end
411 end
412 else
413 return nil, "Unexpected EOF"
414 end
415 end
416
417
418 -- Init urldecoding stream
419 process_states['urldecode-init'] = function( msg, chunk, filecb )
420
421 if chunk ~= nil then
422
423 -- Check for Content-Length
424 if msg.env.CONTENT_LENGTH then
425 msg.content_length = tonumber(msg.env.CONTENT_LENGTH)
426
427 if msg.content_length <= HTTP_MAX_CONTENT then
428 -- Initialize buffer
429 msg._urldecbuffer = chunk
430 msg._urldeclength = 0
431
432 -- Switch to urldecode-key state
433 return true, function(chunk)
434 return process_states['urldecode-key']( msg, chunk, filecb )
435 end
436 else
437 return nil, "Request exceeds maximum allowed size"
438 end
439 else
440 return nil, "Missing Content-Length header"
441 end
442 else
443 return nil, "Unexpected EOF"
444 end
445 end
446
447
448 -- Process urldecoding stream, read and validate parameter key
449 process_states['urldecode-key'] = function( msg, chunk, filecb )
450 if chunk ~= nil then
451
452 -- Prevent oversized requests
453 if msg._urldeclength >= msg.content_length then
454 return nil, "Request exceeds maximum allowed size"
455 end
456
457 -- Combine look-behind buffer with current chunk
458 local buffer = msg._urldecbuffer .. chunk
459 local spos, epos = buffer:find("=")
460
461 -- Found param
462 if spos then
463
464 -- Check that key doesn't exceed maximum allowed key length
465 if ( spos - 1 ) <= HTTP_URLENC_MAXKEYLEN then
466 local key = urldecode( buffer:sub( 1, spos - 1 ) )
467
468 -- Prepare buffers
469 msg.params[key] = ""
470 msg._urldeclength = msg._urldeclength + epos
471 msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
472
473 -- Use file callback or store values inside msg.params
474 if filecb then
475 msg._urldeccallback = function( chunk, eof )
476 filecb( field, chunk, eof )
477 end
478 else
479 local mv = __initval( msg.params, key )
480 msg._urldeccallback = function( chunk, eof )
481 __appendval( msg.params, key, mv, chunk )
482
483 -- FIXME: Use a filter
484 if eof then
485 __finishval( msg.params, key, mv, urldecode )
486 end
487 end
488 end
489
490 -- Proceed with urldecode-value state
491 return true, function( chunk )
492 return process_states['urldecode-value']( msg, chunk, filecb )
493 end
494 else
495 return nil, "POST parameter exceeds maximum allowed length"
496 end
497 else
498 return nil, "POST data exceeds maximum allowed length"
499 end
500 else
501 return nil, "Unexpected EOF"
502 end
503 end
504
505
506 -- Process urldecoding stream, read parameter value
507 process_states['urldecode-value'] = function( msg, chunk, filecb )
508
509 if chunk ~= nil then
510
511 -- Combine look-behind buffer with current chunk
512 local buffer = msg._urldecbuffer .. chunk
513
514 -- Check for EOF
515 if #buffer == 0 then
516 -- Compare processed length
517 if msg._urldeclength == msg.content_length then
518 -- Cleanup
519 msg._urldeclength = nil
520 msg._urldecbuffer = nil
521 msg._urldeccallback = nil
522
523 -- We won't accept data anymore
524 return false
525 else
526 return nil, "Content-Length mismatch"
527 end
528 end
529
530 -- Check for end of value
531 local spos, epos = buffer:find("[&;]")
532 if spos then
533
534 -- Flush buffer, send eof
535 msg._urldeccallback( buffer:sub( 1, spos - 1 ), true )
536 msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
537 msg._urldeclength = msg._urldeclength + epos
538
539 -- Back to urldecode-key state
540 return true, function( chunk )
541 return process_states['urldecode-key']( msg, chunk, filecb )
542 end
543 else
544 -- We're somewhere within a data section and our buffer is full
545 if #buffer > #chunk then
546 -- Flush buffered data
547 msg._urldeccallback( buffer:sub( 1, #buffer - #chunk ), false )
548
549 -- Store new data
550 msg._urldeclength = msg._urldeclength + #buffer - #chunk
551 msg._urldecbuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
552
553 -- Buffer is not full yet, append new data
554 else
555 msg._urldecbuffer = buffer
556 end
557
558 -- Keep feeding me
559 return true
560 end
561 else
562 -- Send EOF
563 msg._urldeccallback( "", true )
564 return false
565 end
566 end
567
568
569 -- Creates a header source from a given socket
570 function header_source( sock )
571 return ltn12.source.simplify( function()
572
573 local chunk, err, part = sock:receive("*l")
574
575 -- Line too long
576 if chunk == nil then
577 if err ~= "timeout" then
578 return nil, part
579 and "Line exceeds maximum allowed length"
580 or "Unexpected EOF"
581 else
582 return nil, err
583 end
584
585 -- Line ok
586 elseif chunk ~= nil then
587
588 -- Strip trailing CR
589 chunk = chunk:gsub("\r$","")
590
591 return chunk, nil
592 end
593 end )
594 end
595
596
597 -- Decode MIME encoded data.
598 function mimedecode_message_body( source, msg, filecb )
599
600 -- Find mime boundary
601 if msg and msg.env.CONTENT_TYPE then
602
603 local bound = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)")
604
605 if bound then
606 msg.mime_boundary = bound
607 else
608 return nil, "No MIME boundary found or invalid content type given"
609 end
610 end
611
612 -- Create an initial LTN12 sink
613 -- The whole MIME parsing process is implemented as fancy sink, sinks replace themself
614 -- depending on current processing state (init, header, data). Return the initial state.
615 local sink = ltn12.sink.simplify(
616 function( chunk )
617 return process_states['mime-init']( msg, chunk, filecb )
618 end
619 )
620
621 -- Create a throttling LTN12 source
622 -- Frequent state switching in the mime parsing process leads to unwanted buffer aggregation.
623 -- This source checks wheather there's still data in our internal read buffer and returns an
624 -- empty string if there's already enough data in the processing queue. If the internal buffer
625 -- runs empty we're calling the original source to get the next chunk of data.
626 local tsrc = function()
627
628 -- XXX: we schould propably keep the maximum buffer size in sync with
629 -- the blocksize of our original source... but doesn't really matter
630 if msg._mimebuffer ~= null and #msg._mimebuffer > 256 then
631 return ""
632 else
633 return source()
634 end
635 end
636
637 -- Pump input data...
638 while true do
639 -- get data
640 local ok, err = ltn12.pump.step( tsrc, sink )
641
642 -- error
643 if not ok and err then
644 return nil, err
645
646 -- eof
647 elseif not ok then
648 return true
649 end
650 end
651 end
652
653
654 -- Decode urlencoded data.
655 function urldecode_message_body( source, msg )
656
657 -- Create an initial LTN12 sink
658 -- Return the initial state.
659 local sink = ltn12.sink.simplify(
660 function( chunk )
661 return process_states['urldecode-init']( msg, chunk )
662 end
663 )
664
665 -- Create a throttling LTN12 source
666 -- See explaination in mimedecode_message_body().
667 local tsrc = function()
668 if msg._urldecbuffer ~= null and #msg._urldecbuffer > 0 then
669 return ""
670 else
671 return source()
672 end
673 end
674
675 -- Pump input data...
676 while true do
677 -- get data
678 local ok, err = ltn12.pump.step( tsrc, sink )
679
680 -- step
681 if not ok and err then
682 return nil, err
683
684 -- eof
685 elseif not ok then
686 return true
687 end
688 end
689 end
690
691
692 -- Parse a http message header
693 function parse_message_header( source )
694
695 local ok = true
696 local msg = { }
697
698 local sink = ltn12.sink.simplify(
699 function( chunk )
700 return process_states['magic']( msg, chunk )
701 end
702 )
703
704 -- Pump input data...
705 while ok do
706
707 -- get data
708 ok, err = ltn12.pump.step( source, sink )
709
710 -- error
711 if not ok and err then
712 return nil, err
713
714 -- eof
715 elseif not ok then
716
717 -- Process get parameters
718 if ( msg.request_method == "get" or msg.request_method == "post" ) and
719 msg.request_uri:match("?")
720 then
721 msg.params = urldecode_params( msg.request_uri )
722 else
723 msg.params = { }
724 end
725
726 -- Populate common environment variables
727 msg.env = {
728 CONTENT_LENGTH = msg.headers['Content-Length'];
729 CONTENT_TYPE = msg.headers['Content-Type'];
730 REQUEST_METHOD = msg.request_method:upper();
731 REQUEST_URI = msg.request_uri;
732 SCRIPT_NAME = msg.request_uri:gsub("?.+$","");
733 SCRIPT_FILENAME = ""; -- XXX implement me
734 SERVER_PROTOCOL = "HTTP/" .. string.format("%.1f", msg.http_version)
735 }
736
737 -- Populate HTTP_* environment variables
738 for i, hdr in ipairs( {
739 'Accept',
740 'Accept-Charset',
741 'Accept-Encoding',
742 'Accept-Language',
743 'Connection',
744 'Cookie',
745 'Host',
746 'Referer',
747 'User-Agent',
748 } ) do
749 local var = 'HTTP_' .. hdr:upper():gsub("%-","_")
750 local val = msg.headers[hdr]
751
752 msg.env[var] = val
753 end
754 end
755 end
756
757 return msg
758 end
759
760
761 -- Parse a http message body
762 function parse_message_body( source, msg, filecb )
763 -- Is it multipart/mime ?
764 if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
765 msg.env.CONTENT_TYPE:match("^multipart/form%-data")
766 then
767
768 return mimedecode_message_body( source, msg, filecb )
769
770 -- Is it application/x-www-form-urlencoded ?
771 elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
772 msg.env.CONTENT_TYPE == "application/x-www-form-urlencoded"
773 then
774 return urldecode_message_body( source, msg, filecb )
775
776
777 -- Unhandled encoding
778 -- If a file callback is given then feed it chunk by chunk, else
779 -- store whole buffer in message.content
780 else
781
782 local sink
783
784 -- If we have a file callback then feed it
785 if type(filecb) == "function" then
786 sink = filecb
787
788 -- ... else append to .content
789 else
790 msg.content = ""
791 msg.content_length = 0
792
793 sink = function( chunk )
794 if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
795
796 msg.content = msg.content .. chunk
797 msg.content_length = msg.content_length + #chunk
798
799 return true
800 else
801 return nil, "POST data exceeds maximum allowed length"
802 end
803 end
804 end
805
806 -- Pump data...
807 while true do
808 local ok, err = ltn12.pump.step( source, sink )
809
810 if not ok and err then
811 return nil, err
812 elseif not err then
813 return true
814 end
815 end
816 end
817 end
818
819 -- Status codes
820 statusmsg = {
821 [200] = "OK",
822 [301] = "Moved Permanently",
823 [304] = "Not Modified",
824 [400] = "Bad Request",
825 [403] = "Forbidden",
826 [404] = "Not Found",
827 [405] = "Method Not Allowed",
828 [411] = "Length Required",
829 [412] = "Precondition Failed",
830 [500] = "Internal Server Error",
831 [503] = "Server Unavailable",
832 }