* luci: fixup svn properties
[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 }