* Moved luci.http.protocol to libs/http
authorSteven Barth <steven@midlink.org>
Fri, 20 Jun 2008 16:47:25 +0000 (16:47 +0000)
committerSteven Barth <steven@midlink.org>
Fri, 20 Jun 2008 16:47:25 +0000 (16:47 +0000)
* Added ltn12 to libs/core
* Fixed libs/httpd Makefile

NOTICE
contrib/package/luci/Makefile
libs/core/lua/ltn12.lua [new file with mode: 0644]
libs/http/Makefile [new file with mode: 0644]
libs/http/luasrc/http/protocol.lua [new file with mode: 0644]
libs/httpd/Makefile
libs/httpd/luasrc/http/protocol.lua [deleted file]

diff --git a/NOTICE b/NOTICE
index e2c3383e3e5f2b5fe518910f1e3bfd2ffcbe756d..7d9ed98019b7f3dcf234ae954fadf39ae9d93b9e 100644 (file)
--- a/NOTICE
+++ b/NOTICE
@@ -6,6 +6,7 @@ Licensed under the Apache License, Version 2.0.
 Contains code from:
 BinDecHex      - Copyright 2007 Tim Kelly/Dialectronics
 coxpcall       - Copyright 2005 - Kepler Project (www.keplerproject.org)
+ltn12/luasocket - Copyright 2004-2007 Diego Nehab
 
 
 Luci-Statistics - Statistics for LuCI
index b2571f750f2b7a1091aebb10b302d57c5280e0fc..8b03f79569cc186ad5d81d551a0e96087d582262 100644 (file)
@@ -132,9 +132,19 @@ define Package/luci-fastindex/install
 endef
 
 
+define Package/luci-http
+  $(call Package/luci/libtemplate)
+  TITLE:=HTTP Protocol implementation
+endef
+
+define Package/luci-http/install
+       $(call Package/luci/install/template,$(1),libs/http)
+endef
+
+
 define Package/luci-web
   $(call Package/luci/libtemplate)
-  DEPENDS+=+luci-addons +luci-uci
+  DEPENDS+=+luci-http +luci-addons +luci-uci
   TITLE:=MVC Webframework
 endef
 
@@ -384,6 +394,9 @@ endif
 ifneq ($(CONFIG_PACKAGE_luci-fastindex),)
        PKG_SELECTED_MODULES+=libs/fastindex
 endif
+ifneq ($(CONFIG_PACKAGE_luci-http),)
+       PKG_SELECTED_MODULES+=libs/http
+endif
 ifneq ($(CONFIG_PACKAGE_luci-uci),)
        PKG_SELECTED_MODULES+=libs/uci
 endif
@@ -455,6 +468,7 @@ MAKE_FLAGS += MODULES="$(PKG_SELECTED_MODULES)" LUA_TARGET="$(LUA_TARGET)" CFLAG
 $(eval $(call BuildPackage,luci-core))
 $(eval $(call BuildPackage,luci-cbi))
 $(eval $(call BuildPackage,luci-fastindex))
+$(eval $(call BuildPackage,luci-http))
 $(eval $(call BuildPackage,luci-uci))
 $(eval $(call BuildPackage,luci-web))
 
diff --git a/libs/core/lua/ltn12.lua b/libs/core/lua/ltn12.lua
new file mode 100644 (file)
index 0000000..417da84
--- /dev/null
@@ -0,0 +1,314 @@
+--[[
+LuaSocket 2.0.2 license
+Copyright � 2004-2007 Diego Nehab
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+]]--
+-----------------------------------------------------------------------------
+-- LTN12 - Filters, sources, sinks and pumps.
+-- LuaSocket toolkit.
+-- Author: Diego Nehab
+-- RCS ID: $Id: ltn12.lua,v 1.31 2006/04/03 04:45:42 diego Exp $
+-----------------------------------------------------------------------------
+
+-----------------------------------------------------------------------------
+-- Declare module
+-----------------------------------------------------------------------------
+local string = require("string")
+local table = require("table")
+local base = _G
+module("ltn12")
+
+filter = {}
+source = {}
+sink = {}
+pump = {}
+
+-- 2048 seems to be better in windows...
+BLOCKSIZE = 2048
+_VERSION = "LTN12 1.0.1"
+
+-----------------------------------------------------------------------------
+-- Filter stuff
+-----------------------------------------------------------------------------
+-- returns a high level filter that cycles a low-level filter
+function filter.cycle(low, ctx, extra)
+    base.assert(low)
+    return function(chunk)
+        local ret
+        ret, ctx = low(ctx, chunk, extra)
+        return ret
+    end
+end
+
+-- chains a bunch of filters together
+-- (thanks to Wim Couwenberg)
+function filter.chain(...)
+    local n = table.getn(arg)
+    local top, index = 1, 1
+    local retry = ""
+    return function(chunk)
+        retry = chunk and retry
+        while true do
+            if index == top then
+                chunk = arg[index](chunk)
+                if chunk == "" or top == n then return chunk
+                elseif chunk then index = index + 1
+                else
+                    top = top+1
+                    index = top
+                end
+            else
+                chunk = arg[index](chunk or "")
+                if chunk == "" then
+                    index = index - 1
+                    chunk = retry
+                elseif chunk then
+                    if index == n then return chunk
+                    else index = index + 1 end
+                else base.error("filter returned inappropriate nil") end
+            end
+        end
+    end
+end
+
+-----------------------------------------------------------------------------
+-- Source stuff
+-----------------------------------------------------------------------------
+-- create an empty source
+local function empty()
+    return nil
+end
+
+function source.empty()
+    return empty
+end
+
+-- returns a source that just outputs an error
+function source.error(err)
+    return function()
+        return nil, err
+    end
+end
+
+-- creates a file source
+function source.file(handle, io_err)
+    if handle then
+        return function()
+            local chunk = handle:read(BLOCKSIZE)
+            if not chunk then handle:close() end
+            return chunk
+        end
+    else return source.error(io_err or "unable to open file") end
+end
+
+-- turns a fancy source into a simple source
+function source.simplify(src)
+    base.assert(src)
+    return function()
+        local chunk, err_or_new = src()
+        src = err_or_new or src
+        if not chunk then return nil, err_or_new
+        else return chunk end
+    end
+end
+
+-- creates string source
+function source.string(s)
+    if s then
+        local i = 1
+        return function()
+            local chunk = string.sub(s, i, i+BLOCKSIZE-1)
+            i = i + BLOCKSIZE
+            if chunk ~= "" then return chunk
+            else return nil end
+        end
+    else return source.empty() end
+end
+
+-- creates rewindable source
+function source.rewind(src)
+    base.assert(src)
+    local t = {}
+    return function(chunk)
+        if not chunk then
+            chunk = table.remove(t)
+            if not chunk then return src()
+            else return chunk end
+        else
+            table.insert(t, chunk)
+        end
+    end
+end
+
+function source.chain(src, f)
+    base.assert(src and f)
+    local last_in, last_out = "", ""
+    local state = "feeding"
+    local err
+    return function()
+        if not last_out then
+            base.error('source is empty!', 2)
+        end
+        while true do
+            if state == "feeding" then
+                last_in, err = src()
+                if err then return nil, err end
+                last_out = f(last_in)
+                if not last_out then
+                    if last_in then
+                        base.error('filter returned inappropriate nil')
+                    else
+                        return nil
+                    end
+                elseif last_out ~= "" then
+                    state = "eating"
+                    if last_in then last_in = "" end
+                    return last_out
+                end
+            else
+                last_out = f(last_in)
+                if last_out == "" then
+                    if last_in == "" then
+                        state = "feeding"
+                    else
+                        base.error('filter returned ""')
+                    end
+                elseif not last_out then
+                    if last_in then
+                        base.error('filter returned inappropriate nil')
+                    else
+                        return nil
+                    end
+                else
+                    return last_out
+                end
+            end
+        end
+    end
+end
+
+-- creates a source that produces contents of several sources, one after the
+-- other, as if they were concatenated
+-- (thanks to Wim Couwenberg)
+function source.cat(...)
+    local src = table.remove(arg, 1)
+    return function()
+        while src do
+            local chunk, err = src()
+            if chunk then return chunk end
+            if err then return nil, err end
+            src = table.remove(arg, 1)
+        end
+    end
+end
+
+-----------------------------------------------------------------------------
+-- Sink stuff
+-----------------------------------------------------------------------------
+-- creates a sink that stores into a table
+function sink.table(t)
+    t = t or {}
+    local f = function(chunk, err)
+        if chunk then table.insert(t, chunk) end
+        return 1
+    end
+    return f, t
+end
+
+-- turns a fancy sink into a simple sink
+function sink.simplify(snk)
+    base.assert(snk)
+    return function(chunk, err)
+        local ret, err_or_new = snk(chunk, err)
+        if not ret then return nil, err_or_new end
+        snk = err_or_new or snk
+        return 1
+    end
+end
+
+-- creates a file sink
+function sink.file(handle, io_err)
+    if handle then
+        return function(chunk, err)
+            if not chunk then
+                handle:close()
+                return 1
+            else return handle:write(chunk) end
+        end
+    else return sink.error(io_err or "unable to open file") end
+end
+
+-- creates a sink that discards data
+local function null()
+    return 1
+end
+
+function sink.null()
+    return null
+end
+
+-- creates a sink that just returns an error
+function sink.error(err)
+    return function()
+        return nil, err
+    end
+end
+
+-- chains a sink with a filter
+function sink.chain(f, snk)
+    base.assert(f and snk)
+    return function(chunk, err)
+        if chunk ~= "" then
+            local filtered = f(chunk)
+            local done = chunk and ""
+            while true do
+                local ret, snkerr = snk(filtered, err)
+                if not ret then return nil, snkerr end
+                if filtered == done then return 1 end
+                filtered = f(done)
+            end
+        else return 1 end
+    end
+end
+
+-----------------------------------------------------------------------------
+-- Pump stuff
+-----------------------------------------------------------------------------
+-- pumps one chunk from the source to the sink
+function pump.step(src, snk)
+    local chunk, src_err = src()
+    local ret, snk_err = snk(chunk, src_err)
+    if chunk and ret then return 1
+    else return nil, src_err or snk_err end
+end
+
+-- pumps all data from a source to a sink, using a step function
+function pump.all(src, snk, step)
+    base.assert(src and snk)
+    step = step or pump.step
+    while true do
+        local ret, err = step(src, snk)
+        if not ret then
+            if err then return nil, err
+            else return 1 end
+        end
+    end
+end
+
diff --git a/libs/http/Makefile b/libs/http/Makefile
new file mode 100644 (file)
index 0000000..f7fac77
--- /dev/null
@@ -0,0 +1,2 @@
+include ../../build/config.mk
+include ../../build/module.mk
diff --git a/libs/http/luasrc/http/protocol.lua b/libs/http/luasrc/http/protocol.lua
new file mode 100644 (file)
index 0000000..01d3128
--- /dev/null
@@ -0,0 +1,754 @@
+--[[
+
+HTTP protocol implementation for LuCI
+(c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+
+]]--
+
+module("luci.http.protocol", package.seeall)
+
+require("ltn12")
+require("luci.util")
+
+HTTP_MAX_CONTENT      = 1024*4         -- 4 kB maximum content size
+HTTP_URLENC_MAXKEYLEN = 1024           -- maximum allowd size of urlencoded parameter names
+
+
+-- Decode an urlencoded string.
+-- Returns the decoded value.
+function urldecode( str )
+
+       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 )
+       end
+
+       return str
+end
+
+
+-- Extract and split urlencoded data pairs, separated bei either "&" or ";" from given url.
+-- Returns a table value with urldecoded values.
+function urldecode_params( url, tbl )
+
+       local params = tbl or { }
+
+       if url:find("?") then
+               url = url:gsub( "^.+%?([^?]+)", "%1" )
+       end
+
+       for i, pair in ipairs(luci.util.split( url, "[&;]+", nil, true )) do
+
+               -- find key and value
+               local key = urldecode( pair:match("^([^=]+)")     )
+               local val = urldecode( pair:match("^[^=]+=(.+)$") )
+
+               -- store
+               if type(key) == "string" and key:len() > 0 then
+                       if type(val) ~= "string" then val = "" end
+
+                       if not params[key] then
+                               params[key] = val
+                       elseif type(params[key]) ~= "table" then
+                               params[key] = { params[key], val }
+                       else
+                               table.insert( params[key], val )
+                       end
+               end
+       end
+
+       return params
+end
+
+
+-- Encode given string in urlencoded format.
+-- Returns the encoded string.
+function urlencode( str )
+
+       local function __chrenc( chr )
+               return string.format(
+                       "%%%02x", string.byte( chr )
+               )
+       end
+
+       if type(str) == "string" then
+               str = str:gsub(
+                       "([^a-zA-Z0-9$_%-%.+!*'(),])",
+                       __chrenc
+               )
+       end
+
+       return str
+end
+
+
+-- Encode given table to urlencoded string.
+-- Returns the encoded string.
+function urlencode_params( tbl )
+       local enc = ""
+
+       for k, v in pairs(tbl) do
+               enc = enc .. ( enc and "&" or "" ) ..
+                       urlencode(k) .. "="  ..
+                       urlencode(v)
+       end
+
+       return enc
+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 )
+
+       if chunk ~= nil then
+
+               -- Is it a request?
+               local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
+
+               -- Yup, it is
+               if method then
+
+                       msg.type           = "request"
+                       msg.request_method = method:lower()
+                       msg.request_uri    = uri
+                       msg.http_version   = http_ver
+                       msg.headers        = { }
+
+                       -- We're done, next state is header parsing
+                       return true, function( chunk )
+                               return process_states['headers']( msg, chunk )
+                       end
+
+               -- Is it a response?
+               else
+
+                       local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
+
+                       -- Is a response
+                       if code then
+
+                               msg.type           = "response"
+                               msg.status_code    = code
+                               msg.status_message = message
+                               msg.http_version   = http_ver
+                               msg.headers        = { }
+
+                               -- We're done, next state is header parsing
+                               return true, function( chunk )
+                                       return process_states['headers']( msg, chunk )
+                               end
+                       end
+               end
+       end
+
+       -- Can't handle it
+       return nil, "Invalid HTTP message magic"
+end
+
+
+-- Extract headers from given string.
+process_states['headers'] = function( msg, chunk )
+
+       if chunk ~= nil then
+
+               -- Look for a valid header format
+               local hdr, val = chunk:match( "^([A-Z][A-Za-z0-9%-_]+): +(.+)$" )
+
+               if type(hdr) == "string" and hdr:len() > 0 and
+                  type(val) == "string" and val:len() > 0
+               then
+                       msg.headers[hdr] = val
+
+                       -- Valid header line, proceed
+                       return true, nil
+
+               elseif #chunk == 0 then
+                       -- Empty line, we won't accept data anymore
+                       return false, nil
+               else
+                       -- Junk data
+                       return nil, "Invalid HTTP header received"
+               end
+       else
+               return nil, "Unexpected EOF"
+       end
+end
+
+
+-- Find first MIME boundary
+process_states['mime-init'] = function( msg, chunk, filecb )
+
+       if chunk ~= nil then
+               if #chunk >= #msg.mime_boundary + 2 then
+                       local boundary = chunk:sub( 1, #msg.mime_boundary + 4 )
+
+                       if boundary == "--" .. msg.mime_boundary .. "\r\n" then
+
+                               -- Store remaining data in buffer
+                               msg._mimebuffer = chunk:sub( #msg.mime_boundary + 5, #chunk )
+
+                               -- Switch to header processing state
+                               return true, function( chunk )
+                                       return process_states['mime-headers']( msg, chunk, filecb )
+                               end
+                       else
+                               return nil, "Invalid MIME boundary"
+                       end
+               else
+                       return true
+               end
+       else
+               return nil, "Unexpected EOF"
+       end
+end
+
+
+-- Read MIME part headers
+process_states['mime-headers'] = function( msg, chunk, filecb )
+
+       if chunk ~= nil then
+
+               -- Combine look-behind buffer with current chunk
+               chunk = msg._mimebuffer .. chunk
+
+               if not msg._mimeheaders then
+                       msg._mimeheaders = { }
+               end
+
+               local function __storehdr( k, v )
+                       msg._mimeheaders[k] = v
+                       return ""
+               end
+
+               -- Read all header lines
+               local ok, count = 1, 0
+               while ok > 0 do
+                       chunk, ok = chunk:gsub( "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n", __storehdr )
+                       count = count + ok
+               end
+
+               -- Headers processed, check for empty line
+               chunk, ok = chunk:gsub( "^\r\n", "" )
+
+               -- Store remaining buffer contents
+               msg._mimebuffer = chunk
+
+               -- End of headers
+               if ok > 0 then
+
+                       -- When no Content-Type header is given assume text/plain
+                       if not msg._mimeheaders['Content-Type'] then
+                               msg._mimeheaders['Content-Type'] = 'text/plain'
+                       end
+
+                       -- Check Content-Disposition
+                       if msg._mimeheaders['Content-Disposition'] then
+                               -- Check for "form-data" token
+                               if msg._mimeheaders['Content-Disposition']:match("^form%-data; ") then
+                                       -- Check for field name, filename
+                                       local field = msg._mimeheaders['Content-Disposition']:match('name="(.-)"')
+                                       local file  = msg._mimeheaders['Content-Disposition']:match('filename="(.+)"$')
+
+                                       -- Is a file field and we have a callback
+                                       if file and filecb then
+                                               msg.params[field] = file
+                                               msg._mimecallback = function(chunk,eof)
+                                                       filecb( {
+                                                               name    = field;
+                                                               file    = file;
+                                                               headers = msg._mimeheaders
+                                                       }, chunk, eof )
+                                               end
+
+                                       -- Treat as form field
+                                       else
+                                               msg.params[field] = ""
+                                               msg._mimecallback = function(chunk,eof)
+                                                       msg.params[field] = msg.params[field] .. chunk
+                                               end
+                                       end
+
+                                       -- Header was valid, continue with mime-data
+                                       return true, function( chunk )
+                                               return process_states['mime-data']( msg, chunk, filecb )
+                                       end
+                               else
+                                       -- Unknown Content-Disposition, abort
+                                       return nil, "Unexpected Content-Disposition MIME section header"
+                               end
+                       else
+                               -- Content-Disposition is required, abort without
+                               return nil, "Missing Content-Disposition MIME section header"
+                       end
+
+               -- We parsed no headers yet and buffer is almost empty
+               elseif count > 0 or #chunk < 128 then
+                       -- Keep feeding me with chunks
+                       return true, nil
+               end
+
+               -- Buffer looks like garbage
+               return nil, "Malformed MIME section header"
+       else
+               return nil, "Unexpected EOF"
+       end
+end
+
+
+-- Read MIME part data
+process_states['mime-data'] = function( msg, chunk, filecb )
+
+       if chunk ~= nil then
+
+               -- Combine look-behind buffer with current chunk
+               local buffer = msg._mimebuffer .. chunk
+
+               -- Look for MIME boundary
+               local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
+
+               if spos then
+                       -- Content data
+                       msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
+
+                       -- Store remainder
+                       msg._mimebuffer = buffer:sub( epos + 1, #buffer )
+
+                       -- Next state is mime-header processing
+                       return true, function( chunk )
+                               return process_states['mime-headers']( msg, chunk, filecb )
+                       end
+               else
+                       -- Look for EOF?
+                       local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
+
+                       if spos then
+                               -- Content data
+                               msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
+
+                               -- We processed the final MIME boundary, cleanup
+                               msg._mimebuffer   = nil
+                               msg._mimeheaders  = nil
+                               msg._mimecallback = nil
+
+                               -- We won't accept data anymore
+                               return false
+                       else
+                               -- We're somewhere within a data section and our buffer is full
+                               if #buffer > #chunk then
+                                       -- Flush buffered data
+                                       msg._mimecallback( buffer:sub( 1, #buffer - #chunk ), false )
+
+                                       -- Store new data
+                                       msg._mimebuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
+
+                               -- Buffer is not full yet, append new data
+                               else
+                                       msg._mimebuffer = buffer
+                               end
+
+                               -- Keep feeding me
+                               return true
+                       end
+               end
+       else
+               return nil, "Unexpected EOF"
+       end
+end
+
+
+-- Init urldecoding stream
+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.content_length <= HTTP_MAX_CONTENT then
+                               -- Initialize buffer
+                               msg._urldecbuffer = chunk
+                               msg._urldeclength = 0
+
+                               -- Switch to urldecode-key state
+                               return true, function(chunk)
+                                       return process_states['urldecode-key']( msg, chunk, filecb )
+                               end
+                       else
+                               return nil, "Request exceeds maximum allowed size"
+                       end
+               else
+                       return nil, "Missing Content-Length header"
+               end
+       else
+               return nil, "Unexpected EOF"
+       end
+end
+
+
+-- Process urldecoding stream, read and validate parameter key
+process_states['urldecode-key'] = function( msg, chunk, filecb )
+
+       if chunk ~= nil then
+
+               -- Prevent oversized requests
+               if msg._urldeclength >= msg.content_length then
+                       return nil, "Request exceeds maximum allowed size"
+               end
+
+               -- Combine look-behind buffer with current chunk
+               local buffer = msg._urldecbuffer .. chunk
+               local spos, epos = buffer:find("=")
+
+               -- Found param
+               if spos then
+
+                       -- Check that key doesn't exceed maximum allowed key length
+                       if ( spos - 1 ) <= HTTP_URLENC_MAXKEYLEN then
+                               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 )
+
+                               -- Use file callback or store values inside msg.params
+                               if filecb then
+                                       msg._urldeccallback = function( chunk, eof )
+                                               filecb( field, chunk, eof )
+                                       end
+                               else
+                                       msg._urldeccallback = function( chunk, eof )
+                                               msg.params[key] = msg.params[key] .. chunk
+                                       end
+                               end
+
+                               -- Proceed with urldecode-value state
+                               return true, function( chunk )
+                                       return process_states['urldecode-value']( msg, chunk, filecb )
+                               end
+                       else
+                               return nil, "POST parameter exceeds maximum allowed length"
+                       end
+               else
+                       return nil, "POST data exceeds maximum allowed length"
+               end
+       else
+               return nil, "Unexpected EOF"
+       end
+end
+
+
+-- Process urldecoding stream, read parameter value
+process_states['urldecode-value'] = function( msg, chunk, filecb )
+
+       if chunk ~= nil then
+
+               -- Combine look-behind buffer with current chunk
+               local buffer = msg._urldecbuffer .. chunk
+
+               -- Check for EOF
+               if #buffer == 0 then
+                       -- Compare processed length
+                       if msg._urldeclength == msg.content_length then
+                               -- Cleanup
+                               msg._urldeclength   = nil
+                               msg._urldecbuffer   = nil
+                               msg._urldeccallback = nil
+
+                               -- We won't accept data anymore
+                               return false
+                       else
+                               return nil, "Content-Length mismatch"
+                       end
+               end
+
+               -- Check for end of value
+               local spos, epos = buffer:find("[&;]")
+               if spos then
+
+                       -- Flush buffer, send eof
+                       msg._urldeccallback( buffer:sub( 1, spos - 1 ), true )
+                       msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
+                       msg._urldeclength = msg._urldeclength + epos
+
+                       -- Back to urldecode-key state
+                       return true, function( chunk )
+                               return process_states['urldecode-key']( msg, chunk, filecb )
+                       end
+               else
+                       -- We're somewhere within a data section and our buffer is full
+                       if #buffer > #chunk then
+                               -- Flush buffered data
+                               msg._urldeccallback( buffer:sub( 1, #buffer - #chunk ), false )
+
+                               -- Store new data
+                               msg._urldeclength = msg._urldeclength + #buffer - #chunk
+                               msg._urldecbuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
+
+                       -- Buffer is not full yet, append new data
+                       else
+                               msg._urldecbuffer = buffer
+                       end
+
+                       -- Keep feeding me
+                       return true
+               end
+       else
+               return nil, "Unexpected EOF"
+       end
+end
+
+
+-- Decode MIME encoded data.
+function mimedecode_message_body( source, msg, filecb )
+
+       -- Find mime boundary
+       if msg and msg.headers['Content-Type'] then
+
+               local bound = msg.headers['Content-Type']:match("^multipart/form%-data; boundary=(.+)")
+
+               if bound then
+                       msg.mime_boundary = bound
+               else
+                       return nil, "No MIME boundary found or invalid content type given"
+               end
+       end
+
+       -- Create an initial LTN12 sink
+       -- The whole MIME parsing process is implemented as fancy sink, sinks replace themself
+       -- depending on current processing state (init, header, data). Return the initial state.
+       local sink = ltn12.sink.simplify(
+               function( chunk )
+                       return process_states['mime-init']( msg, chunk, filecb )
+               end
+       )
+
+       -- Create a throttling LTN12 source
+       -- Frequent state switching in the mime parsing process leads to unwanted buffer aggregation.
+       -- This source checks wheather there's still data in our internal read buffer and returns an
+       -- empty string if there's already enough data in the processing queue. If the internal buffer
+       -- runs empty we're calling the original source to get the next chunk of data.
+       local tsrc = function()
+
+               -- 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
+                       return ""
+               else
+                       return source()
+               end
+       end
+
+       -- Pump input data...
+       while true do
+               -- get data
+               local ok, err = ltn12.pump.step( tsrc, sink )
+
+               -- error
+               if not ok and err then
+                       return nil, err
+
+               -- eof
+               elseif not ok then
+                       return true
+               end
+       end
+end
+
+
+-- Decode urlencoded data.
+function urldecode_message_body( source, msg )
+
+       -- Create an initial LTN12 sink
+       -- Return the initial state.
+       local sink = ltn12.sink.simplify(
+               function( chunk )
+                       return process_states['urldecode-init']( msg, chunk )
+               end
+       )
+
+       -- Create a throttling LTN12 source
+       -- See explaination in mimedecode_message_body().
+       local tsrc = function()
+               if msg._urldecbuffer ~= null and #msg._urldecbuffer > 0 then
+                       return ""
+               else
+                       return source()
+               end
+       end
+
+       -- Pump input data...
+       while true do
+               -- get data
+               local ok, err = ltn12.pump.step( tsrc, sink )
+
+               -- step
+               if not ok and err then
+                       return nil, err
+
+               -- eof
+               elseif not ok then
+                       return true
+               end
+       end
+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 )
+
+       local ok   = true
+       local msg  = { }
+
+       local sink = ltn12.sink.simplify(
+               function( chunk )
+                       return process_states['magic']( msg, chunk )
+               end
+       )
+
+       -- Pump input data...
+       while ok do
+
+               -- get data
+               ok, err = ltn12.pump.step( source, sink )
+
+               -- error
+               if not ok and err then
+                       return nil, err
+
+               -- eof
+               elseif not ok then
+
+                       -- Process get parameters
+                       if ( msg.request_method == "get" or msg.request_method == "post" ) and
+                          msg.request_uri:match("?")
+                       then
+                               msg.params = urldecode_params( msg.request_uri )
+                       else
+                               msg.params = { }
+                       end
+
+                       -- Populate common environment variables
+                       msg.env = {
+                               CONTENT_LENGTH    = msg.headers['Content-Length'];
+                               CONTENT_TYPE      = msg.headers['Content-Type'];
+                               REQUEST_METHOD    = msg.request_method:upper();
+                               REQUEST_URI       = msg.request_uri;
+                               SCRIPT_NAME       = msg.request_uri:gsub("?.+$","");
+                               SCRIPT_FILENAME   = ""          -- XXX implement me
+                       }
+
+                       -- Populate HTTP_* environment variables
+                       for i, hdr in ipairs( {
+                               'Accept',
+                               'Accept-Charset',
+                               'Accept-Encoding',
+                               'Accept-Language',
+                               'Connection',
+                               'Cookie',
+                               'Host',
+                               'Referer',
+                               'User-Agent',
+                       } ) do
+                               local var = 'HTTP_' .. hdr:upper():gsub("%-","_")
+                               local val = msg.headers[hdr]
+
+                               msg.env[var] = val
+                       end
+               end
+       end
+
+       return msg
+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")
+       then
+
+               return mimedecode_message_body( source, msg, filecb )
+
+       -- Is it application/x-www-form-urlencoded ?
+       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
+       -- 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
+                       sink = filecb
+
+               -- ... else append to .content
+               else
+                       msg.content = ""
+                       msg.content_length = 0
+
+                       sink = function( chunk )
+                               if ( msg.content_length ) + #chunk <= HTTP_MAX_CONTENT then
+
+                                       msg.content        = msg.content        .. chunk
+                                       msg.content_length = msg.content_length + #chunk
+
+                                       return true
+                               else
+                                       return nil, "POST data exceeds maximum allowed length"
+                               end
+                       end
+               end
+
+               -- Pump data...
+               while true do
+                       local ok, err = ltn12.pump.step( source, sink )
+
+                       if not ok and err then
+                               return nil, err
+                       elseif not err then
+                               return true
+                       end
+               end
+       end
+end
index ee1a40ea80fb46b73892aff14a8a4f26fb783094..f7fac7740e84c3d1c6eab2347fc2f0bb694fb7a1 100644 (file)
@@ -1,13 +1,2 @@
-include ../../build/module.mk
 include ../../build/config.mk
-include ../../build/gccconfig.mk
-
-%.o: %.c
-       $(COMPILE) $(LUA_CFLAGS) $(FPIC) -c -o $@ $< 
-
-compile: src/fastindex.o
-       mkdir -p dist$(LUCI_LIBRARYDIR)
-       $(LINK) $(SHLIB_FLAGS) -o dist$(LUCI_LIBRARYDIR)/fastindex.so src/fastindex.o $(LUA_SHLIBS)
-
-clean:
-       rm -f src/*.o
+include ../../build/module.mk
diff --git a/libs/httpd/luasrc/http/protocol.lua b/libs/httpd/luasrc/http/protocol.lua
deleted file mode 100644 (file)
index 01d3128..0000000
+++ /dev/null
@@ -1,754 +0,0 @@
---[[
-
-HTTP protocol implementation for LuCI
-(c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-$Id$
-
-]]--
-
-module("luci.http.protocol", package.seeall)
-
-require("ltn12")
-require("luci.util")
-
-HTTP_MAX_CONTENT      = 1024*4         -- 4 kB maximum content size
-HTTP_URLENC_MAXKEYLEN = 1024           -- maximum allowd size of urlencoded parameter names
-
-
--- Decode an urlencoded string.
--- Returns the decoded value.
-function urldecode( str )
-
-       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 )
-       end
-
-       return str
-end
-
-
--- Extract and split urlencoded data pairs, separated bei either "&" or ";" from given url.
--- Returns a table value with urldecoded values.
-function urldecode_params( url, tbl )
-
-       local params = tbl or { }
-
-       if url:find("?") then
-               url = url:gsub( "^.+%?([^?]+)", "%1" )
-       end
-
-       for i, pair in ipairs(luci.util.split( url, "[&;]+", nil, true )) do
-
-               -- find key and value
-               local key = urldecode( pair:match("^([^=]+)")     )
-               local val = urldecode( pair:match("^[^=]+=(.+)$") )
-
-               -- store
-               if type(key) == "string" and key:len() > 0 then
-                       if type(val) ~= "string" then val = "" end
-
-                       if not params[key] then
-                               params[key] = val
-                       elseif type(params[key]) ~= "table" then
-                               params[key] = { params[key], val }
-                       else
-                               table.insert( params[key], val )
-                       end
-               end
-       end
-
-       return params
-end
-
-
--- Encode given string in urlencoded format.
--- Returns the encoded string.
-function urlencode( str )
-
-       local function __chrenc( chr )
-               return string.format(
-                       "%%%02x", string.byte( chr )
-               )
-       end
-
-       if type(str) == "string" then
-               str = str:gsub(
-                       "([^a-zA-Z0-9$_%-%.+!*'(),])",
-                       __chrenc
-               )
-       end
-
-       return str
-end
-
-
--- Encode given table to urlencoded string.
--- Returns the encoded string.
-function urlencode_params( tbl )
-       local enc = ""
-
-       for k, v in pairs(tbl) do
-               enc = enc .. ( enc and "&" or "" ) ..
-                       urlencode(k) .. "="  ..
-                       urlencode(v)
-       end
-
-       return enc
-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 )
-
-       if chunk ~= nil then
-
-               -- Is it a request?
-               local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
-
-               -- Yup, it is
-               if method then
-
-                       msg.type           = "request"
-                       msg.request_method = method:lower()
-                       msg.request_uri    = uri
-                       msg.http_version   = http_ver
-                       msg.headers        = { }
-
-                       -- We're done, next state is header parsing
-                       return true, function( chunk )
-                               return process_states['headers']( msg, chunk )
-                       end
-
-               -- Is it a response?
-               else
-
-                       local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
-
-                       -- Is a response
-                       if code then
-
-                               msg.type           = "response"
-                               msg.status_code    = code
-                               msg.status_message = message
-                               msg.http_version   = http_ver
-                               msg.headers        = { }
-
-                               -- We're done, next state is header parsing
-                               return true, function( chunk )
-                                       return process_states['headers']( msg, chunk )
-                               end
-                       end
-               end
-       end
-
-       -- Can't handle it
-       return nil, "Invalid HTTP message magic"
-end
-
-
--- Extract headers from given string.
-process_states['headers'] = function( msg, chunk )
-
-       if chunk ~= nil then
-
-               -- Look for a valid header format
-               local hdr, val = chunk:match( "^([A-Z][A-Za-z0-9%-_]+): +(.+)$" )
-
-               if type(hdr) == "string" and hdr:len() > 0 and
-                  type(val) == "string" and val:len() > 0
-               then
-                       msg.headers[hdr] = val
-
-                       -- Valid header line, proceed
-                       return true, nil
-
-               elseif #chunk == 0 then
-                       -- Empty line, we won't accept data anymore
-                       return false, nil
-               else
-                       -- Junk data
-                       return nil, "Invalid HTTP header received"
-               end
-       else
-               return nil, "Unexpected EOF"
-       end
-end
-
-
--- Find first MIME boundary
-process_states['mime-init'] = function( msg, chunk, filecb )
-
-       if chunk ~= nil then
-               if #chunk >= #msg.mime_boundary + 2 then
-                       local boundary = chunk:sub( 1, #msg.mime_boundary + 4 )
-
-                       if boundary == "--" .. msg.mime_boundary .. "\r\n" then
-
-                               -- Store remaining data in buffer
-                               msg._mimebuffer = chunk:sub( #msg.mime_boundary + 5, #chunk )
-
-                               -- Switch to header processing state
-                               return true, function( chunk )
-                                       return process_states['mime-headers']( msg, chunk, filecb )
-                               end
-                       else
-                               return nil, "Invalid MIME boundary"
-                       end
-               else
-                       return true
-               end
-       else
-               return nil, "Unexpected EOF"
-       end
-end
-
-
--- Read MIME part headers
-process_states['mime-headers'] = function( msg, chunk, filecb )
-
-       if chunk ~= nil then
-
-               -- Combine look-behind buffer with current chunk
-               chunk = msg._mimebuffer .. chunk
-
-               if not msg._mimeheaders then
-                       msg._mimeheaders = { }
-               end
-
-               local function __storehdr( k, v )
-                       msg._mimeheaders[k] = v
-                       return ""
-               end
-
-               -- Read all header lines
-               local ok, count = 1, 0
-               while ok > 0 do
-                       chunk, ok = chunk:gsub( "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n", __storehdr )
-                       count = count + ok
-               end
-
-               -- Headers processed, check for empty line
-               chunk, ok = chunk:gsub( "^\r\n", "" )
-
-               -- Store remaining buffer contents
-               msg._mimebuffer = chunk
-
-               -- End of headers
-               if ok > 0 then
-
-                       -- When no Content-Type header is given assume text/plain
-                       if not msg._mimeheaders['Content-Type'] then
-                               msg._mimeheaders['Content-Type'] = 'text/plain'
-                       end
-
-                       -- Check Content-Disposition
-                       if msg._mimeheaders['Content-Disposition'] then
-                               -- Check for "form-data" token
-                               if msg._mimeheaders['Content-Disposition']:match("^form%-data; ") then
-                                       -- Check for field name, filename
-                                       local field = msg._mimeheaders['Content-Disposition']:match('name="(.-)"')
-                                       local file  = msg._mimeheaders['Content-Disposition']:match('filename="(.+)"$')
-
-                                       -- Is a file field and we have a callback
-                                       if file and filecb then
-                                               msg.params[field] = file
-                                               msg._mimecallback = function(chunk,eof)
-                                                       filecb( {
-                                                               name    = field;
-                                                               file    = file;
-                                                               headers = msg._mimeheaders
-                                                       }, chunk, eof )
-                                               end
-
-                                       -- Treat as form field
-                                       else
-                                               msg.params[field] = ""
-                                               msg._mimecallback = function(chunk,eof)
-                                                       msg.params[field] = msg.params[field] .. chunk
-                                               end
-                                       end
-
-                                       -- Header was valid, continue with mime-data
-                                       return true, function( chunk )
-                                               return process_states['mime-data']( msg, chunk, filecb )
-                                       end
-                               else
-                                       -- Unknown Content-Disposition, abort
-                                       return nil, "Unexpected Content-Disposition MIME section header"
-                               end
-                       else
-                               -- Content-Disposition is required, abort without
-                               return nil, "Missing Content-Disposition MIME section header"
-                       end
-
-               -- We parsed no headers yet and buffer is almost empty
-               elseif count > 0 or #chunk < 128 then
-                       -- Keep feeding me with chunks
-                       return true, nil
-               end
-
-               -- Buffer looks like garbage
-               return nil, "Malformed MIME section header"
-       else
-               return nil, "Unexpected EOF"
-       end
-end
-
-
--- Read MIME part data
-process_states['mime-data'] = function( msg, chunk, filecb )
-
-       if chunk ~= nil then
-
-               -- Combine look-behind buffer with current chunk
-               local buffer = msg._mimebuffer .. chunk
-
-               -- Look for MIME boundary
-               local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
-
-               if spos then
-                       -- Content data
-                       msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
-
-                       -- Store remainder
-                       msg._mimebuffer = buffer:sub( epos + 1, #buffer )
-
-                       -- Next state is mime-header processing
-                       return true, function( chunk )
-                               return process_states['mime-headers']( msg, chunk, filecb )
-                       end
-               else
-                       -- Look for EOF?
-                       local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
-
-                       if spos then
-                               -- Content data
-                               msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
-
-                               -- We processed the final MIME boundary, cleanup
-                               msg._mimebuffer   = nil
-                               msg._mimeheaders  = nil
-                               msg._mimecallback = nil
-
-                               -- We won't accept data anymore
-                               return false
-                       else
-                               -- We're somewhere within a data section and our buffer is full
-                               if #buffer > #chunk then
-                                       -- Flush buffered data
-                                       msg._mimecallback( buffer:sub( 1, #buffer - #chunk ), false )
-
-                                       -- Store new data
-                                       msg._mimebuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
-
-                               -- Buffer is not full yet, append new data
-                               else
-                                       msg._mimebuffer = buffer
-                               end
-
-                               -- Keep feeding me
-                               return true
-                       end
-               end
-       else
-               return nil, "Unexpected EOF"
-       end
-end
-
-
--- Init urldecoding stream
-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.content_length <= HTTP_MAX_CONTENT then
-                               -- Initialize buffer
-                               msg._urldecbuffer = chunk
-                               msg._urldeclength = 0
-
-                               -- Switch to urldecode-key state
-                               return true, function(chunk)
-                                       return process_states['urldecode-key']( msg, chunk, filecb )
-                               end
-                       else
-                               return nil, "Request exceeds maximum allowed size"
-                       end
-               else
-                       return nil, "Missing Content-Length header"
-               end
-       else
-               return nil, "Unexpected EOF"
-       end
-end
-
-
--- Process urldecoding stream, read and validate parameter key
-process_states['urldecode-key'] = function( msg, chunk, filecb )
-
-       if chunk ~= nil then
-
-               -- Prevent oversized requests
-               if msg._urldeclength >= msg.content_length then
-                       return nil, "Request exceeds maximum allowed size"
-               end
-
-               -- Combine look-behind buffer with current chunk
-               local buffer = msg._urldecbuffer .. chunk
-               local spos, epos = buffer:find("=")
-
-               -- Found param
-               if spos then
-
-                       -- Check that key doesn't exceed maximum allowed key length
-                       if ( spos - 1 ) <= HTTP_URLENC_MAXKEYLEN then
-                               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 )
-
-                               -- Use file callback or store values inside msg.params
-                               if filecb then
-                                       msg._urldeccallback = function( chunk, eof )
-                                               filecb( field, chunk, eof )
-                                       end
-                               else
-                                       msg._urldeccallback = function( chunk, eof )
-                                               msg.params[key] = msg.params[key] .. chunk
-                                       end
-                               end
-
-                               -- Proceed with urldecode-value state
-                               return true, function( chunk )
-                                       return process_states['urldecode-value']( msg, chunk, filecb )
-                               end
-                       else
-                               return nil, "POST parameter exceeds maximum allowed length"
-                       end
-               else
-                       return nil, "POST data exceeds maximum allowed length"
-               end
-       else
-               return nil, "Unexpected EOF"
-       end
-end
-
-
--- Process urldecoding stream, read parameter value
-process_states['urldecode-value'] = function( msg, chunk, filecb )
-
-       if chunk ~= nil then
-
-               -- Combine look-behind buffer with current chunk
-               local buffer = msg._urldecbuffer .. chunk
-
-               -- Check for EOF
-               if #buffer == 0 then
-                       -- Compare processed length
-                       if msg._urldeclength == msg.content_length then
-                               -- Cleanup
-                               msg._urldeclength   = nil
-                               msg._urldecbuffer   = nil
-                               msg._urldeccallback = nil
-
-                               -- We won't accept data anymore
-                               return false
-                       else
-                               return nil, "Content-Length mismatch"
-                       end
-               end
-
-               -- Check for end of value
-               local spos, epos = buffer:find("[&;]")
-               if spos then
-
-                       -- Flush buffer, send eof
-                       msg._urldeccallback( buffer:sub( 1, spos - 1 ), true )
-                       msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
-                       msg._urldeclength = msg._urldeclength + epos
-
-                       -- Back to urldecode-key state
-                       return true, function( chunk )
-                               return process_states['urldecode-key']( msg, chunk, filecb )
-                       end
-               else
-                       -- We're somewhere within a data section and our buffer is full
-                       if #buffer > #chunk then
-                               -- Flush buffered data
-                               msg._urldeccallback( buffer:sub( 1, #buffer - #chunk ), false )
-
-                               -- Store new data
-                               msg._urldeclength = msg._urldeclength + #buffer - #chunk
-                               msg._urldecbuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
-
-                       -- Buffer is not full yet, append new data
-                       else
-                               msg._urldecbuffer = buffer
-                       end
-
-                       -- Keep feeding me
-                       return true
-               end
-       else
-               return nil, "Unexpected EOF"
-       end
-end
-
-
--- Decode MIME encoded data.
-function mimedecode_message_body( source, msg, filecb )
-
-       -- Find mime boundary
-       if msg and msg.headers['Content-Type'] then
-
-               local bound = msg.headers['Content-Type']:match("^multipart/form%-data; boundary=(.+)")
-
-               if bound then
-                       msg.mime_boundary = bound
-               else
-                       return nil, "No MIME boundary found or invalid content type given"
-               end
-       end
-
-       -- Create an initial LTN12 sink
-       -- The whole MIME parsing process is implemented as fancy sink, sinks replace themself
-       -- depending on current processing state (init, header, data). Return the initial state.
-       local sink = ltn12.sink.simplify(
-               function( chunk )
-                       return process_states['mime-init']( msg, chunk, filecb )
-               end
-       )
-
-       -- Create a throttling LTN12 source
-       -- Frequent state switching in the mime parsing process leads to unwanted buffer aggregation.
-       -- This source checks wheather there's still data in our internal read buffer and returns an
-       -- empty string if there's already enough data in the processing queue. If the internal buffer
-       -- runs empty we're calling the original source to get the next chunk of data.
-       local tsrc = function()
-
-               -- 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
-                       return ""
-               else
-                       return source()
-               end
-       end
-
-       -- Pump input data...
-       while true do
-               -- get data
-               local ok, err = ltn12.pump.step( tsrc, sink )
-
-               -- error
-               if not ok and err then
-                       return nil, err
-
-               -- eof
-               elseif not ok then
-                       return true
-               end
-       end
-end
-
-
--- Decode urlencoded data.
-function urldecode_message_body( source, msg )
-
-       -- Create an initial LTN12 sink
-       -- Return the initial state.
-       local sink = ltn12.sink.simplify(
-               function( chunk )
-                       return process_states['urldecode-init']( msg, chunk )
-               end
-       )
-
-       -- Create a throttling LTN12 source
-       -- See explaination in mimedecode_message_body().
-       local tsrc = function()
-               if msg._urldecbuffer ~= null and #msg._urldecbuffer > 0 then
-                       return ""
-               else
-                       return source()
-               end
-       end
-
-       -- Pump input data...
-       while true do
-               -- get data
-               local ok, err = ltn12.pump.step( tsrc, sink )
-
-               -- step
-               if not ok and err then
-                       return nil, err
-
-               -- eof
-               elseif not ok then
-                       return true
-               end
-       end
-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 )
-
-       local ok   = true
-       local msg  = { }
-
-       local sink = ltn12.sink.simplify(
-               function( chunk )
-                       return process_states['magic']( msg, chunk )
-               end
-       )
-
-       -- Pump input data...
-       while ok do
-
-               -- get data
-               ok, err = ltn12.pump.step( source, sink )
-
-               -- error
-               if not ok and err then
-                       return nil, err
-
-               -- eof
-               elseif not ok then
-
-                       -- Process get parameters
-                       if ( msg.request_method == "get" or msg.request_method == "post" ) and
-                          msg.request_uri:match("?")
-                       then
-                               msg.params = urldecode_params( msg.request_uri )
-                       else
-                               msg.params = { }
-                       end
-
-                       -- Populate common environment variables
-                       msg.env = {
-                               CONTENT_LENGTH    = msg.headers['Content-Length'];
-                               CONTENT_TYPE      = msg.headers['Content-Type'];
-                               REQUEST_METHOD    = msg.request_method:upper();
-                               REQUEST_URI       = msg.request_uri;
-                               SCRIPT_NAME       = msg.request_uri:gsub("?.+$","");
-                               SCRIPT_FILENAME   = ""          -- XXX implement me
-                       }
-
-                       -- Populate HTTP_* environment variables
-                       for i, hdr in ipairs( {
-                               'Accept',
-                               'Accept-Charset',
-                               'Accept-Encoding',
-                               'Accept-Language',
-                               'Connection',
-                               'Cookie',
-                               'Host',
-                               'Referer',
-                               'User-Agent',
-                       } ) do
-                               local var = 'HTTP_' .. hdr:upper():gsub("%-","_")
-                               local val = msg.headers[hdr]
-
-                               msg.env[var] = val
-                       end
-               end
-       end
-
-       return msg
-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")
-       then
-
-               return mimedecode_message_body( source, msg, filecb )
-
-       -- Is it application/x-www-form-urlencoded ?
-       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
-       -- 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
-                       sink = filecb
-
-               -- ... else append to .content
-               else
-                       msg.content = ""
-                       msg.content_length = 0
-
-                       sink = function( chunk )
-                               if ( msg.content_length ) + #chunk <= HTTP_MAX_CONTENT then
-
-                                       msg.content        = msg.content        .. chunk
-                                       msg.content_length = msg.content_length + #chunk
-
-                                       return true
-                               else
-                                       return nil, "POST data exceeds maximum allowed length"
-                               end
-                       end
-               end
-
-               -- Pump data...
-               while true do
-                       local ok, err = ltn12.pump.step( source, sink )
-
-                       if not ok and err then
-                               return nil, err
-                       elseif not err then
-                               return true
-                       end
-               end
-       end
-end