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