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
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
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
$(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))
--- /dev/null
+--[[
+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
+
--- /dev/null
+include ../../build/config.mk
+include ../../build/module.mk
--- /dev/null
+--[[
+
+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
-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
+++ /dev/null
---[[
-
-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