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
parent 5462:3ecae471d9dd
child 5475: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;