* libs/http: fix header handling in conditionals.lua
[project/luci.git] / libs / http / luasrc / http / protocol.lua
index 01d3128..67b4258 100644 (file)
@@ -16,7 +16,7 @@ $Id$
 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
@@ -48,7 +48,7 @@ function urldecode_params( url, tbl )
                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("^([^=]+)")     )
@@ -114,9 +114,13 @@ local process_states = { }
 -- 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])$")
@@ -127,7 +131,7 @@ process_states['magic'] = function( msg, chunk )
                        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
@@ -146,7 +150,7 @@ process_states['magic'] = function( msg, chunk )
                                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
@@ -378,8 +382,8 @@ process_states['urldecode-init'] = function( msg, chunk, filecb )
        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
@@ -404,7 +408,6 @@ end
 
 -- Process urldecoding stream, read and validate parameter key
 process_states['urldecode-key'] = function( msg, chunk, filecb )
-
        if chunk ~= nil then
 
                -- Prevent oversized requests
@@ -436,6 +439,11 @@ process_states['urldecode-key'] = function( msg, chunk, filecb )
                                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
 
@@ -511,18 +519,48 @@ process_states['urldecode-value'] = function( msg, chunk, filecb )
                        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
@@ -611,20 +649,6 @@ function urldecode_message_body( source, msg )
 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 )
 
@@ -666,7 +690,8 @@ 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
@@ -695,7 +720,6 @@ end
 
 -- 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")
@@ -707,16 +731,15 @@ function parse_message_body( source, msg, filecb )
        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
@@ -728,7 +751,7 @@ function parse_message_body( source, msg, filecb )
                        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
@@ -752,3 +775,17 @@ function parse_message_body( source, msg, filecb )
                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",
+}