* luci/libs/http: fix two typos in http.protocol
[project/luci.git] / libs / http / luasrc / http / protocol.lua
1 --[[
2
3 HTTP protocol implementation for LuCI
4 (c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10         http://www.apache.org/licenses/LICENSE-2.0
11
12 $Id$
13
14 ]]--
15
16 module("luci.http.protocol", package.seeall)
17
18 local ltn12 = require("luci.ltn12")
19
20 HTTP_MAX_CONTENT      = 1024*4          -- 4 kB maximum content size
21 HTTP_URLENC_MAXKEYLEN = 1024            -- maximum allowd size of urlencoded parameter names
22
23
24 -- Decode an urlencoded string.
25 -- Returns the decoded value.
26 function urldecode( str, no_plus )
27
28         local function __chrdec( hex )
29                 return string.char( tonumber( hex, 16 ) )
30         end
31
32         if type(str) == "string" then
33                 if not no_plus then
34                         str = str:gsub( "+", " " )
35                 end
36
37                 str = str:gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec )
38         end
39
40         return str
41 end
42
43
44 -- Extract and split urlencoded data pairs, separated bei either "&" or ";" from given url.
45 -- Returns a table value with urldecoded values.
46 function urldecode_params( url, tbl )
47
48         local params = tbl or { }
49
50         if url:find("?") then
51                 url = url:gsub( "^.+%?([^?]+)", "%1" )
52         end
53
54         for pair in url:gmatch( "[^&;]+" ) do
55
56                 -- find key and value
57                 local key = urldecode( pair:match("^([^=]+)")     )
58                 local val = urldecode( pair:match("^[^=]+=(.+)$") )
59
60                 -- store
61                 if type(key) == "string" and key:len() > 0 then
62                         if type(val) ~= "string" then val = "" end
63
64                         if not params[key] then
65                                 params[key] = val
66                         elseif type(params[key]) ~= "table" then
67                                 params[key] = { params[key], val }
68                         else
69                                 table.insert( params[key], val )
70                         end
71                 end
72         end
73
74         return params
75 end
76
77
78 -- Encode given string in urlencoded format.
79 -- Returns the encoded string.
80 function urlencode( str )
81
82         local function __chrenc( chr )
83                 return string.format(
84                         "%%%02x", string.byte( chr )
85                 )
86         end
87
88         if type(str) == "string" then
89                 str = str:gsub(
90                         "([^a-zA-Z0-9$_%-%.%+!*'(),])",
91                         __chrenc
92                 )
93         end
94
95         return str
96 end
97
98
99 -- Encode given table to urlencoded string.
100 -- Returns the encoded string.
101 function urlencode_params( tbl )
102         local enc = ""
103
104         for k, v in pairs(tbl) do
105                 enc = enc .. ( enc and "&" or "" ) ..
106                         urlencode(k) .. "="  ..
107                         urlencode(v)
108         end
109
110         return enc
111 end
112
113
114 -- Parameter helper
115 local function __initval( tbl, key )
116         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 ~= nil 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 ~= nil 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 }