require("luci.util")
-HTTP_MAX_CONTENT = 1048576 -- 1 MB
+HTTP_MAX_CONTENT = 1024^2 -- 1 MB maximum content size
+HTTP_MAX_READBUF = 1024 -- 1 kB read buffer size
+
HTTP_DEFAULT_CTYPE = "text/html" -- default content type
HTTP_DEFAULT_VERSION = "1.0" -- HTTP default version
local params = { }
-- create a line reader
- local reader = _linereader( data )
+ local reader = _linereader( data, HTTP_MAX_READBUF )
-- state variables
local in_part = false
local field
local clen = 0
-
-- try to read all mime parts
- for line in reader do
+ for line, eol in reader do
-- update content length
clen = clen + line:len()
-- Parse a http message
function parse_message( data, filecb )
+ local reader = _linereader( data, HTTP_MAX_READBUF )
+ local message = parse_message_header( reader )
+
+ if message then
+ parse_message_body( reader, message, filecb )
+ end
+
+ return message
+end
+
+
+-- Parse a http message header
+function parse_message_header( data )
+
-- Create a line reader
- local reader = _linereader( data )
+ local reader = _linereader( data, HTTP_MAX_READBUF )
local message = { }
-- Try to extract magic
message.headers = hdrs
- -- Get content
- local clen = ( hdrs['Content-Length'] or HTTP_MAX_CONTENT ) + 0
-
-- Process get parameters
if ( method == "get" or method == "post" ) and
message.request_uri:match("?")
message.params = { }
end
- -- Process post method
- if method == "post" and hdrs['Content-Type'] then
-
- -- Is it multipart/form-data ?
- if hdrs['Content-Type']:match("^multipart/form%-data") then
- for k, v in pairs( mimedecode(
- reader,
- hdrs['Content-Type']:match("boundary=(.+)"),
- filecb
- ) ) do
- message.params[k] = v
- end
-
- -- Is it x-www-urlencoded?
- elseif hdrs['Content-Type'] == 'application/x-www-urlencoded' then
-
- -- XXX: readline isn't the best solution here
- for chunk in reader do
- for k, v in pairs( urldecode_params( chunk ) ) do
- message.params[k] = v
- end
-
- -- XXX: unreliable (undefined line length)
- if clen + chunk:len() >= HTTP_MAX_CONTENT then
- break
- end
-
- clen = clen + chunk:len()
- end
-
- -- Unhandled encoding
- -- If a file callback is given then feed it line by line, else
- -- store whole buffer in message.content
- else
-
- for chunk in reader do
-
- -- We have a callback, feed it.
- if type(filecb) == "function" then
-
- filecb( "_post", nil, chunk, false )
-
- -- Append to .content buffer.
- else
- message.content =
- type(message.content) == "string"
- and message.content .. chunk
- or chunk
- end
-
- -- XXX: unreliable
- if clen + chunk:len() >= HTTP_MAX_CONTENT then
- break
- end
-
- clen = clen + chunk:len()
- end
-
- -- Send eof to callback
- if type(filecb) == "function" then
- filecb( "_post", nil, "", true )
- end
- end
- end
-
-- Populate common environment variables
message.env = {
CONTENT_LENGTH = hdrs['Content-Length'];
end
end
-function _linereader( obj )
+
+-- Parse a http message body
+function parse_message_body( reader, message, filecb )
+
+ if type(message) == "table" then
+ local env = message.env
+
+ local clen = ( env.CONTENT_LENGTH or HTTP_MAX_CONTENT ) + 0
+
+ -- Process post method
+ if env.REQUEST_METHOD:lower() == "post" and env.CONTENT_TYPE then
+
+ -- Is it multipart/form-data ?
+ if env.CONTENT_TYPE:match("^multipart/form%-data") then
+
+ -- Read multipart/mime data
+ for k, v in pairs( mimedecode(
+ reader,
+ env.CONTENT_TYPE:match("boundary=(.+)"),
+ filecb
+ ) ) do
+ message.params[k] = v
+ end
+
+ -- Is it x-www-form-urlencoded?
+ elseif env.CONTENT_TYPE:match('^application/x%-www%-form%-urlencoded') then
+
+ -- Read post data
+ local post_data = ""
+
+ for chunk, eol in reader do
+
+ post_data = post_data .. chunk
+
+ -- Abort on eol or if maximum allowed size or content length is reached
+ if eol or #post_data >= HTTP_MAX_CONTENT or #post_data > clen then
+ break
+ end
+ end
+
+ -- Parse params
+ for k, v in pairs( urldecode_params( post_data ) ) do
+ message.params[k] = v
+ end
+
+ -- Unhandled encoding
+ -- If a file callback is given then feed it line by line, else
+ -- store whole buffer in message.content
+ else
+
+ local len = 0
+
+ for chunk in reader do
+
+ len = len + #chunk
+
+ -- We have a callback, feed it.
+ if type(filecb) == "function" then
+
+ filecb( "_post", nil, chunk, false )
+
+ -- Append to .content buffer.
+ else
+ message.content =
+ type(message.content) == "string"
+ and message.content .. chunk
+ or chunk
+ end
+
+ -- Abort if maximum allowed size or content length is reached
+ if len >= HTTP_MAX_CONTENT or len >= clen then
+ break
+ end
+ end
+
+ -- Send eof to callback
+ if type(filecb) == "function" then
+ filecb( "_post", nil, "", true )
+ end
+ end
+ end
+ end
+end
+
+
+-- Wrap given object into a line read iterator
+function _linereader( obj, bufsz )
+
+ bufsz = ( bufsz and bufsz >= 256 ) and bufsz or 256
+
+ local __read = function() return nil end
+ local __eof = function(x) return type(x) ~= "string" or #x == 0 end
+
+ local _pos = 1
+ local _buf = ""
+ local _eof = nil
-- object is string
if type(obj) == "string" then
- return obj:gmatch( "[^\r\n]*\r?\n" )
+ __read = function() return obj:sub( _pos, _pos + bufsz - #_buf - 1 ) end
+
+ -- object implements a receive() or read() function
+ elseif (type(obj) == "userdata" or type(obj) == "table") and ( type(obj.receive) == "function" or type(obj.read) == "function" ) then
+
+ if type(obj.read) == "function" then
+ __read = function() return obj:read( bufsz - #_buf ) end
+ else
+ __read = function() return obj:receive( bufsz - #_buf ) end
+ end
-- object is a function
elseif type(obj) == "function" then
return obj
- -- object is a table and implements a readline() function
- elseif type(obj) == "table" and type(obj.readline) == "function" then
+ -- no usable data type
+ else
- return obj.readline
+ -- dummy iterator
+ return __read
+ end
- -- object is a table and has a lines property
- elseif type(obj) == "table" and obj.lines then
- -- decide wheather to use "lines" as function or table
- local _lns = ( type(obj.lines) == "function" ) and obj.lines() or obj.lines
- local _pos = 1
-
- return function()
- if _pos <= #_lns then
- _pos = _pos + 1
- return _lns[_pos]
+ -- generic block to line algorithm
+ return function()
+ if not _eof then
+ local buffer = __read()
+
+ if __eof( buffer ) then
+ buffer = ""
end
- end
- -- no usable data type
- else
+ _pos = _pos + #buffer
+ buffer = _buf .. buffer
- -- dummy iterator
- return function()
+ local crlf, endpos = buffer:find("\r?\n")
+
+
+ if crlf then
+ _buf = buffer:sub( endpos + 1, #buffer )
+ return buffer:sub( 1, endpos ), true
+ else
+ -- check for eof
+ _eof = __eof( buffer )
+
+ -- clear overflow buffer
+ _buf = ""
+
+ return buffer, false
+ end
+ else
return nil
end
end