Mercurial > prosody-hg
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 |
