X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fhttp%2Fluasrc%2Fhttp%2Fprotocol.lua;h=542c3147ea3f49570264e4e1d18cc36bf6af1157;hp=6e53d7ca187c15a3fe530a4aa211ee3b5f4897a2;hb=8d1aff78b17d6f7437d776bdf53a6aa2112f31db;hpb=65870edf9f1e579b4301e1677d70c9387a9a72dc diff --git a/libs/http/luasrc/http/protocol.lua b/libs/http/luasrc/http/protocol.lua index 6e53d7ca1..542c3147e 100644 --- a/libs/http/luasrc/http/protocol.lua +++ b/libs/http/luasrc/http/protocol.lua @@ -15,23 +15,27 @@ $Id$ module("luci.http.protocol", package.seeall) -require("ltn12") -require("luci.util") +local ltn12 = require("luci.ltn12") HTTP_MAX_CONTENT = 1024*4 -- 4 kB maximum content size HTTP_URLENC_MAXKEYLEN = 1024 -- maximum allowd size of urlencoded parameter names +TSRC_BLOCKSIZE = 2048 -- target block size for throttling sources -- Decode an urlencoded string. -- Returns the decoded value. -function urldecode( str ) +function urldecode( str, no_plus ) local function __chrdec( hex ) return string.char( tonumber( hex, 16 ) ) end if type(str) == "string" then - str = str:gsub( "+", " " ):gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec ) + if not no_plus then + str = str:gsub( "+", " " ) + end + + str = str:gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec ) end return str @@ -48,7 +52,7 @@ function urldecode_params( url, tbl ) url = url:gsub( "^.+%?([^?]+)", "%1" ) end - for i, pair in ipairs(luci.util.split( url, "[&;]+", nil, true )) do + for pair in url:gmatch( "[^&;]+" ) do -- find key and value local key = urldecode( pair:match("^([^=]+)") ) @@ -84,7 +88,7 @@ function urlencode( str ) if type(str) == "string" then str = str:gsub( - "([^a-zA-Z0-9$_%-%.+!*'(),])", + "([^a-zA-Z0-9$_%-%.%+!*'(),])", __chrenc ) end @@ -108,15 +112,49 @@ function urlencode_params( tbl ) end +-- Parameter helper +local function __initval( tbl, key ) + if tbl[key] == nil then + tbl[key] = "" + elseif type(tbl[key]) == "string" then + tbl[key] = { tbl[key], "" } + else + table.insert( tbl[key], "" ) + end +end + +local function __appendval( tbl, key, chunk ) + if type(tbl[key]) == "table" then + tbl[key][#tbl[key]] = tbl[key][#tbl[key]] .. chunk + else + tbl[key] = tbl[key] .. chunk + end +end + +local function __finishval( tbl, key, handler ) + if handler then + if type(tbl[key]) == "table" then + tbl[key][#tbl[key]] = handler( tbl[key][#tbl[key]] ) + else + tbl[key] = handler( tbl[key] ) + end + end +end + + -- Table of our process states local process_states = { } -- Extract "magic", the first line of a http message. -- Extracts the message type ("get", "post" or "response"), the requested uri -- or the status code if the line descripes a http response. -process_states['magic'] = function( msg, chunk ) +process_states['magic'] = function( msg, chunk, err ) if chunk ~= nil then + -- ignore empty lines before request + if #chunk == 0 then + return true, nil + end -- Is it a request? local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$") @@ -127,7 +165,7 @@ process_states['magic'] = function( msg, chunk ) msg.type = "request" msg.request_method = method:lower() msg.request_uri = uri - msg.http_version = http_ver + msg.http_version = tonumber( http_ver ) msg.headers = { } -- We're done, next state is header parsing @@ -146,7 +184,7 @@ process_states['magic'] = function( msg, chunk ) msg.type = "response" msg.status_code = code msg.status_message = message - msg.http_version = http_ver + msg.http_version = tonumber( http_ver ) msg.headers = { } -- We're done, next state is header parsing @@ -278,9 +316,10 @@ process_states['mime-headers'] = function( msg, chunk, filecb ) -- Treat as form field else - msg.params[field] = "" + __initval( msg.params, field ) + msg._mimecallback = function(chunk,eof) - msg.params[field] = msg.params[field] .. chunk + __appendval( msg.params, field, chunk ) end end @@ -423,7 +462,6 @@ process_states['urldecode-key'] = function( msg, chunk, filecb ) local key = urldecode( buffer:sub( 1, spos - 1 ) ) -- Prepare buffers - msg.params[key] = "" msg._urldeclength = msg._urldeclength + epos msg._urldecbuffer = buffer:sub( epos + 1, #buffer ) @@ -433,12 +471,14 @@ process_states['urldecode-key'] = function( msg, chunk, filecb ) filecb( field, chunk, eof ) end else + __initval( msg.params, key ) + msg._urldeccallback = function( chunk, eof ) - msg.params[key] = msg.params[key] .. chunk - + __appendval( msg.params, key, chunk ) + -- FIXME: Use a filter if eof then - msg.params[key] = urldecode( msg.params[key] ) + __finishval( msg.params, key, urldecode ) end end end @@ -515,11 +555,41 @@ process_states['urldecode-value'] = function( msg, chunk, filecb ) return true end else - return nil, "Unexpected EOF" + -- Send EOF + msg._urldeccallback( "", true ) + return false end end +-- Creates a header source from a given socket +function header_source( sock ) + return ltn12.source.simplify( function() + + local chunk, err, part = sock:receive("*l") + + -- Line too long + if chunk == nil then + if err ~= "timeout" then + return nil, part + and "Line exceeds maximum allowed length" + or "Unexpected EOF" + else + return nil, err + end + + -- Line ok + elseif chunk ~= nil then + + -- Strip trailing CR + chunk = chunk:gsub("\r$","") + + return chunk, nil + end + end ) +end + + -- Decode MIME encoded data. function mimedecode_message_body( source, msg, filecb ) @@ -553,7 +623,7 @@ function mimedecode_message_body( source, msg, filecb ) -- XXX: we schould propably keep the maximum buffer size in sync with -- the blocksize of our original source... but doesn't really matter - if msg._mimebuffer ~= null and #msg._mimebuffer > 256 then + if msg._mimebuffer ~= nil and #msg._mimebuffer > TSRC_BLOCKSIZE then return "" else return source() @@ -591,7 +661,7 @@ function urldecode_message_body( source, msg ) -- Create a throttling LTN12 source -- See explaination in mimedecode_message_body(). local tsrc = function() - if msg._urldecbuffer ~= null and #msg._urldecbuffer > 0 then + if msg._urldecbuffer ~= nil and #msg._urldecbuffer > 0 then return "" else return source() @@ -615,20 +685,6 @@ function urldecode_message_body( source, msg ) end --- Parse a http message -function parse_message( data, filecb ) - - local reader = _linereader( data, HTTP_MAX_READBUF ) - local message = parse_message_header( reader ) - - if message then - parse_message_body( reader, message, filecb ) - end - - return message -end - - -- Parse a http message header function parse_message_header( source ) @@ -671,7 +727,7 @@ function parse_message_header( source ) REQUEST_URI = msg.request_uri; SCRIPT_NAME = msg.request_uri:gsub("?.+$",""); SCRIPT_FILENAME = ""; -- XXX implement me - SERVER_PROTOCOL = "HTTP/" .. msg.http_version + SERVER_PROTOCOL = "HTTP/" .. string.format("%.1f", msg.http_version) } -- Populate HTTP_* environment variables @@ -700,7 +756,6 @@ end -- Parse a http message body function parse_message_body( source, msg, filecb ) - -- Is it multipart/mime ? if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and msg.env.CONTENT_TYPE:match("^multipart/form%-data") @@ -713,15 +768,14 @@ function parse_message_body( source, msg, filecb ) msg.env.CONTENT_TYPE == "application/x-www-form-urlencoded" then return urldecode_message_body( source, msg, filecb ) - + -- Unhandled encoding - -- If a file callback is given then feed it line by line, else + -- If a file callback is given then feed it chunk by chunk, else -- store whole buffer in message.content else local sink - local length = 0 -- If we have a file callback then feed it if type(filecb) == "function" then @@ -733,7 +787,7 @@ function parse_message_body( source, msg, filecb ) msg.content_length = 0 sink = function( chunk ) - if ( msg.content_length ) + #chunk <= HTTP_MAX_CONTENT then + if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then msg.content = msg.content .. chunk msg.content_length = msg.content_length + #chunk @@ -757,3 +811,18 @@ function parse_message_body( source, msg, filecb ) end end end + +-- Status codes +statusmsg = { + [200] = "OK", + [301] = "Moved Permanently", + [304] = "Not Modified", + [400] = "Bad Request", + [403] = "Forbidden", + [404] = "Not Found", + [405] = "Method Not Allowed", + [411] = "Length Required", + [412] = "Precondition Failed", + [500] = "Internal Server Error", + [503] = "Server Unavailable", +}