* luci/libs: add support for chunked transfer decoding in http.protocol
authorJo-Philipp Wich <jow@openwrt.org>
Sun, 22 Jun 2008 12:07:37 +0000 (12:07 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Sun, 22 Jun 2008 12:07:37 +0000 (12:07 +0000)
libs/http/luasrc/http/protocol.lua
libs/http/luasrc/http/protocol/filter.lua [new file with mode: 0644]

index 6e53d7c..08e8ba2 100644 (file)
@@ -17,6 +17,7 @@ module("luci.http.protocol", package.seeall)
 
 require("ltn12")
 require("luci.util")
+require("luci.http.protocol.filter")
 
 HTTP_MAX_CONTENT      = 1024*4         -- 4 kB maximum content size
 HTTP_URLENC_MAXKEYLEN = 1024           -- maximum allowd size of urlencoded parameter names
@@ -127,7 +128,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 +147,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
@@ -435,7 +436,7 @@ process_states['urldecode-key'] = function( msg, chunk, filecb )
                                else
                                        msg._urldeccallback = function( chunk, eof )
                                                msg.params[key] = msg.params[key] .. chunk
-                                               
+
                                                -- FIXME: Use a filter
                                                if eof then
                                                        msg.params[key] = urldecode( msg.params[key] )
@@ -701,6 +702,18 @@ end
 -- Parse a http message body
 function parse_message_body( source, msg, filecb )
 
+       -- Install an additional filter if we're operating on chunked transfer
+       -- coding and client is HTTP/1.1 capable
+       if msg.http_version == 1.1 and
+          msg.headers['Transfer-Encoding'] and
+          msg.headers['Transfer-Encoding']:find("chunked")
+       then
+               source = ltn12.source.chain(
+                       source, luci.http.protocol.filter.decode_chunked
+               )
+       end
+
+
        -- Is it multipart/mime ?
        if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
           msg.env.CONTENT_TYPE:match("^multipart/form%-data")
@@ -713,7 +726,7 @@ 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
@@ -721,7 +734,6 @@ function parse_message_body( source, msg, filecb )
        else
 
                local sink
-               local length = 0
 
                -- If we have a file callback then feed it
                if type(filecb) == "function" then
@@ -733,7 +745,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
diff --git a/libs/http/luasrc/http/protocol/filter.lua b/libs/http/luasrc/http/protocol/filter.lua
new file mode 100644 (file)
index 0000000..71263c3
--- /dev/null
@@ -0,0 +1,81 @@
+--[[
+
+HTTP protocol implementation for LuCI - filter implementation
+(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.filter", package.seeall)
+
+require("ltn12")
+
+
+-- Factory that produces a filter which normalizes chunked transfer encoding
+function decode_chunked()
+
+       local length = 0
+       local read   = 0
+
+       return ltn12.filter.cycle(
+               function( chunk, ctx )
+
+                       if chunk ~= nil then
+
+                               -- EOF
+                               if ctx == nil then
+                                       if ( length - read ) > 0 then
+                                               return nil, "Unexpected EOF"
+                                       else
+                                               return ""
+                                       end
+                               end
+
+                               chunk = ctx .. chunk
+
+                               local buf = ""
+                               while true do
+
+                                       if read == length then
+
+                                               -- Find chunk length indicator
+                                               local spos, epos = chunk:find("^\r?\n?[a-fA-F0-9]+ *\r\n")
+                                               if spos and spos == 1 then
+                                                       read   = 0
+                                                       length = tonumber(
+                                                               chunk:sub( 1, epos ):gsub( "[^a-fA-F0-9]", "" ), 16
+                                                       )
+
+                                                       -- Check for end of chunk
+                                                       if length > 0 then
+                                                               chunk = chunk:sub( epos + 1, #chunk )
+                                                       else
+                                                               return buf, ""
+                                                       end
+                                               else
+                                                       return "", nil
+                                               end
+                                       else
+                                               if ( read + #chunk ) <= length then
+                                                       read = read + #chunk
+                                                       return buf .. chunk, ""
+                                               else
+                                                       local rest = length - read
+                                                       read  = read + rest
+                                                       buf   = buf .. chunk:sub( 1, rest )
+                                                       chunk = chunk:sub( rest + 1, #chunk )
+                                               end
+                                       end
+                               end
+                       end
+               end,
+               ""
+       )
+end