3 HTTP protocol implementation for LuCI
4 (c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
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
10 http://www.apache.org/licenses/LICENSE-2.0
16 module("luci.http.protocol", package.seeall)
19 require("luci.http.protocol.filter")
21 HTTP_MAX_CONTENT = 1024*4 -- 4 kB maximum content size
22 HTTP_URLENC_MAXKEYLEN = 1024 -- maximum allowd size of urlencoded parameter names
25 -- Decode an urlencoded string.
26 -- Returns the decoded value.
27 function urldecode( str )
29 local function __chrdec( hex )
30 return string.char( tonumber( hex, 16 ) )
33 if type(str) == "string" then
34 str = str:gsub( "+", " " ):gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec )
41 -- Extract and split urlencoded data pairs, separated bei either "&" or ";" from given url.
42 -- Returns a table value with urldecoded values.
43 function urldecode_params( url, tbl )
45 local params = tbl or { }
48 url = url:gsub( "^.+%?([^?]+)", "%1" )
51 for pair in url:gmatch( "[^&;]+" ) do
54 local key = urldecode( pair:match("^([^=]+)") )
55 local val = urldecode( pair:match("^[^=]+=(.+)$") )
58 if type(key) == "string" and key:len() > 0 then
59 if type(val) ~= "string" then val = "" end
61 if not params[key] then
63 elseif type(params[key]) ~= "table" then
64 params[key] = { params[key], val }
66 table.insert( params[key], val )
75 -- Encode given string in urlencoded format.
76 -- Returns the encoded string.
77 function urlencode( str )
79 local function __chrenc( chr )
81 "%%%02x", string.byte( chr )
85 if type(str) == "string" then
87 "([^a-zA-Z0-9$_%-%.+!*'(),])",
96 -- Encode given table to urlencoded string.
97 -- Returns the encoded string.
98 function urlencode_params( tbl )
101 for k, v in pairs(tbl) do
102 enc = enc .. ( enc and "&" or "" ) ..
103 urlencode(k) .. "=" ..
111 -- Table of our process states
112 local process_states = { }
114 -- Extract "magic", the first line of a http message.
115 -- Extracts the message type ("get", "post" or "response"), the requested uri
116 -- or the status code if the line descripes a http response.
117 process_states['magic'] = function( msg, chunk )
122 local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
128 msg.request_method = method:lower()
129 msg.request_uri = uri
130 msg.http_version = tonumber( http_ver )
133 -- We're done, next state is header parsing
134 return true, function( chunk )
135 return process_states['headers']( msg, chunk )
141 local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
146 msg.type = "response"
147 msg.status_code = code
148 msg.status_message = message
149 msg.http_version = tonumber( http_ver )
152 -- We're done, next state is header parsing
153 return true, function( chunk )
154 return process_states['headers']( msg, chunk )
161 return nil, "Invalid HTTP message magic"
165 -- Extract headers from given string.
166 process_states['headers'] = function( msg, chunk )
170 -- Look for a valid header format
171 local hdr, val = chunk:match( "^([A-Z][A-Za-z0-9%-_]+): +(.+)$" )
173 if type(hdr) == "string" and hdr:len() > 0 and
174 type(val) == "string" and val:len() > 0
176 msg.headers[hdr] = val
178 -- Valid header line, proceed
181 elseif #chunk == 0 then
182 -- Empty line, we won't accept data anymore
186 return nil, "Invalid HTTP header received"
189 return nil, "Unexpected EOF"
194 -- Find first MIME boundary
195 process_states['mime-init'] = function( msg, chunk, filecb )
198 if #chunk >= #msg.mime_boundary + 2 then
199 local boundary = chunk:sub( 1, #msg.mime_boundary + 4 )
201 if boundary == "--" .. msg.mime_boundary .. "\r\n" then
203 -- Store remaining data in buffer
204 msg._mimebuffer = chunk:sub( #msg.mime_boundary + 5, #chunk )
206 -- Switch to header processing state
207 return true, function( chunk )
208 return process_states['mime-headers']( msg, chunk, filecb )
211 return nil, "Invalid MIME boundary"
217 return nil, "Unexpected EOF"
222 -- Read MIME part headers
223 process_states['mime-headers'] = function( msg, chunk, filecb )
227 -- Combine look-behind buffer with current chunk
228 chunk = msg._mimebuffer .. chunk
230 if not msg._mimeheaders then
231 msg._mimeheaders = { }
234 local function __storehdr( k, v )
235 msg._mimeheaders[k] = v
239 -- Read all header lines
240 local ok, count = 1, 0
242 chunk, ok = chunk:gsub( "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n", __storehdr )
246 -- Headers processed, check for empty line
247 chunk, ok = chunk:gsub( "^\r\n", "" )
249 -- Store remaining buffer contents
250 msg._mimebuffer = chunk
255 -- When no Content-Type header is given assume text/plain
256 if not msg._mimeheaders['Content-Type'] then
257 msg._mimeheaders['Content-Type'] = 'text/plain'
260 -- Check Content-Disposition
261 if msg._mimeheaders['Content-Disposition'] then
262 -- Check for "form-data" token
263 if msg._mimeheaders['Content-Disposition']:match("^form%-data; ") then
264 -- Check for field name, filename
265 local field = msg._mimeheaders['Content-Disposition']:match('name="(.-)"')
266 local file = msg._mimeheaders['Content-Disposition']:match('filename="(.+)"$')
268 -- Is a file field and we have a callback
269 if file and filecb then
270 msg.params[field] = file
271 msg._mimecallback = function(chunk,eof)
275 headers = msg._mimeheaders
279 -- Treat as form field
281 msg.params[field] = ""
282 msg._mimecallback = function(chunk,eof)
283 msg.params[field] = msg.params[field] .. chunk
287 -- Header was valid, continue with mime-data
288 return true, function( chunk )
289 return process_states['mime-data']( msg, chunk, filecb )
292 -- Unknown Content-Disposition, abort
293 return nil, "Unexpected Content-Disposition MIME section header"
296 -- Content-Disposition is required, abort without
297 return nil, "Missing Content-Disposition MIME section header"
300 -- We parsed no headers yet and buffer is almost empty
301 elseif count > 0 or #chunk < 128 then
302 -- Keep feeding me with chunks
306 -- Buffer looks like garbage
307 return nil, "Malformed MIME section header"
309 return nil, "Unexpected EOF"
314 -- Read MIME part data
315 process_states['mime-data'] = function( msg, chunk, filecb )
319 -- Combine look-behind buffer with current chunk
320 local buffer = msg._mimebuffer .. chunk
322 -- Look for MIME boundary
323 local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
327 msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
330 msg._mimebuffer = buffer:sub( epos + 1, #buffer )
332 -- Next state is mime-header processing
333 return true, function( chunk )
334 return process_states['mime-headers']( msg, chunk, filecb )
338 local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
342 msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
344 -- We processed the final MIME boundary, cleanup
345 msg._mimebuffer = nil
346 msg._mimeheaders = nil
347 msg._mimecallback = nil
349 -- We won't accept data anymore
352 -- We're somewhere within a data section and our buffer is full
353 if #buffer > #chunk then
354 -- Flush buffered data
355 msg._mimecallback( buffer:sub( 1, #buffer - #chunk ), false )
358 msg._mimebuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
360 -- Buffer is not full yet, append new data
362 msg._mimebuffer = buffer
370 return nil, "Unexpected EOF"
375 -- Init urldecoding stream
376 process_states['urldecode-init'] = function( msg, chunk, filecb )
380 -- Check for Content-Length
381 if msg.env.CONTENT_LENGTH then
382 msg.content_length = tonumber(msg.env.CONTENT_LENGTH)
384 if msg.content_length <= HTTP_MAX_CONTENT then
386 msg._urldecbuffer = chunk
387 msg._urldeclength = 0
389 -- Switch to urldecode-key state
390 return true, function(chunk)
391 return process_states['urldecode-key']( msg, chunk, filecb )
394 return nil, "Request exceeds maximum allowed size"
397 return nil, "Missing Content-Length header"
400 return nil, "Unexpected EOF"
405 -- Process urldecoding stream, read and validate parameter key
406 process_states['urldecode-key'] = function( msg, chunk, filecb )
409 -- Prevent oversized requests
410 if msg._urldeclength >= msg.content_length then
411 return nil, "Request exceeds maximum allowed size"
414 -- Combine look-behind buffer with current chunk
415 local buffer = msg._urldecbuffer .. chunk
416 local spos, epos = buffer:find("=")
421 -- Check that key doesn't exceed maximum allowed key length
422 if ( spos - 1 ) <= HTTP_URLENC_MAXKEYLEN then
423 local key = urldecode( buffer:sub( 1, spos - 1 ) )
427 msg._urldeclength = msg._urldeclength + epos
428 msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
430 -- Use file callback or store values inside msg.params
432 msg._urldeccallback = function( chunk, eof )
433 filecb( field, chunk, eof )
436 msg._urldeccallback = function( chunk, eof )
437 msg.params[key] = msg.params[key] .. chunk
439 -- FIXME: Use a filter
441 msg.params[key] = urldecode( msg.params[key] )
446 -- Proceed with urldecode-value state
447 return true, function( chunk )
448 return process_states['urldecode-value']( msg, chunk, filecb )
451 return nil, "POST parameter exceeds maximum allowed length"
454 return nil, "POST data exceeds maximum allowed length"
457 return nil, "Unexpected EOF"
462 -- Process urldecoding stream, read parameter value
463 process_states['urldecode-value'] = function( msg, chunk, filecb )
467 -- Combine look-behind buffer with current chunk
468 local buffer = msg._urldecbuffer .. chunk
472 -- Compare processed length
473 if msg._urldeclength == msg.content_length then
475 msg._urldeclength = nil
476 msg._urldecbuffer = nil
477 msg._urldeccallback = nil
479 -- We won't accept data anymore
482 return nil, "Content-Length mismatch"
486 -- Check for end of value
487 local spos, epos = buffer:find("[&;]")
490 -- Flush buffer, send eof
491 msg._urldeccallback( buffer:sub( 1, spos - 1 ), true )
492 msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
493 msg._urldeclength = msg._urldeclength + epos
495 -- Back to urldecode-key state
496 return true, function( chunk )
497 return process_states['urldecode-key']( msg, chunk, filecb )
500 -- We're somewhere within a data section and our buffer is full
501 if #buffer > #chunk then
502 -- Flush buffered data
503 -- Send EOF if chunk is empty
504 msg._urldeccallback( buffer:sub( 1, #buffer - #chunk ), ( #chunk == 0 ) )
507 msg._urldeclength = msg._urldeclength + #buffer - #chunk
508 msg._urldecbuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
510 -- Buffer is not full yet, append new data
512 msg._urldecbuffer = buffer
519 return nil, "Unexpected EOF"
524 -- Decode MIME encoded data.
525 function mimedecode_message_body( source, msg, filecb )
527 -- Find mime boundary
528 if msg and msg.env.CONTENT_TYPE then
530 local bound = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)")
533 msg.mime_boundary = bound
535 return nil, "No MIME boundary found or invalid content type given"
539 -- Create an initial LTN12 sink
540 -- The whole MIME parsing process is implemented as fancy sink, sinks replace themself
541 -- depending on current processing state (init, header, data). Return the initial state.
542 local sink = ltn12.sink.simplify(
544 return process_states['mime-init']( msg, chunk, filecb )
548 -- Create a throttling LTN12 source
549 -- Frequent state switching in the mime parsing process leads to unwanted buffer aggregation.
550 -- This source checks wheather there's still data in our internal read buffer and returns an
551 -- empty string if there's already enough data in the processing queue. If the internal buffer
552 -- runs empty we're calling the original source to get the next chunk of data.
553 local tsrc = function()
555 -- XXX: we schould propably keep the maximum buffer size in sync with
556 -- the blocksize of our original source... but doesn't really matter
557 if msg._mimebuffer ~= null and #msg._mimebuffer > 256 then
564 -- Pump input data...
567 local ok, err = ltn12.pump.step( tsrc, sink )
570 if not ok and err then
581 -- Decode urlencoded data.
582 function urldecode_message_body( source, msg )
584 -- Create an initial LTN12 sink
585 -- Return the initial state.
586 local sink = ltn12.sink.simplify(
588 return process_states['urldecode-init']( msg, chunk )
592 -- Create a throttling LTN12 source
593 -- See explaination in mimedecode_message_body().
594 local tsrc = function()
595 if msg._urldecbuffer ~= null and #msg._urldecbuffer > 0 then
602 -- Pump input data...
605 local ok, err = ltn12.pump.step( tsrc, sink )
608 if not ok and err then
619 -- Parse a http message
620 function parse_message( data, filecb )
622 local reader = _linereader( data, HTTP_MAX_READBUF )
623 local message = parse_message_header( reader )
626 parse_message_body( reader, message, filecb )
633 -- Parse a http message header
634 function parse_message_header( source )
639 local sink = ltn12.sink.simplify(
641 return process_states['magic']( msg, chunk )
645 -- Pump input data...
649 ok, err = ltn12.pump.step( source, sink )
652 if not ok and err then
658 -- Process get parameters
659 if ( msg.request_method == "get" or msg.request_method == "post" ) and
660 msg.request_uri:match("?")
662 msg.params = urldecode_params( msg.request_uri )
667 -- Populate common environment variables
669 CONTENT_LENGTH = msg.headers['Content-Length'];
670 CONTENT_TYPE = msg.headers['Content-Type'];
671 REQUEST_METHOD = msg.request_method:upper();
672 REQUEST_URI = msg.request_uri;
673 SCRIPT_NAME = msg.request_uri:gsub("?.+$","");
674 SCRIPT_FILENAME = ""; -- XXX implement me
675 SERVER_PROTOCOL = "HTTP/" .. msg.http_version
678 -- Populate HTTP_* environment variables
679 for i, hdr in ipairs( {
690 local var = 'HTTP_' .. hdr:upper():gsub("%-","_")
691 local val = msg.headers[hdr]
702 -- Parse a http message body
703 function parse_message_body( source, msg, filecb )
705 -- Install an additional filter if we're operating on chunked transfer
706 -- coding and client is HTTP/1.1 capable
707 if msg.http_version == 1.1 and
708 msg.headers['Transfer-Encoding'] and
709 msg.headers['Transfer-Encoding']:find("chunked")
711 source = ltn12.source.chain(
712 source, luci.http.protocol.filter.decode_chunked
717 -- Is it multipart/mime ?
718 if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
719 msg.env.CONTENT_TYPE:match("^multipart/form%-data")
722 return mimedecode_message_body( source, msg, filecb )
724 -- Is it application/x-www-form-urlencoded ?
725 elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
726 msg.env.CONTENT_TYPE == "application/x-www-form-urlencoded"
728 return urldecode_message_body( source, msg, filecb )
731 -- Unhandled encoding
732 -- If a file callback is given then feed it line by line, else
733 -- store whole buffer in message.content
738 -- If we have a file callback then feed it
739 if type(filecb) == "function" then
742 -- ... else append to .content
745 msg.content_length = 0
747 sink = function( chunk )
748 if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
750 msg.content = msg.content .. chunk
751 msg.content_length = msg.content_length + #chunk
755 return nil, "POST data exceeds maximum allowed length"
762 local ok, err = ltn12.pump.step( source, sink )
764 if not ok and err then
774 -- Push a response to a socket
775 function push_response(request, response, sourceout, sinkout, sinkerr)
776 local code = response.status
777 sinkout(request.env.SERVER_PROTOCOL .. " " .. code .. " " .. statusmsg[code] .. "\r\n")
779 -- FIXME: Add support for keep-alive
780 response.headers["Connection"] = "close"
782 for k,v in pairs(response.headers) do
783 sinkout(k .. ": " .. v .. "\r\n")
789 ltn12.pump.all(sourceout, sinkout)
797 [400] = "Bad Request",
800 [500] = "Internal Server Error",
801 [503] = "Server Unavailable",