ab696d58c85f67e5ddcf911ac726e9eb93027688
[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 --- LuCI http protocol class.
17 -- This class contains several functions useful for http message- and content
18 -- decoding and to retrive form data from raw http messages.
19 module("luci.http.protocol", package.seeall)
20
21 local ltn12 = require("luci.ltn12")
22
23 HTTP_MAX_CONTENT      = 1024*8          -- 8 kB maximum content size
24
25 --- Decode an urlencoded string - optionally without decoding
26 -- the "+" sign to " " - and return the decoded string.
27 -- @param str           Input string in x-www-urlencoded format
28 -- @param no_plus       Don't decode "+" signs to spaces
29 -- @return                      The decoded string
30 -- @see                         urlencode
31 function urldecode( str, no_plus )
32
33         local function __chrdec( hex )
34                 return string.char( tonumber( hex, 16 ) )
35         end
36
37         if type(str) == "string" then
38                 if not no_plus then
39                         str = str:gsub( "+", " " )
40                 end
41
42                 str = str:gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec )
43         end
44
45         return str
46 end
47
48 --- Extract and split urlencoded data pairs, separated bei either "&" or ";"
49 -- from given url or string. Returns a table with urldecoded values.
50 -- Simple parameters are stored as string values associated with the parameter
51 -- name within the table. Parameters with multiple values are stored as array
52 -- containing the corresponding values.
53 -- @param url   The url or string which contains x-www-urlencoded form data
54 -- @param tbl   Use the given table for storing values (optional)
55 -- @return              Table containing the urldecoded parameters
56 -- @see                 urlencode_params
57 function urldecode_params( url, tbl )
58
59         local params = tbl or { }
60
61         if url:find("?") then
62                 url = url:gsub( "^.+%?([^?]+)", "%1" )
63         end
64
65         for pair in url:gmatch( "[^&;]+" ) do
66
67                 -- find key and value
68                 local key = urldecode( pair:match("^([^=]+)")     )
69                 local val = urldecode( pair:match("^[^=]+=(.+)$") )
70
71                 -- store
72                 if type(key) == "string" and key:len() > 0 then
73                         if type(val) ~= "string" then val = "" end
74
75                         if not params[key] then
76                                 params[key] = val
77                         elseif type(params[key]) ~= "table" then
78                                 params[key] = { params[key], val }
79                         else
80                                 table.insert( params[key], val )
81                         end
82                 end
83         end
84
85         return params
86 end
87
88 --- Encode given string to x-www-urlencoded format.
89 -- @param str   String to encode
90 -- @return              String containing the encoded data
91 -- @see                 urldecode
92 function urlencode( str )
93
94         local function __chrenc( chr )
95                 return string.format(
96                         "%%%02x", string.byte( chr )
97                 )
98         end
99
100         if type(str) == "string" then
101                 str = str:gsub(
102                         "([^a-zA-Z0-9$_%-%.%+!*'(),])",
103                         __chrenc
104                 )
105         end
106
107         return str
108 end
109
110 --- Encode each key-value-pair in given table to x-www-urlencoded format,
111 -- separated by "&". Tables are encoded as parameters with multiple values by
112 -- repeating the parameter name with each value.
113 -- @param tbl   Table with the values
114 -- @return              String containing encoded values
115 -- @see                 urldecode_params
116 function urlencode_params( tbl )
117         local enc = ""
118
119         for k, v in pairs(tbl) do
120                 if type(v) == "table" then
121                         for i, v2 in ipairs(v) do
122                                 enc = enc .. ( #enc > 0 and "&" or "" ) ..
123                                         urlencode(k) .. "=" .. urlencode(v2)
124                         end
125                 else
126                         enc = enc .. ( #enc > 0 and "&" or "" ) ..
127                                 urlencode(k) .. "=" .. urlencode(v)
128                 end
129         end
130
131         return enc
132 end
133
134 -- (Internal function)
135 -- Initialize given parameter and coerce string into table when the parameter
136 -- already exists.
137 -- @param tbl   Table where parameter should be created
138 -- @param key   Parameter name
139 -- @return              Always nil
140 local function __initval( tbl, key )
141         if tbl[key] == nil then
142                 tbl[key] = ""
143         elseif type(tbl[key]) == "string" then
144                 tbl[key] = { tbl[key], "" }
145         else
146                 table.insert( tbl[key], "" )
147         end
148 end
149
150 -- (Internal function)
151 -- Append given data to given parameter, either by extending the string value
152 -- or by appending it to the last string in the parameter's value table.
153 -- @param tbl   Table containing the previously initialized parameter value
154 -- @param key   Parameter name
155 -- @param chunk String containing the data to append
156 -- @return              Always nil
157 -- @see                 __initval
158 local function __appendval( tbl, key, chunk )
159         if type(tbl[key]) == "table" then
160                 tbl[key][#tbl[key]] = tbl[key][#tbl[key]] .. chunk
161         else
162                 tbl[key] = tbl[key] .. chunk
163         end
164 end
165
166 -- (Internal function)
167 -- Finish the value of given parameter, either by transforming the string value
168 -- or - in the case of multi value parameters - the last element in the
169 -- associated values table.
170 -- @param tbl           Table containing the previously initialized parameter value
171 -- @param key           Parameter name
172 -- @param handler       Function which transforms the parameter value
173 -- @return                      Always nil
174 -- @see                         __initval
175 -- @see                         __appendval
176 local function __finishval( tbl, key, handler )
177         if handler then
178                 if type(tbl[key]) == "table" then
179                         tbl[key][#tbl[key]] = handler( tbl[key][#tbl[key]] )
180                 else
181                         tbl[key] = handler( tbl[key] )
182                 end
183         end
184 end
185
186
187 -- Table of our process states
188 local process_states = { }
189
190 -- Extract "magic", the first line of a http message.
191 -- Extracts the message type ("get", "post" or "response"), the requested uri
192 -- or the status code if the line descripes a http response.
193 process_states['magic'] = function( msg, chunk, err )
194
195         if chunk ~= nil then
196                 -- ignore empty lines before request
197                 if #chunk == 0 then
198                         return true, nil
199                 end
200
201                 -- Is it a request?
202                 local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
203
204                 -- Yup, it is
205                 if method then
206
207                         msg.type           = "request"
208                         msg.request_method = method:lower()
209                         msg.request_uri    = uri
210                         msg.http_version   = tonumber( http_ver )
211                         msg.headers        = { }
212
213                         -- We're done, next state is header parsing
214                         return true, function( chunk )
215                                 return process_states['headers']( msg, chunk )
216                         end
217
218                 -- Is it a response?
219                 else
220
221                         local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
222
223                         -- Is a response
224                         if code then
225
226                                 msg.type           = "response"
227                                 msg.status_code    = code
228                                 msg.status_message = message
229                                 msg.http_version   = tonumber( http_ver )
230                                 msg.headers        = { }
231
232                                 -- We're done, next state is header parsing
233                                 return true, function( chunk )
234                                         return process_states['headers']( msg, chunk )
235                                 end
236                         end
237                 end
238         end
239
240         -- Can't handle it
241         return nil, "Invalid HTTP message magic"
242 end
243
244
245 -- Extract headers from given string.
246 process_states['headers'] = function( msg, chunk )
247
248         if chunk ~= nil then
249
250                 -- Look for a valid header format
251                 local hdr, val = chunk:match( "^([A-Z][A-Za-z0-9%-_]+): +(.+)$" )
252
253                 if type(hdr) == "string" and hdr:len() > 0 and
254                    type(val) == "string" and val:len() > 0
255                 then
256                         msg.headers[hdr] = val
257
258                         -- Valid header line, proceed
259                         return true, nil
260
261                 elseif #chunk == 0 then
262                         -- Empty line, we won't accept data anymore
263                         return false, nil
264                 else
265                         -- Junk data
266                         return nil, "Invalid HTTP header received"
267                 end
268         else
269                 return nil, "Unexpected EOF"
270         end
271 end
272
273
274 --- Creates a ltn12 source from the given socket. The source will return it's
275 -- data line by line with the trailing \r\n stripped of.
276 -- @param sock  Readable network socket
277 -- @return              Ltn12 source function
278 function header_source( sock )
279         return ltn12.source.simplify( function()
280
281                 local chunk, err, part = sock:receive("*l")
282
283                 -- Line too long
284                 if chunk == nil then
285                         if err ~= "timeout" then
286                                 return nil, part
287                                         and "Line exceeds maximum allowed length"
288                                         or  "Unexpected EOF"
289                         else
290                                 return nil, err
291                         end
292
293                 -- Line ok
294                 elseif chunk ~= nil then
295
296                         -- Strip trailing CR
297                         chunk = chunk:gsub("\r$","")
298
299                         return chunk, nil
300                 end
301         end )
302 end
303
304 --- Decode a mime encoded http message body with multipart/form-data
305 -- Content-Type. Stores all extracted data associated with its parameter name
306 -- in the params table withing the given message object. Multiple parameter
307 -- values are stored as tables, ordinary ones as strings.
308 -- If an optional file callback function is given then it is feeded with the
309 -- file contents chunk by chunk and only the extracted file name is stored
310 -- within the params table. The callback function will be called subsequently
311 -- with three arguments:
312 --  o Table containing decoded (name, file) and raw (headers) mime header data
313 --  o String value containing a chunk of the file data
314 --  o Boolean which indicates wheather the current chunk is the last one (eof)
315 -- @param src           Ltn12 source function
316 -- @param msg           HTTP message object
317 -- @param filecb        File callback function (optional)
318 -- @return                      Value indicating successful operation (not nil means "ok")
319 -- @return                      String containing the error if unsuccessful
320 -- @see                         parse_message_header
321 function mimedecode_message_body( src, msg, filecb )
322
323         if msg and msg.env.CONTENT_TYPE then
324                 msg.mime_boundary = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)$")
325         end
326
327         if not msg.mime_boundary then
328                 return nil, "Invalid Content-Type found"
329         end
330
331
332         local tlen   = 0
333         local inhdr  = false
334         local field  = nil
335         local store  = nil
336         local lchunk = nil
337
338         local function parse_headers( chunk, field )
339
340                 local stat
341                 repeat
342                         chunk, stat = chunk:gsub(
343                                 "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n",
344                                 function(k,v)
345                                         field.headers[k] = v
346                                         return ""
347                                 end
348                         )
349                 until stat == 0
350
351                 chunk, stat = chunk:gsub("^\r\n","")
352
353                 -- End of headers
354                 if stat > 0 then
355                         if field.headers["Content-Disposition"] then
356                                 if field.headers["Content-Disposition"]:match("^form%-data; ") then
357                                         field.name = field.headers["Content-Disposition"]:match('name="(.-)"')
358                                         field.file = field.headers["Content-Disposition"]:match('filename="(.+)"$')
359                                 end
360                         end
361
362                         if not field.headers["Content-Type"] then
363                                 field.headers["Content-Type"] = "text/plain"
364                         end
365
366                         if field.name and field.file and filecb then
367                                 __initval( msg.params, field.name )
368                                 __appendval( msg.params, field.name, field.file )
369
370                                 store = filecb
371                         elseif field.name then
372                                 __initval( msg.params, field.name )
373
374                                 store = function( hdr, buf, eof )
375                                         __appendval( msg.params, field.name, buf )
376                                 end
377                         else
378                                 store = nil
379                         end
380
381                         return chunk, true
382                 end
383
384                 return chunk, false
385         end
386
387         local function snk( chunk )
388
389                 tlen = tlen + ( chunk and #chunk or 0 )
390
391                 if msg.env.CONTENT_LENGTH and tlen > tonumber(msg.env.CONTENT_LENGTH) + 2 then
392                         return nil, "Message body size exceeds Content-Length"
393                 end
394
395                 if chunk and not lchunk then
396                         lchunk = "\r\n" .. chunk
397
398                 elseif lchunk then
399                         local data = lchunk .. ( chunk or "" )
400                         local spos, epos, found
401
402                         repeat
403                                 spos, epos = data:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
404
405                                 if not spos then
406                                         spos, epos = data:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
407                                 end
408
409
410                                 if spos then
411                                         local predata = data:sub( 1, spos - 1 )
412
413                                         if inhdr then
414                                                 predata, eof = parse_headers( predata, field )
415
416                                                 if not eof then
417                                                         return nil, "Invalid MIME section header"
418                                                 elseif not field.name then
419                                                         return nil, "Invalid Content-Disposition header"
420                                                 end
421                                         end
422
423                                         if store then
424                                                 store( field, predata, true )
425                                         end
426
427
428                                         field = { headers = { } }
429                                         found = found or true
430
431                                         data, eof = parse_headers( data:sub( epos + 1, #data ), field )
432                                         inhdr = not eof
433                                 end
434                         until not spos
435
436                         if found then
437                                 if #data > 78 then
438                                         lchunk = data:sub( #data - 78 + 1, #data )
439                                         data   = data:sub( 1, #data - 78 )
440
441                                         if store then
442                                                 store( field, data, false )
443                                         else
444                                                 return nil, "Invalid MIME section header"
445                                         end
446                                 else
447                                         lchunk, data = data, nil
448                                 end
449                         else
450                                 if inhdr then
451                                         lchunk, eof = parse_headers( data, field )
452                                         inhdr = not eof
453                                 else
454                                         store( field, lchunk, false )
455                                         lchunk, chunk = chunk, nil
456                                 end
457                         end
458                 end
459
460                 return true
461         end
462
463         return ltn12.pump.all( src, snk )
464 end
465
466 --- Decode an urlencoded http message body with application/x-www-urlencoded
467 -- Content-Type. Stores all extracted data associated with its parameter name
468 -- in the params table withing the given message object. Multiple parameter
469 -- values are stored as tables, ordinary ones as strings.
470 -- @param src   Ltn12 source function
471 -- @param msg   HTTP message object
472 -- @return              Value indicating successful operation (not nil means "ok")
473 -- @return              String containing the error if unsuccessful
474 -- @see                 parse_message_header
475 function urldecode_message_body( src, msg )
476
477         local tlen   = 0
478         local lchunk = nil
479
480         local function snk( chunk )
481
482                 tlen = tlen + ( chunk and #chunk or 0 )
483
484                 if msg.env.CONTENT_LENGTH and tlen > tonumber(msg.env.CONTENT_LENGTH) + 2 then
485                         return nil, "Message body size exceeds Content-Length"
486                 elseif tlen > HTTP_MAX_CONTENT then
487                         return nil, "Message body size exceeds maximum allowed length"
488                 end
489
490                 if not lchunk and chunk then
491                         lchunk = chunk
492
493                 elseif lchunk then
494                         local data = lchunk .. ( chunk or "&" )
495                         local spos, epos
496
497                         repeat
498                                 spos, epos = data:find("^.-[;&]")
499
500                                 if spos then
501                                         local pair = data:sub( spos, epos - 1 )
502                                         local key  = pair:match("^(.-)=")
503                                         local val  = pair:match("=([^%s]*)%s*$")
504
505                                         if key and #key > 0 then
506                                                 __initval( msg.params, key )
507                                                 __appendval( msg.params, key, val )
508                                                 __finishval( msg.params, key, urldecode )
509                                         end
510
511                                         data = data:sub( epos + 1, #data )
512                                 end
513                         until not spos
514
515                         lchunk = data
516                 end
517
518                 return true
519         end
520
521         return ltn12.pump.all( src, snk )
522 end
523
524 --- Try to extract an http message header including information like protocol
525 -- version, message headers and resulting CGI environment variables from the
526 -- given ltn12 source.
527 -- @param src   Ltn12 source function
528 -- @return              HTTP message object
529 -- @see                 parse_message_body
530 function parse_message_header( src )
531
532         local ok   = true
533         local msg  = { }
534
535         local sink = ltn12.sink.simplify(
536                 function( chunk )
537                         return process_states['magic']( msg, chunk )
538                 end
539         )
540
541         -- Pump input data...
542         while ok do
543
544                 -- get data
545                 ok, err = ltn12.pump.step( src, sink )
546
547                 -- error
548                 if not ok and err then
549                         return nil, err
550
551                 -- eof
552                 elseif not ok then
553
554                         -- Process get parameters
555                         if ( msg.request_method == "get" or msg.request_method == "post" ) and
556                            msg.request_uri:match("?")
557                         then
558                                 msg.params = urldecode_params( msg.request_uri )
559                         else
560                                 msg.params = { }
561                         end
562
563                         -- Populate common environment variables
564                         msg.env = {
565                                 CONTENT_LENGTH    = msg.headers['Content-Length'];
566                                 CONTENT_TYPE      = msg.headers['Content-Type'];
567                                 REQUEST_METHOD    = msg.request_method:upper();
568                                 REQUEST_URI       = msg.request_uri;
569                                 SCRIPT_NAME       = msg.request_uri:gsub("?.+$","");
570                                 SCRIPT_FILENAME   = "";         -- XXX implement me
571                                 SERVER_PROTOCOL   = "HTTP/" .. string.format("%.1f", msg.http_version)
572                         }
573
574                         -- Populate HTTP_* environment variables
575                         for i, hdr in ipairs( {
576                                 'Accept',
577                                 'Accept-Charset',
578                                 'Accept-Encoding',
579                                 'Accept-Language',
580                                 'Connection',
581                                 'Cookie',
582                                 'Host',
583                                 'Referer',
584                                 'User-Agent',
585                         } ) do
586                                 local var = 'HTTP_' .. hdr:upper():gsub("%-","_")
587                                 local val = msg.headers[hdr]
588
589                                 msg.env[var] = val
590                         end
591                 end
592         end
593
594         return msg
595 end
596
597 --- Try to extract and decode a http message body from the given ltn12 source.
598 -- This function will examine the Content-Type within the given message object
599 -- to select the appropriate content decoder.
600 -- Currently the application/x-www-urlencoded and application/form-data
601 -- mime types are supported. If the encountered content encoding can't be
602 -- handled then the whole message body will be stored unaltered as "content"
603 -- property within the given message object.
604 -- @param src           Ltn12 source function
605 -- @param msg           HTTP message object
606 -- @param filecb        File data callback (optional, see mimedecode_message_body())
607 -- @return                      Value indicating successful operation (not nil means "ok")
608 -- @return                      String containing the error if unsuccessful
609 -- @see                         parse_message_header
610 function parse_message_body( src, msg, filecb )
611         -- Is it multipart/mime ?
612         if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
613            msg.env.CONTENT_TYPE:match("^multipart/form%-data")
614         then
615
616                 return mimedecode_message_body( src, msg, filecb )
617
618         -- Is it application/x-www-form-urlencoded ?
619         elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
620                msg.env.CONTENT_TYPE == "application/x-www-form-urlencoded"
621         then
622                 return urldecode_message_body( src, msg, filecb )
623
624
625         -- Unhandled encoding
626         -- If a file callback is given then feed it chunk by chunk, else
627         -- store whole buffer in message.content
628         else
629
630                 local sink
631
632                 -- If we have a file callback then feed it
633                 if type(filecb) == "function" then
634                         sink = filecb
635
636                 -- ... else append to .content
637                 else
638                         msg.content = ""
639                         msg.content_length = 0
640
641                         sink = function( chunk, err )
642                                 if chunk then
643                                         if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
644                                                 msg.content        = msg.content        .. chunk
645                                                 msg.content_length = msg.content_length + #chunk
646                                                 return true
647                                         else
648                                                 return nil, "POST data exceeds maximum allowed length"
649                                         end
650                                 end
651                                 return true
652                         end
653                 end
654
655                 -- Pump data...
656                 while true do
657                         local ok, err = ltn12.pump.step( src, sink )
658
659                         if not ok and err then
660                                 return nil, err
661                         elseif not err then
662                                 return true
663                         end
664                 end
665
666                 return true
667         end
668 end
669
670 --- Table containing human readable messages for several http status codes.
671 -- @class table
672 statusmsg = {
673         [200] = "OK",
674         [301] = "Moved Permanently",
675         [302] = "Found",
676         [304] = "Not Modified",
677         [400] = "Bad Request",
678         [403] = "Forbidden",
679         [404] = "Not Found",
680         [405] = "Method Not Allowed",
681         [411] = "Length Required",
682         [412] = "Precondition Failed",
683         [500] = "Internal Server Error",
684         [503] = "Server Unavailable",
685 }