modules/admin-mini: Added Wifi configuration
[project/luci.git] / libs / http / luasrc / http / protocol.lua
index 318169e..542c314 100644 (file)
@@ -15,23 +15,27 @@ $Id$
 
 module("luci.http.protocol", package.seeall)
 
-require("ltn12")
-require("luci.http.protocol.filter")
+local ltn12 = require("luci.ltn12")
 
 HTTP_MAX_CONTENT      = 1024*4         -- 4 kB maximum content size
 HTTP_URLENC_MAXKEYLEN = 1024           -- maximum allowd size of urlencoded parameter names
+TSRC_BLOCKSIZE        = 2048           -- target block size for throttling sources
 
 
 -- Decode an urlencoded string.
 -- Returns the decoded value.
-function urldecode( str )
+function urldecode( str, no_plus )
 
        local function __chrdec( hex )
                return string.char( tonumber( hex, 16 ) )
        end
 
        if type(str) == "string" then
-               str = str:gsub( "+", " " ):gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec )
+               if not no_plus then
+                       str = str:gsub( "+", " " )
+               end
+
+               str = str:gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec )
        end
 
        return str
@@ -84,7 +88,7 @@ function urlencode( str )
 
        if type(str) == "string" then
                str = str:gsub(
-                       "([^a-zA-Z0-9$_%-%.+!*'(),])",
+                       "([^a-zA-Z0-9$_%-%.%+!*'(),])",
                        __chrenc
                )
        end
@@ -108,15 +112,49 @@ function urlencode_params( tbl )
 end
 
 
+-- Parameter helper
+local function __initval( tbl, key )
+       if tbl[key] == nil then
+               tbl[key] = ""
+       elseif type(tbl[key]) == "string" then
+               tbl[key] = { tbl[key], "" }
+       else
+               table.insert( tbl[key], "" )
+       end
+end
+
+local function __appendval( tbl, key, chunk )
+       if type(tbl[key]) == "table" then
+               tbl[key][#tbl[key]] = tbl[key][#tbl[key]] .. chunk
+       else
+               tbl[key] = tbl[key] .. chunk
+       end
+end
+
+local function __finishval( tbl, key, handler )
+       if handler then
+               if type(tbl[key]) == "table" then
+                       tbl[key][#tbl[key]] = handler( tbl[key][#tbl[key]] )
+               else
+                       tbl[key] = handler( tbl[key] )
+               end
+       end
+end
+
+
 -- Table of our process states
 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])$")
@@ -278,9 +316,10 @@ process_states['mime-headers'] = function( msg, chunk, filecb )
 
                                        -- Treat as form field
                                        else
-                                               msg.params[field] = ""
+                                               __initval( msg.params, field )
+
                                                msg._mimecallback = function(chunk,eof)
-                                                       msg.params[field] = msg.params[field] .. chunk
+                                                       __appendval( msg.params, field, chunk )
                                                end
                                        end
 
@@ -423,7 +462,6 @@ process_states['urldecode-key'] = function( msg, chunk, filecb )
                                local key = urldecode( buffer:sub( 1, spos - 1 ) )
 
                                -- Prepare buffers
-                               msg.params[key]         = ""
                                msg._urldeclength   = msg._urldeclength + epos
                                msg._urldecbuffer   = buffer:sub( epos + 1, #buffer )
 
@@ -433,12 +471,14 @@ process_states['urldecode-key'] = function( msg, chunk, filecb )
                                                filecb( field, chunk, eof )
                                        end
                                else
+                                       __initval( msg.params, key )
+
                                        msg._urldeccallback = function( chunk, eof )
-                                               msg.params[key] = msg.params[key] .. chunk
+                                               __appendval( msg.params, key, chunk )
 
                                                -- FIXME: Use a filter
                                                if eof then
-                                                       msg.params[key] = urldecode( msg.params[key] )
+                                                       __finishval( msg.params, key, urldecode )
                                                end
                                        end
                                end
@@ -522,6 +562,34 @@ process_states['urldecode-value'] = function( msg, chunk, filecb )
 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"
+                                       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 )
 
@@ -555,7 +623,7 @@ function mimedecode_message_body( source, msg, filecb )
 
                -- XXX: we schould propably keep the maximum buffer size in sync with
                --      the blocksize of our original source... but doesn't really matter
-               if msg._mimebuffer ~= null and #msg._mimebuffer > 256 then
+               if msg._mimebuffer ~= nil and #msg._mimebuffer > TSRC_BLOCKSIZE then
                        return ""
                else
                        return source()
@@ -593,7 +661,7 @@ function urldecode_message_body( source, msg )
        -- Create a throttling LTN12 source
        -- See explaination in mimedecode_message_body().
        local tsrc = function()
-               if msg._urldecbuffer ~= null and #msg._urldecbuffer > 0 then
+               if msg._urldecbuffer ~= nil and #msg._urldecbuffer > 0 then
                        return ""
                else
                        return source()
@@ -617,20 +685,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 )
 
@@ -673,7 +727,7 @@ function parse_message_header( source )
                                REQUEST_URI       = msg.request_uri;
                                SCRIPT_NAME       = msg.request_uri:gsub("?.+$","");
                                SCRIPT_FILENAME   = "";         -- XXX implement me
-                               SERVER_PROTOCOL   = "HTTP/" .. msg.http_version
+                               SERVER_PROTOCOL   = "HTTP/" .. string.format("%.1f", msg.http_version)
                        }
 
                        -- Populate HTTP_* environment variables
@@ -702,19 +756,6 @@ end
 
 -- Parse a http message body
 function parse_message_body( source, msg, filecb )
-
-       -- Install an additional filter if we're operating on chunked transfer
-       -- coding and client is HTTP/1.1 capable
-       if msg.http_version == 1.1 and
-          msg.headers['Transfer-Encoding'] and
-          msg.headers['Transfer-Encoding']:find("chunked")
-       then
-               source = ltn12.source.chain(
-                       source, luci.http.protocol.filter.decode_chunked
-               )
-       end
-
-
        -- Is it multipart/mime ?
        if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
           msg.env.CONTENT_TYPE:match("^multipart/form%-data")
@@ -771,33 +812,17 @@ function parse_message_body( source, msg, filecb )
        end
 end
 
-
--- Push a response to a socket
-function push_response(request, response, sourceout, sinkout, sinkerr)
-       local code = response.status
-       sinkout(request.env.SERVER_PROTOCOL .. " " .. code .. " " .. statusmsg[code] .. "\r\n")
-
-       -- FIXME: Add support for keep-alive
-       response.headers["Connection"] = "close"
-
-       for k,v in pairs(response.headers) do
-               sinkout(k .. ": " .. v .. "\r\n")
-       end
-
-       sinkout("\r\n")
-
-       if sourceout then
-               ltn12.pump.all(sourceout, sinkout)
-       end
-end
-
-
 -- Status codes
 statusmsg = {
        [200] = "OK",
+       [301] = "Moved Permanently",
+       [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",
 }