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