module("luci.http.protocol", package.seeall)
require("ltn12")
-require("luci.util")
+require("luci.http.protocol.filter")
HTTP_MAX_CONTENT = 1024*4 -- 4 kB maximum content size
HTTP_URLENC_MAXKEYLEN = 1024 -- maximum allowd size of urlencoded parameter names
url = url:gsub( "^.+%?([^?]+)", "%1" )
end
- for i, pair in ipairs(luci.util.split( url, "[&;]+", nil, true )) do
+ for pair in url:gmatch( "[^&;]+" ) do
-- find key and value
local key = urldecode( pair:match("^([^=]+)") )
-- Extract "magic", the first line of a http message.
-- Extracts the message type ("get", "post" or "response"), the requested uri
-- or the status code if the line descripes a http response.
-process_states['magic'] = function( msg, chunk )
+process_states['magic'] = function( msg, chunk, err )
if chunk ~= nil then
+ -- ignore empty lines before request
+ if #chunk == 0 then
+ return true, nil
+ end
-- Is it a request?
local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
msg.type = "request"
msg.request_method = method:lower()
msg.request_uri = uri
- msg.http_version = http_ver
+ msg.http_version = tonumber( http_ver )
msg.headers = { }
-- We're done, next state is header parsing
msg.type = "response"
msg.status_code = code
msg.status_message = message
- msg.http_version = http_ver
+ msg.http_version = tonumber( http_ver )
msg.headers = { }
-- We're done, next state is header parsing
if chunk ~= nil then
-- Check for Content-Length
- if msg.headers['Content-Length'] then
- msg.content_length = tonumber(msg.headers['Content-Length'])
+ if msg.env.CONTENT_LENGTH then
+ msg.content_length = tonumber(msg.env.CONTENT_LENGTH)
if msg.content_length <= HTTP_MAX_CONTENT then
-- Initialize buffer
-- Process urldecoding stream, read and validate parameter key
process_states['urldecode-key'] = function( msg, chunk, filecb )
-
if chunk ~= nil then
-- Prevent oversized requests
else
msg._urldeccallback = function( chunk, eof )
msg.params[key] = msg.params[key] .. chunk
+
+ -- FIXME: Use a filter
+ if eof then
+ msg.params[key] = urldecode( msg.params[key] )
+ end
end
end
return true
end
else
- return nil, "Unexpected EOF"
+ -- Send EOF
+ msg._urldeccallback( "", true )
+ return false
end
end
+-- Creates a header source from a given socket
+function header_source( sock )
+ return ltn12.source.simplify( function()
+
+ local chunk, err, part = sock:receive("*l")
+
+ -- Line too long
+ if chunk == nil then
+ if err ~= "timeout" then
+ return nil, part
+ and "Line exceeds maximum allowed length["..part.."]"
+ or "Unexpected EOF"
+ else
+ return nil, err
+ end
+
+ -- Line ok
+ elseif chunk ~= nil then
+
+ -- Strip trailing CR
+ chunk = chunk:gsub("\r$","")
+
+ return chunk, nil
+ end
+ end )
+end
+
+
-- Decode MIME encoded data.
function mimedecode_message_body( source, msg, filecb )
-- Find mime boundary
- if msg and msg.headers['Content-Type'] then
+ if msg and msg.env.CONTENT_TYPE then
- local bound = msg.headers['Content-Type']:match("^multipart/form%-data; boundary=(.+)")
+ local bound = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)")
if bound then
msg.mime_boundary = bound
end
--- 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( source )
REQUEST_METHOD = msg.request_method:upper();
REQUEST_URI = msg.request_uri;
SCRIPT_NAME = msg.request_uri:gsub("?.+$","");
- SCRIPT_FILENAME = "" -- XXX implement me
+ SCRIPT_FILENAME = ""; -- XXX implement me
+ SERVER_PROTOCOL = "HTTP/" .. string.format("%.1f", msg.http_version)
}
-- Populate HTTP_* environment variables
-- Parse a http message body
function parse_message_body( source, msg, filecb )
-
-- Is it multipart/mime ?
if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
msg.env.CONTENT_TYPE:match("^multipart/form%-data")
elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
msg.env.CONTENT_TYPE == "application/x-www-form-urlencoded"
then
-
return urldecode_message_body( source, msg, filecb )
+
-- Unhandled encoding
- -- If a file callback is given then feed it line by line, else
+ -- If a file callback is given then feed it chunk by chunk, else
-- store whole buffer in message.content
else
local sink
- local length = 0
-- If we have a file callback then feed it
if type(filecb) == "function" then
msg.content_length = 0
sink = function( chunk )
- if ( msg.content_length ) + #chunk <= HTTP_MAX_CONTENT then
+ if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
msg.content = msg.content .. chunk
msg.content_length = msg.content_length + #chunk
end
end
end
+
+-- Status codes
+statusmsg = {
+ [200] = "OK",
+ [304] = "Not Modified",
+ [400] = "Bad Request",
+ [403] = "Forbidden",
+ [404] = "Not Found",
+ [405] = "Method Not Allowed",
+ [411] = "Length Required",
+ [412] = "Precondition Failed",
+ [500] = "Internal Server Error",
+ [503] = "Server Unavailable",
+}