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