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