comparison net/http/parser.lua @ 11200:bf8f2da84007

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Thu, 05 Nov 2020 22:31:25 +0100
parents 2ede7f43ccfe
children f3aee8a825cc
comparison
equal deleted inserted replaced
11199:6c7c50a4de32 11200:bf8f2da84007
1 local tonumber = tonumber; 1 local tonumber = tonumber;
2 local assert = assert; 2 local assert = assert;
3 local t_insert, t_concat = table.insert, table.concat;
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;
5 local dbuffer = require "util.dbuffer";
6 6
7 local function preprocess_path(path) 7 local function preprocess_path(path)
8 path = urldecode((path:gsub("//+", "/"))); 8 path = urldecode((path:gsub("//+", "/")));
9 if path:sub(1,1) ~= "/" then 9 if path:sub(1,1) ~= "/" then
10 path = "/"..path; 10 path = "/"..path;
26 local httpstream = {}; 26 local httpstream = {};
27 27
28 function httpstream.new(success_cb, error_cb, parser_type, options_cb) 28 function httpstream.new(success_cb, error_cb, parser_type, options_cb)
29 local client = true; 29 local client = true;
30 if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end 30 if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end
31 local buf, buflen, buftable = {}, 0, true;
32 local bodylimit = tonumber(options_cb and options_cb().body_size_limit) or 10*1024*1024; 31 local bodylimit = tonumber(options_cb and options_cb().body_size_limit) or 10*1024*1024;
32 -- https://stackoverflow.com/a/686243
33 -- Indiviual headers can be up to 16k? What madness?
34 local headlimit = tonumber(options_cb and options_cb().head_size_limit) or 10*1024;
33 local buflimit = tonumber(options_cb and options_cb().buffer_size_limit) or bodylimit * 2; 35 local buflimit = tonumber(options_cb and options_cb().buffer_size_limit) or bodylimit * 2;
34 local chunked, chunk_size, chunk_start; 36 local buffer = dbuffer.new(buflimit);
37 local chunked;
35 local state = nil; 38 local state = nil;
36 local packet; 39 local packet;
37 local len; 40 local len;
38 local have_body; 41 local have_body;
39 local error; 42 local error;
40 return { 43 return {
41 feed = function(_, data) 44 feed = function(_, data)
42 if error then return nil, "parse has failed"; end 45 if error then return nil, "parse has failed"; end
43 if not data then -- EOF 46 if not data then -- EOF
44 if buftable then buf, buftable = t_concat(buf), false; end
45 if state and client and not len then -- reading client body until EOF 47 if state and client and not len then -- reading client body until EOF
46 packet.body = buf; 48 buffer:collapse();
49 packet.body = buffer:read_chunk() or "";
50 packet.partial = nil;
47 success_cb(packet); 51 success_cb(packet);
48 elseif buf ~= "" then -- unexpected EOF 52 state = nil;
53 elseif buffer:length() ~= 0 then -- unexpected EOF
49 error = true; return error_cb("unexpected-eof"); 54 error = true; return error_cb("unexpected-eof");
50 end 55 end
51 return; 56 return;
52 end 57 end
53 if buftable then 58 if not buffer:write(data) then error = true; return error_cb("max-buffer-size-exceeded"); end
54 t_insert(buf, data); 59 while buffer:length() > 0 do
55 else
56 buf = { buf, data };
57 buftable = true;
58 end
59 buflen = buflen + #data;
60 if buflen > buflimit then error = true; return error_cb("max-buffer-size-exceeded"); end
61 while buflen > 0 do
62 if state == nil then -- read request 60 if state == nil then -- read request
63 if buftable then buf, buftable = t_concat(buf), false; end 61 local index = buffer:sub(1, headlimit):find("\r\n\r\n", nil, true);
64 local index = buf:find("\r\n\r\n", nil, true);
65 if not index then return; end -- not enough data 62 if not index then return; end -- not enough data
66 local method, path, httpversion, status_code, reason_phrase; 63 -- FIXME was reason_phrase meant to be passed on somewhere?
64 local method, path, httpversion, status_code, reason_phrase; -- luacheck: ignore reason_phrase
67 local first_line; 65 local first_line;
68 local headers = {}; 66 local headers = {};
69 for line in buf:sub(1,index+1):gmatch("([^\r\n]+)\r\n") do -- parse request 67 for line in buffer:read(index+3):gmatch("([^\r\n]+)\r\n") do -- parse request
70 if first_line then 68 if first_line then
71 local key, val = line:match("^([^%s:]+): *(.*)$"); 69 local key, val = line:match("^([^%s:]+): *(.*)$");
72 if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers 70 if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers
73 key = key:lower(); 71 key = key:lower();
74 headers[key] = headers[key] and headers[key]..","..val or val; 72 headers[key] = headers[key] and headers[key]..","..val or val;
89 end 87 end
90 end 88 end
91 if not first_line then error = true; return error_cb("invalid-status-line"); end 89 if not first_line then error = true; return error_cb("invalid-status-line"); end
92 chunked = have_body and headers["transfer-encoding"] == "chunked"; 90 chunked = have_body and headers["transfer-encoding"] == "chunked";
93 len = tonumber(headers["content-length"]); -- TODO check for invalid len 91 len = tonumber(headers["content-length"]); -- TODO check for invalid len
94 if len and len > bodylimit then error = true; return error_cb("content-length-limit-exceeded"); end
95 if client then 92 if client then
96 -- FIXME handle '100 Continue' response (by skipping it) 93 -- FIXME handle '100 Continue' response (by skipping it)
97 if not have_body then len = 0; end 94 if not have_body then len = 0; end
98 packet = { 95 packet = {
99 code = status_code; 96 code = status_code;
100 httpversion = httpversion; 97 httpversion = httpversion;
101 headers = headers; 98 headers = headers;
102 body = have_body and "" or nil; 99 body = false;
100 body_length = len;
101 chunked = chunked;
102 partial = true;
103 -- COMPAT the properties below are deprecated 103 -- COMPAT the properties below are deprecated
104 responseversion = httpversion; 104 responseversion = httpversion;
105 responseheaders = headers; 105 responseheaders = headers;
106 }; 106 };
107 else 107 else
122 method = method; 122 method = method;
123 url = parsed_url; 123 url = parsed_url;
124 path = path; 124 path = path;
125 httpversion = httpversion; 125 httpversion = httpversion;
126 headers = headers; 126 headers = headers;
127 body = nil; 127 body = false;
128 body_sink = nil;
129 chunked = chunked;
130 partial = true;
128 }; 131 };
129 end 132 end
130 buf = buf:sub(index + 4); 133 if len and len > bodylimit then
131 buflen = #buf; 134 -- Early notification, for redirection
135 success_cb(packet);
136 if not packet.body_sink then error = true; return error_cb("content-length-limit-exceeded"); end
137 end
138 if chunked and not packet.body_sink then
139 success_cb(packet);
140 if not packet.body_sink then
141 packet.body_buffer = dbuffer.new(buflimit);
142 end
143 end
132 state = true; 144 state = true;
133 end 145 end
134 if state then -- read body 146 if state then -- read body
135 if client then 147 if chunked then
136 if chunked then 148 local chunk_header = buffer:sub(1, 512); -- XXX How large do chunk headers grow?
137 if chunk_start and buflen - chunk_start - 2 < chunk_size then 149 local chunk_size, chunk_start = chunk_header:match("^(%x+)[^\r\n]*\r\n()");
138 return; 150 if not chunk_size then return; end
139 end -- not enough data 151 chunk_size = chunk_size and tonumber(chunk_size, 16);
140 if buftable then buf, buftable = t_concat(buf), false; end 152 if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end
141 if not buf:find("\r\n", nil, true) then 153 if chunk_size == 0 and chunk_header:find("\r\n\r\n", chunk_start-2, true) then
142 return; 154 local body_buffer = packet.body_buffer;
143 end -- not enough data 155 if body_buffer then
144 if not chunk_size then 156 packet.body_buffer = nil;
145 chunk_size, chunk_start = buf:match("^(%x+)[^\r\n]*\r\n()"); 157 body_buffer:collapse();
146 chunk_size = chunk_size and tonumber(chunk_size, 16); 158 packet.body = body_buffer:read_chunk() or "";
147 if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end
148 end 159 end
149 if chunk_size == 0 and buf:find("\r\n\r\n", chunk_start-2, true) then 160
150 state, chunk_size = nil, nil; 161 buffer:collapse();
151 buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped 162 local buf = buffer:read_chunk();
152 success_cb(packet); 163 buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped
153 elseif buflen - chunk_start - 2 >= chunk_size then -- we have a chunk 164 buffer:write(buf);
154 packet.body = packet.body..buf:sub(chunk_start, chunk_start + (chunk_size-1)); 165 state, chunked = nil, nil;
155 buf = buf:sub(chunk_start + chunk_size + 2); 166 packet.partial = nil;
156 buflen = buflen - (chunk_start + chunk_size + 2 - 1); 167 success_cb(packet);
157 chunk_size, chunk_start = nil, nil; 168 elseif buffer:length() - chunk_start - 2 >= chunk_size then -- we have a chunk
158 else -- Partial chunk remaining 169 buffer:discard(chunk_start - 1); -- TODO verify that it's not off-by-one
159 break; 170 (packet.body_sink or packet.body_buffer):write(buffer:read(chunk_size));
171 buffer:discard(2); -- CRLF
172 else -- Partial chunk remaining
173 break;
174 end
175 elseif packet.body_sink then
176 local chunk = buffer:read_chunk(len);
177 while chunk and len > 0 do
178 if packet.body_sink:write(chunk) then
179 len = len - #chunk;
180 chunk = buffer:read_chunk(len);
181 else
182 error = true;
183 return error_cb("body-sink-write-failure");
160 end 184 end
161 elseif len and buflen >= len then 185 end
162 if buftable then buf, buftable = t_concat(buf), false; end 186 if len == 0 then
163 if packet.code == 101 then 187 state = nil;
164 packet.body, buf, buflen, buftable = buf, {}, 0, true; 188 packet.partial = nil;
165 else 189 success_cb(packet);
166 packet.body, buf = buf:sub(1, len), buf:sub(len + 1); 190 end
167 buflen = #buf; 191 elseif buffer:length() >= len then
168 end 192 assert(not chunked)
169 state = nil; success_cb(packet); 193 packet.body = buffer:read(len) or "";
170 else 194 state = nil;
171 break; 195 packet.partial = nil;
172 end 196 success_cb(packet);
173 elseif buflen >= len then
174 if buftable then buf, buftable = t_concat(buf), false; end
175 packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
176 buflen = #buf;
177 state = nil; success_cb(packet);
178 else 197 else
179 break; 198 break;
180 end 199 end
200 else
201 break;
181 end 202 end
182 end 203 end
183 end; 204 end;
184 }; 205 };
185 end 206 end