configmanager: nameprep VirtualHost and Component names
[prosody.git] / net / http / parser.lua
index 2d822598c9a7ccd4a759ab375519367d6161cf67..f9e6cea0607d1c74f3d26203fad8374370a344d1 100644 (file)
@@ -1,11 +1,10 @@
-
 local tonumber = tonumber;
 local assert = assert;
 local url_parse = require "socket.url".parse;
-local urldecode = require "net.http".urldecode;
+local urldecode = require "util.http".urldecode;
 
 local function preprocess_path(path)
-       path = urldecode(path);
+       path = urldecode((path:gsub("//+", "/")));
        if path:sub(1,1) ~= "/" then
                path = "/"..path;
        end
@@ -29,7 +28,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb)
        local client = true;
        if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end
        local buf = "";
-       local chunked;
+       local chunked, chunk_size, chunk_start;
        local state = nil;
        local packet;
        local len;
@@ -65,18 +64,20 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb)
                                                        first_line = line;
                                                        if client then
                                                                httpversion, status_code, reason_phrase = line:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$");
+                                                               status_code = tonumber(status_code);
                                                                if not status_code then error = true; return error_cb("invalid-status-line"); end
                                                                have_body = not
                                                                         ( (options_cb and options_cb().method == "HEAD")
                                                                        or (status_code == 204 or status_code == 304 or status_code == 301)
                                                                        or (status_code >= 100 and status_code < 200) );
-                                                               chunked = have_body and headers["transfer-encoding"] == "chunked";
                                                        else
                                                                method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$");
                                                                if not method then error = true; return error_cb("invalid-status-line"); end
                                                        end
                                                end
                                        end
+                                       if not first_line then error = true; return error_cb("invalid-status-line"); end
+                                       chunked = have_body and headers["transfer-encoding"] == "chunked";
                                        len = tonumber(headers["content-length"]); -- TODO check for invalid len
                                        if client then
                                                -- FIXME handle '100 Continue' response (by skipping it)
@@ -91,7 +92,15 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb)
                                                        responseheaders = headers;
                                                };
                                        else
-                                               local parsed_url = url_parse(path);
+                                               local parsed_url;
+                                               if path:byte() == 47 then -- starts with /
+                                                       local _path, _query = path:match("([^?]*).?(.*)");
+                                                       if _query == "" then _query = nil; end
+                                                       parsed_url = { path = _path, query = _query };
+                                               else
+                                                       parsed_url = url_parse(path);
+                                                       if not(parsed_url and parsed_url.path) then error = true; return error_cb("invalid-url"); end
+                                               end
                                                path = preprocess_path(parsed_url.path);
                                                headers.host = parsed_url.host or headers.host;
 
@@ -111,26 +120,36 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb)
                                if state then -- read body
                                        if client then
                                                if chunked then
-                                                       local index = buf:find("\r\n", nil, true);
-                                                       if not index then return; end -- not enough data
-                                                       local chunk_size = buf:match("^%x+");
-                                                       if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end
-                                                       chunk_size = tonumber(chunk_size, 16);
-                                                       index = index + 2;
-                                                       if chunk_size == 0 then
-                                                               state = nil; success_cb(packet);
-                                                       elseif #buf - index + 1 >= chunk_size then -- we have a chunk
-                                                               packet.body = packet.body..buf:sub(index, index + chunk_size - 1);
-                                                               buf = buf:sub(index + chunk_size);
+                                                       if not buf:find("\r\n", nil, true) then
+                                                               return;
+                                                       end -- not enough data
+                                                       if not chunk_size then
+                                                               chunk_size, chunk_start = buf:match("^(%x+)[^\r\n]*\r\n()");
+                                                               chunk_size = chunk_size and tonumber(chunk_size, 16);
+                                                               if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end
+                                                       end
+                                                       if chunk_size == 0 and buf:find("\r\n\r\n", chunk_start-2, true) then
+                                                               state, chunk_size = nil, nil;
+                                                               buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped
+                                                               success_cb(packet);
+                                                       elseif #buf - chunk_start + 2 >= chunk_size then -- we have a chunk
+                                                               packet.body = packet.body..buf:sub(chunk_start, chunk_start + (chunk_size-1));
+                                                               buf = buf:sub(chunk_start + chunk_size + 2);
+                                                               chunk_size, chunk_start = nil, nil;
+                                                       else -- Partial chunk remaining
+                                                               break;
                                                        end
-                                                       error("trailers"); -- FIXME MUST read trailers
                                                elseif len and #buf >= len then
                                                        packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
                                                        state = nil; success_cb(packet);
+                                               else
+                                                       break;
                                                end
                                        elseif #buf >= len then
                                                packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
                                                state = nil; success_cb(packet);
+                                       else
+                                               break;
                                        end
                                end
                        end