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, err )
120 -- ignore empty lines before request
126 local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
132 msg.request_method = method:lower()
133 msg.request_uri = uri
134 msg.http_version = tonumber( http_ver )
137 -- We're done, next state is header parsing
138 return true, function( chunk )
139 return process_states['headers']( msg, chunk )
145 local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
150 msg.type = "response"
151 msg.status_code = code
152 msg.status_message = message
153 msg.http_version = tonumber( http_ver )
156 -- We're done, next state is header parsing
157 return true, function( chunk )
158 return process_states['headers']( msg, chunk )
165 return nil, "Invalid HTTP message magic"
169 -- Extract headers from given string.
170 process_states['headers'] = function( msg, chunk )
174 -- Look for a valid header format
175 local hdr, val = chunk:match( "^([A-Z][A-Za-z0-9%-_]+): +(.+)$" )
177 if type(hdr) == "string" and hdr:len() > 0 and
178 type(val) == "string" and val:len() > 0
180 msg.headers[hdr] = val
182 -- Valid header line, proceed
185 elseif #chunk == 0 then
186 -- Empty line, we won't accept data anymore
190 return nil, "Invalid HTTP header received"
193 return nil, "Unexpected EOF"
198 -- Find first MIME boundary
199 process_states['mime-init'] = function( msg, chunk, filecb )
202 if #chunk >= #msg.mime_boundary + 2 then
203 local boundary = chunk:sub( 1, #msg.mime_boundary + 4 )
205 if boundary == "--" .. msg.mime_boundary .. "\r\n" then
207 -- Store remaining data in buffer
208 msg._mimebuffer = chunk:sub( #msg.mime_boundary + 5, #chunk )
210 -- Switch to header processing state
211 return true, function( chunk )
212 return process_states['mime-headers']( msg, chunk, filecb )
215 return nil, "Invalid MIME boundary"
221 return nil, "Unexpected EOF"
226 -- Read MIME part headers
227 process_states['mime-headers'] = function( msg, chunk, filecb )
231 -- Combine look-behind buffer with current chunk
232 chunk = msg._mimebuffer .. chunk
234 if not msg._mimeheaders then
235 msg._mimeheaders = { }
238 local function __storehdr( k, v )
239 msg._mimeheaders[k] = v
243 -- Read all header lines
244 local ok, count = 1, 0
246 chunk, ok = chunk:gsub( "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n", __storehdr )
250 -- Headers processed, check for empty line
251 chunk, ok = chunk:gsub( "^\r\n", "" )
253 -- Store remaining buffer contents
254 msg._mimebuffer = chunk
259 -- When no Content-Type header is given assume text/plain
260 if not msg._mimeheaders['Content-Type'] then
261 msg._mimeheaders['Content-Type'] = 'text/plain'
264 -- Check Content-Disposition
265 if msg._mimeheaders['Content-Disposition'] then
266 -- Check for "form-data" token
267 if msg._mimeheaders['Content-Disposition']:match("^form%-data; ") then
268 -- Check for field name, filename
269 local field = msg._mimeheaders['Content-Disposition']:match('name="(.-)"')
270 local file = msg._mimeheaders['Content-Disposition']:match('filename="(.+)"$')
272 -- Is a file field and we have a callback
273 if file and filecb then
274 msg.params[field] = file
275 msg._mimecallback = function(chunk,eof)
279 headers = msg._mimeheaders
283 -- Treat as form field
285 msg.params[field] = ""
286 msg._mimecallback = function(chunk,eof)
287 msg.params[field] = msg.params[field] .. chunk
291 -- Header was valid, continue with mime-data
292 return true, function( chunk )
293 return process_states['mime-data']( msg, chunk, filecb )
296 -- Unknown Content-Disposition, abort
297 return nil, "Unexpected Content-Disposition MIME section header"
300 -- Content-Disposition is required, abort without
301 return nil, "Missing Content-Disposition MIME section header"
304 -- We parsed no headers yet and buffer is almost empty
305 elseif count > 0 or #chunk < 128 then
306 -- Keep feeding me with chunks
310 -- Buffer looks like garbage
311 return nil, "Malformed MIME section header"
313 return nil, "Unexpected EOF"
318 -- Read MIME part data
319 process_states['mime-data'] = function( msg, chunk, filecb )
323 -- Combine look-behind buffer with current chunk
324 local buffer = msg._mimebuffer .. chunk
326 -- Look for MIME boundary
327 local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
331 msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
334 msg._mimebuffer = buffer:sub( epos + 1, #buffer )
336 -- Next state is mime-header processing
337 return true, function( chunk )
338 return process_states['mime-headers']( msg, chunk, filecb )
342 local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
346 msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
348 -- We processed the final MIME boundary, cleanup
349 msg._mimebuffer = nil
350 msg._mimeheaders = nil
351 msg._mimecallback = nil
353 -- We won't accept data anymore
356 -- We're somewhere within a data section and our buffer is full
357 if #buffer > #chunk then
358 -- Flush buffered data
359 msg._mimecallback( buffer:sub( 1, #buffer - #chunk ), false )
362 msg._mimebuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
364 -- Buffer is not full yet, append new data
366 msg._mimebuffer = buffer
374 return nil, "Unexpected EOF"
379 -- Init urldecoding stream
380 process_states['urldecode-init'] = function( msg, chunk, filecb )
384 -- Check for Content-Length
385 if msg.env.CONTENT_LENGTH then
386 msg.content_length = tonumber(msg.env.CONTENT_LENGTH)
388 if msg.content_length <= HTTP_MAX_CONTENT then
390 msg._urldecbuffer = chunk
391 msg._urldeclength = 0
393 -- Switch to urldecode-key state
394 return true, function(chunk)
395 return process_states['urldecode-key']( msg, chunk, filecb )
398 return nil, "Request exceeds maximum allowed size"
401 return nil, "Missing Content-Length header"
404 return nil, "Unexpected EOF"
409 -- Process urldecoding stream, read and validate parameter key
410 process_states['urldecode-key'] = function( msg, chunk, filecb )
413 -- Prevent oversized requests
414 if msg._urldeclength >= msg.content_length then
415 return nil, "Request exceeds maximum allowed size"
418 -- Combine look-behind buffer with current chunk
419 local buffer = msg._urldecbuffer .. chunk
420 local spos, epos = buffer:find("=")
425 -- Check that key doesn't exceed maximum allowed key length
426 if ( spos - 1 ) <= HTTP_URLENC_MAXKEYLEN then
427 local key = urldecode( buffer:sub( 1, spos - 1 ) )
431 msg._urldeclength = msg._urldeclength + epos
432 msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
434 -- Use file callback or store values inside msg.params
436 msg._urldeccallback = function( chunk, eof )
437 filecb( field, chunk, eof )
440 msg._urldeccallback = function( chunk, eof )
441 msg.params[key] = msg.params[key] .. chunk
443 -- FIXME: Use a filter
445 msg.params[key] = urldecode( msg.params[key] )
450 -- Proceed with urldecode-value state
451 return true, function( chunk )
452 return process_states['urldecode-value']( msg, chunk, filecb )
455 return nil, "POST parameter exceeds maximum allowed length"
458 return nil, "POST data exceeds maximum allowed length"
461 return nil, "Unexpected EOF"
466 -- Process urldecoding stream, read parameter value
467 process_states['urldecode-value'] = function( msg, chunk, filecb )
471 -- Combine look-behind buffer with current chunk
472 local buffer = msg._urldecbuffer .. chunk
476 -- Compare processed length
477 if msg._urldeclength == msg.content_length then
479 msg._urldeclength = nil
480 msg._urldecbuffer = nil
481 msg._urldeccallback = nil
483 -- We won't accept data anymore
486 return nil, "Content-Length mismatch"
490 -- Check for end of value
491 local spos, epos = buffer:find("[&;]")
494 -- Flush buffer, send eof
495 msg._urldeccallback( buffer:sub( 1, spos - 1 ), true )
496 msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
497 msg._urldeclength = msg._urldeclength + epos
499 -- Back to urldecode-key state
500 return true, function( chunk )
501 return process_states['urldecode-key']( msg, chunk, filecb )
504 -- We're somewhere within a data section and our buffer is full
505 if #buffer > #chunk then
506 -- Flush buffered data
507 msg._urldeccallback( buffer:sub( 1, #buffer - #chunk ), false )
510 msg._urldeclength = msg._urldeclength + #buffer - #chunk
511 msg._urldecbuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
513 -- Buffer is not full yet, append new data
515 msg._urldecbuffer = buffer
523 msg._urldeccallback( "", true )
529 -- Creates a header source from a given socket
530 function header_source( sock )
531 return ltn12.source.simplify( function()
533 local chunk, err, part = sock:receive("*l")
537 if err ~= "timeout" then
539 and "Line exceeds maximum allowed length["..part.."]"
546 elseif chunk ~= nil then
549 chunk = chunk:gsub("\r$","")
557 -- Decode MIME encoded data.
558 function mimedecode_message_body( source, msg, filecb )
560 -- Find mime boundary
561 if msg and msg.env.CONTENT_TYPE then
563 local bound = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)")
566 msg.mime_boundary = bound
568 return nil, "No MIME boundary found or invalid content type given"
572 -- Create an initial LTN12 sink
573 -- The whole MIME parsing process is implemented as fancy sink, sinks replace themself
574 -- depending on current processing state (init, header, data). Return the initial state.
575 local sink = ltn12.sink.simplify(
577 return process_states['mime-init']( msg, chunk, filecb )
581 -- Create a throttling LTN12 source
582 -- Frequent state switching in the mime parsing process leads to unwanted buffer aggregation.
583 -- This source checks wheather there's still data in our internal read buffer and returns an
584 -- empty string if there's already enough data in the processing queue. If the internal buffer
585 -- runs empty we're calling the original source to get the next chunk of data.
586 local tsrc = function()
588 -- XXX: we schould propably keep the maximum buffer size in sync with
589 -- the blocksize of our original source... but doesn't really matter
590 if msg._mimebuffer ~= null and #msg._mimebuffer > 256 then
597 -- Pump input data...
600 local ok, err = ltn12.pump.step( tsrc, sink )
603 if not ok and err then
614 -- Decode urlencoded data.
615 function urldecode_message_body( source, msg )
617 -- Create an initial LTN12 sink
618 -- Return the initial state.
619 local sink = ltn12.sink.simplify(
621 return process_states['urldecode-init']( msg, chunk )
625 -- Create a throttling LTN12 source
626 -- See explaination in mimedecode_message_body().
627 local tsrc = function()
628 if msg._urldecbuffer ~= null and #msg._urldecbuffer > 0 then
635 -- Pump input data...
638 local ok, err = ltn12.pump.step( tsrc, sink )
641 if not ok and err then
652 -- Parse a http message header
653 function parse_message_header( source )
658 local sink = ltn12.sink.simplify(
660 return process_states['magic']( msg, chunk )
664 -- Pump input data...
668 ok, err = ltn12.pump.step( source, sink )
671 if not ok and err then
677 -- Process get parameters
678 if ( msg.request_method == "get" or msg.request_method == "post" ) and
679 msg.request_uri:match("?")
681 msg.params = urldecode_params( msg.request_uri )
686 -- Populate common environment variables
688 CONTENT_LENGTH = msg.headers['Content-Length'];
689 CONTENT_TYPE = msg.headers['Content-Type'];
690 REQUEST_METHOD = msg.request_method:upper();
691 REQUEST_URI = msg.request_uri;
692 SCRIPT_NAME = msg.request_uri:gsub("?.+$","");
693 SCRIPT_FILENAME = ""; -- XXX implement me
694 SERVER_PROTOCOL = "HTTP/" .. string.format("%.1f", msg.http_version)
697 -- Populate HTTP_* environment variables
698 for i, hdr in ipairs( {
709 local var = 'HTTP_' .. hdr:upper():gsub("%-","_")
710 local val = msg.headers[hdr]
721 -- Parse a http message body
722 function parse_message_body( source, msg, filecb )
723 -- Is it multipart/mime ?
724 if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
725 msg.env.CONTENT_TYPE:match("^multipart/form%-data")
728 return mimedecode_message_body( source, msg, filecb )
730 -- Is it application/x-www-form-urlencoded ?
731 elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
732 msg.env.CONTENT_TYPE == "application/x-www-form-urlencoded"
734 return urldecode_message_body( source, msg, filecb )
737 -- Unhandled encoding
738 -- If a file callback is given then feed it chunk by chunk, else
739 -- store whole buffer in message.content
744 -- If we have a file callback then feed it
745 if type(filecb) == "function" then
748 -- ... else append to .content
751 msg.content_length = 0
753 sink = function( chunk )
754 if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
756 msg.content = msg.content .. chunk
757 msg.content_length = msg.content_length + #chunk
761 return nil, "POST data exceeds maximum allowed length"
768 local ok, err = ltn12.pump.step( source, sink )
770 if not ok and err then
782 [304] = "Not Modified",
783 [400] = "Bad Request",
786 [405] = "Method Not Allowed",
787 [411] = "Length Required",
788 [412] = "Precondition Failed",
789 [500] = "Internal Server Error",
790 [503] = "Server Unavailable",