* luci/libs/http: added inline documentation to luci.http.protocol & friends, fixed...
[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 the mime headers of the corresponding section
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.headers, 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.headers, 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.headers, 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("=(.*)$")
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 )
642 if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
643
644 msg.content = msg.content .. chunk
645 msg.content_length = msg.content_length + #chunk
646
647 return true
648 else
649 return nil, "POST data exceeds maximum allowed length"
650 end
651 end
652 end
653
654 -- Pump data...
655 while true do
656 local ok, err = ltn12.pump.step( src, sink )
657
658 if not ok and err then
659 return nil, err
660 elseif not err then
661 return true
662 end
663 end
664
665 return true
666 end
667 end
668
669 --- Table containing human readable messages for several http status codes.
670 -- @class table
671 statusmsg = {
672 [200] = "OK",
673 [301] = "Moved Permanently",
674 [304] = "Not Modified",
675 [400] = "Bad Request",
676 [403] = "Forbidden",
677 [404] = "Not Found",
678 [405] = "Method Not Allowed",
679 [411] = "Length Required",
680 [412] = "Precondition Failed",
681 [500] = "Internal Server Error",
682 [503] = "Server Unavailable",
683 }