Mercurial > prosody-hg
comparison net/http/parser.lua @ 5463:111953bfe767
net.http.parser: Fix chunked encoding response parsing, and make it more robust
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Thu, 11 Apr 2013 20:01:03 +0100 |
| parents | 3ecae471d9dd |
| children | c2c9f07c5d6a |
comparison
equal
deleted
inserted
replaced
| 5462:3ecae471d9dd | 5463:111953bfe767 |
|---|---|
| 1 | |
| 2 local tonumber = tonumber; | 1 local tonumber = tonumber; |
| 3 local assert = assert; | 2 local assert = assert; |
| 4 local url_parse = require "socket.url".parse; | 3 local url_parse = require "socket.url".parse; |
| 5 local urldecode = require "util.http".urldecode; | 4 local urldecode = require "util.http".urldecode; |
| 6 | 5 |
| 27 | 26 |
| 28 function httpstream.new(success_cb, error_cb, parser_type, options_cb) | 27 function httpstream.new(success_cb, error_cb, parser_type, options_cb) |
| 29 local client = true; | 28 local client = true; |
| 30 if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end | 29 if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end |
| 31 local buf = ""; | 30 local buf = ""; |
| 32 local chunked; | 31 local chunked, chunk_size, chunk_start; |
| 33 local state = nil; | 32 local state = nil; |
| 34 local packet; | 33 local packet; |
| 35 local len; | 34 local len; |
| 36 local have_body; | 35 local have_body; |
| 37 local error; | 36 local error; |
| 69 if not status_code then error = true; return error_cb("invalid-status-line"); end | 68 if not status_code then error = true; return error_cb("invalid-status-line"); end |
| 70 have_body = not | 69 have_body = not |
| 71 ( (options_cb and options_cb().method == "HEAD") | 70 ( (options_cb and options_cb().method == "HEAD") |
| 72 or (status_code == 204 or status_code == 304 or status_code == 301) | 71 or (status_code == 204 or status_code == 304 or status_code == 301) |
| 73 or (status_code >= 100 and status_code < 200) ); | 72 or (status_code >= 100 and status_code < 200) ); |
| 74 chunked = have_body and headers["transfer-encoding"] == "chunked"; | |
| 75 else | 73 else |
| 76 method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$"); | 74 method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$"); |
| 77 if not method then error = true; return error_cb("invalid-status-line"); end | 75 if not method then error = true; return error_cb("invalid-status-line"); end |
| 78 end | 76 end |
| 79 end | 77 end |
| 80 end | 78 end |
| 81 if not first_line then error = true; return error_cb("invalid-status-line"); end | 79 if not first_line then error = true; return error_cb("invalid-status-line"); end |
| 80 chunked = have_body and headers["transfer-encoding"] == "chunked"; | |
| 82 len = tonumber(headers["content-length"]); -- TODO check for invalid len | 81 len = tonumber(headers["content-length"]); -- TODO check for invalid len |
| 83 if client then | 82 if client then |
| 84 -- FIXME handle '100 Continue' response (by skipping it) | 83 -- FIXME handle '100 Continue' response (by skipping it) |
| 85 if not have_body then len = 0; end | 84 if not have_body then len = 0; end |
| 86 packet = { | 85 packet = { |
| 119 state = true; | 118 state = true; |
| 120 end | 119 end |
| 121 if state then -- read body | 120 if state then -- read body |
| 122 if client then | 121 if client then |
| 123 if chunked then | 122 if chunked then |
| 124 local index = buf:find("\r\n", nil, true); | 123 if not buf:find("\r\n", nil, true) then |
| 125 if not index then return; end -- not enough data | 124 return; |
| 126 local chunk_size = buf:match("^%x+"); | 125 end -- not enough data |
| 127 if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end | 126 if not chunk_size then |
| 128 chunk_size = tonumber(chunk_size, 16); | 127 chunk_size, chunk_start = buf:match("^(%x+)[^\r\n]*\r\n()"); |
| 129 index = index + 2; | 128 chunk_size = chunk_size and tonumber(chunk_size, 16); |
| 130 if chunk_size == 0 then | 129 if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end |
| 131 state = nil; success_cb(packet); | |
| 132 elseif #buf - index + 1 >= chunk_size then -- we have a chunk | |
| 133 packet.body = packet.body..buf:sub(index, index + chunk_size - 1); | |
| 134 buf = buf:sub(index + chunk_size); | |
| 135 end | 130 end |
| 136 error("trailers"); -- FIXME MUST read trailers | 131 if chunk_size == 0 and buf:find("\r\n\r\n", chunk_start-2, true) then |
| 132 state, chunk_size = nil, nil; | |
| 133 buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped | |
| 134 success_cb(packet); | |
| 135 elseif #buf - chunk_start + 2 >= chunk_size then -- we have a chunk | |
| 136 packet.body = packet.body..buf:sub(chunk_start, chunk_start + chunk_size); | |
| 137 buf = buf:sub(chunk_start + chunk_size + 2); | |
| 138 chunk_size, chunk_start = nil, nil; | |
| 139 else -- Partial chunk remaining | |
| 140 break; | |
| 141 end | |
| 137 elseif len and #buf >= len then | 142 elseif len and #buf >= len then |
| 138 packet.body, buf = buf:sub(1, len), buf:sub(len + 1); | 143 packet.body, buf = buf:sub(1, len), buf:sub(len + 1); |
| 139 state = nil; success_cb(packet); | 144 state = nil; success_cb(packet); |
| 140 else | 145 else |
| 141 break; | 146 break; |
