|
1 local tonumber = tonumber; |
|
2 local assert = assert; |
|
3 local t_insert, t_concat = table.insert, table.concat; |
|
4 local url_parse = require "socket.url".parse; |
|
5 local urldecode = require "util.http".urldecode; |
|
6 |
|
7 local function preprocess_path(path) |
|
8 path = urldecode((path:gsub("//+", "/"))); |
|
9 if path:sub(1,1) ~= "/" then |
|
10 path = "/"..path; |
|
11 end |
|
12 local level = 0; |
|
13 for component in path:gmatch("([^/]+)/") do |
|
14 if component == ".." then |
|
15 level = level - 1; |
|
16 elseif component ~= "." then |
|
17 level = level + 1; |
|
18 end |
|
19 if level < 0 then |
|
20 return nil; |
|
21 end |
|
22 end |
|
23 return path; |
|
24 end |
|
25 |
|
26 local httpstream = {}; |
|
27 |
|
28 function httpstream.new(success_cb, error_cb, parser_type, options_cb) |
|
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 |
|
31 local buf, buflen, buftable = {}, 0, true; |
|
32 local bodylimit = tonumber(options_cb and options_cb().body_size_limit) or 10*1024*1024; |
|
33 local buflimit = tonumber(options_cb and options_cb().buffer_size_limit) or bodylimit * 2; |
|
34 local chunked, chunk_size, chunk_start; |
|
35 local state = nil; |
|
36 local packet; |
|
37 local len; |
|
38 local have_body; |
|
39 local error; |
|
40 return { |
|
41 feed = function(_, data) |
|
42 if error then return nil, "parse has failed"; end |
|
43 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 |
|
46 packet.body = buf; |
|
47 success_cb(packet); |
|
48 elseif buf ~= "" then -- unexpected EOF |
|
49 error = true; return error_cb("unexpected-eof"); |
|
50 end |
|
51 return; |
|
52 end |
|
53 if buftable then |
|
54 t_insert(buf, data); |
|
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 |
|
63 if buftable then buf, buftable = t_concat(buf), false; end |
|
64 local index = buf:find("\r\n\r\n", nil, true); |
|
65 if not index then return; end -- not enough data |
|
66 local method, path, httpversion, status_code, reason_phrase; |
|
67 local first_line; |
|
68 local headers = {}; |
|
69 for line in buf:sub(1,index+1):gmatch("([^\r\n]+)\r\n") do -- parse request |
|
70 if first_line then |
|
71 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 |
|
73 key = key:lower(); |
|
74 headers[key] = headers[key] and headers[key]..","..val or val; |
|
75 else |
|
76 first_line = line; |
|
77 if client then |
|
78 httpversion, status_code, reason_phrase = line:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$"); |
|
79 status_code = tonumber(status_code); |
|
80 if not status_code then error = true; return error_cb("invalid-status-line"); end |
|
81 have_body = not |
|
82 ( (options_cb and options_cb().method == "HEAD") |
|
83 or (status_code == 204 or status_code == 304 or status_code == 301) |
|
84 or (status_code >= 100 and status_code < 200) ); |
|
85 else |
|
86 method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$"); |
|
87 if not method then error = true; return error_cb("invalid-status-line"); end |
|
88 end |
|
89 end |
|
90 end |
|
91 if not first_line then error = true; return error_cb("invalid-status-line"); end |
|
92 chunked = have_body and headers["transfer-encoding"] == "chunked"; |
|
93 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 |
|
96 -- FIXME handle '100 Continue' response (by skipping it) |
|
97 if not have_body then len = 0; end |
|
98 packet = { |
|
99 code = status_code; |
|
100 httpversion = httpversion; |
|
101 headers = headers; |
|
102 body = have_body and "" or nil; |
|
103 -- COMPAT the properties below are deprecated |
|
104 responseversion = httpversion; |
|
105 responseheaders = headers; |
|
106 }; |
|
107 else |
|
108 local parsed_url; |
|
109 if path:byte() == 47 then -- starts with / |
|
110 local _path, _query = path:match("([^?]*).?(.*)"); |
|
111 if _query == "" then _query = nil; end |
|
112 parsed_url = { path = _path, query = _query }; |
|
113 else |
|
114 parsed_url = url_parse(path); |
|
115 if not(parsed_url and parsed_url.path) then error = true; return error_cb("invalid-url"); end |
|
116 end |
|
117 path = preprocess_path(parsed_url.path); |
|
118 headers.host = parsed_url.host or headers.host; |
|
119 |
|
120 len = len or 0; |
|
121 packet = { |
|
122 method = method; |
|
123 url = parsed_url; |
|
124 path = path; |
|
125 httpversion = httpversion; |
|
126 headers = headers; |
|
127 body = nil; |
|
128 }; |
|
129 end |
|
130 buf = buf:sub(index + 4); |
|
131 buflen = #buf; |
|
132 state = true; |
|
133 end |
|
134 if state then -- read body |
|
135 if client then |
|
136 if chunked then |
|
137 if chunk_start and buflen - chunk_start - 2 < chunk_size then |
|
138 return; |
|
139 end -- not enough data |
|
140 if buftable then buf, buftable = t_concat(buf), false; end |
|
141 if not buf:find("\r\n", nil, true) then |
|
142 return; |
|
143 end -- not enough data |
|
144 if not chunk_size then |
|
145 chunk_size, chunk_start = buf:match("^(%x+)[^\r\n]*\r\n()"); |
|
146 chunk_size = chunk_size and tonumber(chunk_size, 16); |
|
147 if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end |
|
148 end |
|
149 if chunk_size == 0 and buf:find("\r\n\r\n", chunk_start-2, true) then |
|
150 state, chunk_size = nil, nil; |
|
151 buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped |
|
152 success_cb(packet); |
|
153 elseif buflen - chunk_start - 2 >= chunk_size then -- we have a chunk |
|
154 packet.body = packet.body..buf:sub(chunk_start, chunk_start + (chunk_size-1)); |
|
155 buf = buf:sub(chunk_start + chunk_size + 2); |
|
156 buflen = buflen - (chunk_start + chunk_size + 2 - 1); |
|
157 chunk_size, chunk_start = nil, nil; |
|
158 else -- Partial chunk remaining |
|
159 break; |
|
160 end |
|
161 elseif len and buflen >= len then |
|
162 if buftable then buf, buftable = t_concat(buf), false; end |
|
163 if packet.code == 101 then |
|
164 packet.body, buf, buflen, buftable = buf, {}, 0, true; |
|
165 else |
|
166 packet.body, buf = buf:sub(1, len), buf:sub(len + 1); |
|
167 buflen = #buf; |
|
168 end |
|
169 state = nil; success_cb(packet); |
|
170 else |
|
171 break; |
|
172 end |
|
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 |
|
179 break; |
|
180 end |
|
181 end |
|
182 end |
|
183 end; |
|
184 }; |
|
185 end |
|
186 |
|
187 return httpstream; |