Show More
@@ -305,6 +305,7 b' class hgweb(object):' | |||
|
305 | 305 | |
|
306 | 306 | def _runwsgi(self, wsgireq, repo): |
|
307 | 307 | req = wsgireq.req |
|
308 | res = wsgireq.res | |
|
308 | 309 | rctx = requestcontext(self, repo) |
|
309 | 310 | |
|
310 | 311 | # This state is global across all threads. |
@@ -317,11 +318,12 b' class hgweb(object):' | |||
|
317 | 318 | wsgireq.headers = [h for h in wsgireq.headers |
|
318 | 319 | if h[0] != 'Content-Security-Policy'] |
|
319 | 320 | wsgireq.headers.append(('Content-Security-Policy', rctx.csp)) |
|
321 | res.headers['Content-Security-Policy'] = rctx.csp | |
|
320 | 322 | |
|
321 |
handled |
|
|
322 | rctx, wsgireq, req, self.check_perm) | |
|
323 | handled = wireprotoserver.handlewsgirequest( | |
|
324 | rctx, wsgireq, req, res, self.check_perm) | |
|
323 | 325 | if handled: |
|
324 | return res | |
|
326 | return res.sendresponse() | |
|
325 | 327 | |
|
326 | 328 | if req.havepathinfo: |
|
327 | 329 | query = req.dispatchpath |
@@ -23,6 +23,7 b' from ..thirdparty import (' | |||
|
23 | 23 | attr, |
|
24 | 24 | ) |
|
25 | 25 | from .. import ( |
|
26 | error, | |
|
26 | 27 | pycompat, |
|
27 | 28 | util, |
|
28 | 29 | ) |
@@ -201,6 +202,128 b' def parserequestfromenv(env, bodyfh):' | |||
|
201 | 202 | headers=headers, |
|
202 | 203 | bodyfh=bodyfh) |
|
203 | 204 | |
|
205 | class wsgiresponse(object): | |
|
206 | """Represents a response to a WSGI request. | |
|
207 | ||
|
208 | A response consists of a status line, headers, and a body. | |
|
209 | ||
|
210 | Consumers must populate the ``status`` and ``headers`` fields and | |
|
211 | make a call to a ``setbody*()`` method before the response can be | |
|
212 | issued. | |
|
213 | ||
|
214 | When it is time to start sending the response over the wire, | |
|
215 | ``sendresponse()`` is called. It handles emitting the header portion | |
|
216 | of the response message. It then yields chunks of body data to be | |
|
217 | written to the peer. Typically, the WSGI application itself calls | |
|
218 | and returns the value from ``sendresponse()``. | |
|
219 | """ | |
|
220 | ||
|
221 | def __init__(self, req, startresponse): | |
|
222 | """Create an empty response tied to a specific request. | |
|
223 | ||
|
224 | ``req`` is a ``parsedrequest``. ``startresponse`` is the | |
|
225 | ``start_response`` function passed to the WSGI application. | |
|
226 | """ | |
|
227 | self._req = req | |
|
228 | self._startresponse = startresponse | |
|
229 | ||
|
230 | self.status = None | |
|
231 | self.headers = wsgiheaders.Headers([]) | |
|
232 | ||
|
233 | self._bodybytes = None | |
|
234 | self._bodygen = None | |
|
235 | self._started = False | |
|
236 | ||
|
237 | def setbodybytes(self, b): | |
|
238 | """Define the response body as static bytes.""" | |
|
239 | if self._bodybytes is not None or self._bodygen is not None: | |
|
240 | raise error.ProgrammingError('cannot define body multiple times') | |
|
241 | ||
|
242 | self._bodybytes = b | |
|
243 | self.headers['Content-Length'] = '%d' % len(b) | |
|
244 | ||
|
245 | def setbodygen(self, gen): | |
|
246 | """Define the response body as a generator of bytes.""" | |
|
247 | if self._bodybytes is not None or self._bodygen is not None: | |
|
248 | raise error.ProgrammingError('cannot define body multiple times') | |
|
249 | ||
|
250 | self._bodygen = gen | |
|
251 | ||
|
252 | def sendresponse(self): | |
|
253 | """Send the generated response to the client. | |
|
254 | ||
|
255 | Before this is called, ``status`` must be set and one of | |
|
256 | ``setbodybytes()`` or ``setbodygen()`` must be called. | |
|
257 | ||
|
258 | Calling this method multiple times is not allowed. | |
|
259 | """ | |
|
260 | if self._started: | |
|
261 | raise error.ProgrammingError('sendresponse() called multiple times') | |
|
262 | ||
|
263 | self._started = True | |
|
264 | ||
|
265 | if not self.status: | |
|
266 | raise error.ProgrammingError('status line not defined') | |
|
267 | ||
|
268 | if self._bodybytes is None and self._bodygen is None: | |
|
269 | raise error.ProgrammingError('response body not defined') | |
|
270 | ||
|
271 | # Various HTTP clients (notably httplib) won't read the HTTP response | |
|
272 | # until the HTTP request has been sent in full. If servers (us) send a | |
|
273 | # response before the HTTP request has been fully sent, the connection | |
|
274 | # may deadlock because neither end is reading. | |
|
275 | # | |
|
276 | # We work around this by "draining" the request data before | |
|
277 | # sending any response in some conditions. | |
|
278 | drain = False | |
|
279 | close = False | |
|
280 | ||
|
281 | # If the client sent Expect: 100-continue, we assume it is smart enough | |
|
282 | # to deal with the server sending a response before reading the request. | |
|
283 | # (httplib doesn't do this.) | |
|
284 | if self._req.headers.get('Expect', '').lower() == '100-continue': | |
|
285 | pass | |
|
286 | # Only tend to request methods that have bodies. Strictly speaking, | |
|
287 | # we should sniff for a body. But this is fine for our existing | |
|
288 | # WSGI applications. | |
|
289 | elif self._req.method not in ('POST', 'PUT'): | |
|
290 | pass | |
|
291 | else: | |
|
292 | # If we don't know how much data to read, there's no guarantee | |
|
293 | # that we can drain the request responsibly. The WSGI | |
|
294 | # specification only says that servers *should* ensure the | |
|
295 | # input stream doesn't overrun the actual request. So there's | |
|
296 | # no guarantee that reading until EOF won't corrupt the stream | |
|
297 | # state. | |
|
298 | if not isinstance(self._req.bodyfh, util.cappedreader): | |
|
299 | close = True | |
|
300 | else: | |
|
301 | # We /could/ only drain certain HTTP response codes. But 200 and | |
|
302 | # non-200 wire protocol responses both require draining. Since | |
|
303 | # we have a capped reader in place for all situations where we | |
|
304 | # drain, it is safe to read from that stream. We'll either do | |
|
305 | # a drain or no-op if we're already at EOF. | |
|
306 | drain = True | |
|
307 | ||
|
308 | if close: | |
|
309 | self.headers['Connection'] = 'Close' | |
|
310 | ||
|
311 | if drain: | |
|
312 | assert isinstance(self._req.bodyfh, util.cappedreader) | |
|
313 | while True: | |
|
314 | chunk = self._req.bodyfh.read(32768) | |
|
315 | if not chunk: | |
|
316 | break | |
|
317 | ||
|
318 | self._startresponse(pycompat.sysstr(self.status), self.headers.items()) | |
|
319 | if self._bodybytes: | |
|
320 | yield self._bodybytes | |
|
321 | elif self._bodygen: | |
|
322 | for chunk in self._bodygen: | |
|
323 | yield chunk | |
|
324 | else: | |
|
325 | error.ProgrammingError('do not know how to send body') | |
|
326 | ||
|
204 | 327 | class wsgirequest(object): |
|
205 | 328 | """Higher-level API for a WSGI request. |
|
206 | 329 | |
@@ -228,6 +351,7 b' class wsgirequest(object):' | |||
|
228 | 351 | self.env = wsgienv |
|
229 | 352 | self.req = parserequestfromenv(wsgienv, inp) |
|
230 | 353 | self.form = self.req.querystringdict |
|
354 | self.res = wsgiresponse(self.req, start_response) | |
|
231 | 355 | self._start_response = start_response |
|
232 | 356 | self.server_write = None |
|
233 | 357 | self.headers = [] |
@@ -149,7 +149,7 b' class httpv1protocolhandler(wireprototyp' | |||
|
149 | 149 | def iscmd(cmd): |
|
150 | 150 | return cmd in wireproto.commands |
|
151 | 151 | |
|
152 | def handlewsgirequest(rctx, wsgireq, req, checkperm): | |
|
152 | def handlewsgirequest(rctx, wsgireq, req, res, checkperm): | |
|
153 | 153 | """Possibly process a wire protocol request. |
|
154 | 154 | |
|
155 | 155 | If the current request is a wire protocol request, the request is |
@@ -157,10 +157,10 b' def handlewsgirequest(rctx, wsgireq, req' | |||
|
157 | 157 | |
|
158 | 158 | ``wsgireq`` is a ``wsgirequest`` instance. |
|
159 | 159 | ``req`` is a ``parsedrequest`` instance. |
|
160 | ``res`` is a ``wsgiresponse`` instance. | |
|
160 | 161 | |
|
161 | Returns a 2-tuple of (bool, response) where the 1st element indicates | |
|
162 | whether the request was handled and the 2nd element is a return | |
|
163 | value for a WSGI application (often a generator of bytes). | |
|
162 | Returns a bool indicating if the request was serviced. If set, the caller | |
|
163 | should stop processing the request, as a response has already been issued. | |
|
164 | 164 | """ |
|
165 | 165 | # Avoid cycle involving hg module. |
|
166 | 166 | from .hgweb import common as hgwebcommon |
@@ -171,7 +171,7 b' def handlewsgirequest(rctx, wsgireq, req' | |||
|
171 | 171 | # string parameter. If it isn't present, this isn't a wire protocol |
|
172 | 172 | # request. |
|
173 | 173 | if 'cmd' not in req.querystringdict: |
|
174 |
return False |
|
|
174 | return False | |
|
175 | 175 | |
|
176 | 176 | cmd = req.querystringdict['cmd'][0] |
|
177 | 177 | |
@@ -183,18 +183,19 b' def handlewsgirequest(rctx, wsgireq, req' | |||
|
183 | 183 | # known wire protocol commands and it is less confusing for machine |
|
184 | 184 | # clients. |
|
185 | 185 | if not iscmd(cmd): |
|
186 |
return False |
|
|
186 | return False | |
|
187 | 187 | |
|
188 | 188 | # The "cmd" query string argument is only valid on the root path of the |
|
189 | 189 | # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo |
|
190 | 190 | # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request |
|
191 | 191 | # in this case. We send an HTTP 404 for backwards compatibility reasons. |
|
192 | 192 | if req.dispatchpath: |
|
193 | res = _handlehttperror( | |
|
194 | hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq, | |
|
195 | req) | |
|
196 | ||
|
197 | return True, res | |
|
193 | res.status = hgwebcommon.statusmessage(404) | |
|
194 | res.headers['Content-Type'] = HGTYPE | |
|
195 | # TODO This is not a good response to issue for this request. This | |
|
196 | # is mostly for BC for now. | |
|
197 | res.setbodybytes('0\n%s\n' % b'Not Found') | |
|
198 | return True | |
|
198 | 199 | |
|
199 | 200 | proto = httpv1protocolhandler(wsgireq, req, repo.ui, |
|
200 | 201 | lambda perm: checkperm(rctx, wsgireq, perm)) |
@@ -204,11 +205,16 b' def handlewsgirequest(rctx, wsgireq, req' | |||
|
204 | 205 | # exception here. So consider refactoring into a exception type that |
|
205 | 206 | # is associated with the wire protocol. |
|
206 | 207 | try: |
|
207 |
|
|
|
208 | _callhttp(repo, wsgireq, req, res, proto, cmd) | |
|
208 | 209 | except hgwebcommon.ErrorResponse as e: |
|
209 | res = _handlehttperror(e, wsgireq, req) | |
|
210 | for k, v in e.headers: | |
|
211 | res.headers[k] = v | |
|
212 | res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e)) | |
|
213 | # TODO This response body assumes the failed command was | |
|
214 | # "unbundle." That assumption is not always valid. | |
|
215 | res.setbodybytes('0\n%s\n' % pycompat.bytestr(e)) | |
|
210 | 216 | |
|
211 |
return True |
|
|
217 | return True | |
|
212 | 218 | |
|
213 | 219 | def _httpresponsetype(ui, req, prefer_uncompressed): |
|
214 | 220 | """Determine the appropriate response type and compression settings. |
@@ -250,7 +256,10 b' def _httpresponsetype(ui, req, prefer_un' | |||
|
250 | 256 | opts = {'level': ui.configint('server', 'zliblevel')} |
|
251 | 257 | return HGTYPE, util.compengines['zlib'], opts |
|
252 | 258 | |
|
253 | def _callhttp(repo, wsgireq, req, proto, cmd): | |
|
259 | def _callhttp(repo, wsgireq, req, res, proto, cmd): | |
|
260 | # Avoid cycle involving hg module. | |
|
261 | from .hgweb import common as hgwebcommon | |
|
262 | ||
|
254 | 263 | def genversion2(gen, engine, engineopts): |
|
255 | 264 | # application/mercurial-0.2 always sends a payload header |
|
256 | 265 | # identifying the compression engine. |
@@ -262,26 +271,35 b' def _callhttp(repo, wsgireq, req, proto,' | |||
|
262 | 271 | for chunk in gen: |
|
263 | 272 | yield chunk |
|
264 | 273 | |
|
274 | def setresponse(code, contenttype, bodybytes=None, bodygen=None): | |
|
275 | if code == HTTP_OK: | |
|
276 | res.status = '200 Script output follows' | |
|
277 | else: | |
|
278 | res.status = hgwebcommon.statusmessage(code) | |
|
279 | ||
|
280 | res.headers['Content-Type'] = contenttype | |
|
281 | ||
|
282 | if bodybytes is not None: | |
|
283 | res.setbodybytes(bodybytes) | |
|
284 | if bodygen is not None: | |
|
285 | res.setbodygen(bodygen) | |
|
286 | ||
|
265 | 287 | if not wireproto.commands.commandavailable(cmd, proto): |
|
266 |
|
|
|
267 |
|
|
|
268 |
|
|
|
269 |
return |
|
|
288 | setresponse(HTTP_OK, HGERRTYPE, | |
|
289 | _('requested wire protocol command is not available over ' | |
|
290 | 'HTTP')) | |
|
291 | return | |
|
270 | 292 | |
|
271 | 293 | proto.checkperm(wireproto.commands[cmd].permission) |
|
272 | 294 | |
|
273 | 295 | rsp = wireproto.dispatch(repo, proto, cmd) |
|
274 | 296 | |
|
275 | 297 | if isinstance(rsp, bytes): |
|
276 |
|
|
|
277 | return [] | |
|
298 | setresponse(HTTP_OK, HGTYPE, bodybytes=rsp) | |
|
278 | 299 | elif isinstance(rsp, wireprototypes.bytesresponse): |
|
279 |
|
|
|
280 | return [] | |
|
300 | setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data) | |
|
281 | 301 | elif isinstance(rsp, wireprototypes.streamreslegacy): |
|
282 | gen = rsp.gen | |
|
283 | wsgireq.respond(HTTP_OK, HGTYPE) | |
|
284 | return gen | |
|
302 | setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen) | |
|
285 | 303 | elif isinstance(rsp, wireprototypes.streamres): |
|
286 | 304 | gen = rsp.gen |
|
287 | 305 | |
@@ -294,30 +312,18 b' def _callhttp(repo, wsgireq, req, proto,' | |||
|
294 | 312 | if mediatype == HGTYPE2: |
|
295 | 313 | gen = genversion2(gen, engine, engineopts) |
|
296 | 314 | |
|
297 |
|
|
|
298 | return gen | |
|
315 | setresponse(HTTP_OK, mediatype, bodygen=gen) | |
|
299 | 316 | elif isinstance(rsp, wireprototypes.pushres): |
|
300 | 317 | rsp = '%d\n%s' % (rsp.res, rsp.output) |
|
301 |
|
|
|
302 | return [] | |
|
318 | setresponse(HTTP_OK, HGTYPE, bodybytes=rsp) | |
|
303 | 319 | elif isinstance(rsp, wireprototypes.pusherr): |
|
304 | 320 | rsp = '0\n%s\n' % rsp.res |
|
305 | wsgireq.respond(HTTP_OK, HGTYPE, body=rsp) | |
|
306 | return [] | |
|
321 | res.drain = True | |
|
322 | setresponse(HTTP_OK, HGTYPE, bodybytes=rsp) | |
|
307 | 323 | elif isinstance(rsp, wireprototypes.ooberror): |
|
308 | rsp = rsp.message | |
|
309 | wsgireq.respond(HTTP_OK, HGERRTYPE, body=rsp) | |
|
310 | return [] | |
|
311 | raise error.ProgrammingError('hgweb.protocol internal failure', rsp) | |
|
312 | ||
|
313 | def _handlehttperror(e, wsgireq, req): | |
|
314 | """Called when an ErrorResponse is raised during HTTP request processing.""" | |
|
315 | ||
|
316 | # TODO This response body assumes the failed command was | |
|
317 | # "unbundle." That assumption is not always valid. | |
|
318 | wsgireq.respond(e, HGTYPE, body='0\n%s\n' % pycompat.bytestr(e)) | |
|
319 | ||
|
320 | return '' | |
|
324 | setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message) | |
|
325 | else: | |
|
326 | raise error.ProgrammingError('hgweb.protocol internal failure', rsp) | |
|
321 | 327 | |
|
322 | 328 | def _sshv1respondbytes(fout, value): |
|
323 | 329 | """Send a bytes response for protocol version 1.""" |
General Comments 0
You need to be logged in to leave comments.
Login now