34 HGERRTYPE = 'application/hg-error' |
34 HGERRTYPE = 'application/hg-error' |
35 |
35 |
36 SSHV1 = wireprototypes.SSHV1 |
36 SSHV1 = wireprototypes.SSHV1 |
37 SSHV2 = wireprototypes.SSHV2 |
37 SSHV2 = wireprototypes.SSHV2 |
38 |
38 |
39 def decodevaluefromheaders(wsgireq, headerprefix): |
39 def decodevaluefromheaders(req, headerprefix): |
40 """Decode a long value from multiple HTTP request headers. |
40 """Decode a long value from multiple HTTP request headers. |
41 |
41 |
42 Returns the value as a bytes, not a str. |
42 Returns the value as a bytes, not a str. |
43 """ |
43 """ |
44 chunks = [] |
44 chunks = [] |
45 i = 1 |
45 i = 1 |
46 prefix = headerprefix.upper().replace(r'-', r'_') |
|
47 while True: |
46 while True: |
48 v = wsgireq.env.get(r'HTTP_%s_%d' % (prefix, i)) |
47 v = req.headers.get(b'%s-%d' % (headerprefix, i)) |
49 if v is None: |
48 if v is None: |
50 break |
49 break |
51 chunks.append(pycompat.bytesurl(v)) |
50 chunks.append(pycompat.bytesurl(v)) |
52 i += 1 |
51 i += 1 |
53 |
52 |
54 return ''.join(chunks) |
53 return ''.join(chunks) |
55 |
54 |
56 class httpv1protocolhandler(wireprototypes.baseprotocolhandler): |
55 class httpv1protocolhandler(wireprototypes.baseprotocolhandler): |
57 def __init__(self, wsgireq, ui, checkperm): |
56 def __init__(self, wsgireq, req, ui, checkperm): |
58 self._wsgireq = wsgireq |
57 self._wsgireq = wsgireq |
|
58 self._req = req |
59 self._ui = ui |
59 self._ui = ui |
60 self._checkperm = checkperm |
60 self._checkperm = checkperm |
61 |
61 |
62 @property |
62 @property |
63 def name(self): |
63 def name(self): |
78 data[k] = knownargs[k][0] |
78 data[k] = knownargs[k][0] |
79 return [data[k] for k in keys] |
79 return [data[k] for k in keys] |
80 |
80 |
81 def _args(self): |
81 def _args(self): |
82 args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy()) |
82 args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy()) |
83 postlen = int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0)) |
83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0)) |
84 if postlen: |
84 if postlen: |
85 args.update(urlreq.parseqs( |
85 args.update(urlreq.parseqs( |
86 self._wsgireq.read(postlen), keep_blank_values=True)) |
86 self._wsgireq.read(postlen), keep_blank_values=True)) |
87 return args |
87 return args |
88 |
88 |
89 argvalue = decodevaluefromheaders(self._wsgireq, r'X-HgArg') |
89 argvalue = decodevaluefromheaders(self._req, b'X-HgArg') |
90 args.update(urlreq.parseqs(argvalue, keep_blank_values=True)) |
90 args.update(urlreq.parseqs(argvalue, keep_blank_values=True)) |
91 return args |
91 return args |
92 |
92 |
93 def forwardpayload(self, fp): |
93 def forwardpayload(self, fp): |
94 if r'HTTP_CONTENT_LENGTH' in self._wsgireq.env: |
94 if b'Content-Length' in self._req.headers: |
95 length = int(self._wsgireq.env[r'HTTP_CONTENT_LENGTH']) |
95 length = int(self._req.headers[b'Content-Length']) |
96 else: |
96 else: |
97 length = int(self._wsgireq.env[r'CONTENT_LENGTH']) |
97 length = int(self._wsgireq.env[r'CONTENT_LENGTH']) |
98 # If httppostargs is used, we need to read Content-Length |
98 # If httppostargs is used, we need to read Content-Length |
99 # minus the amount that was consumed by args. |
99 # minus the amount that was consumed by args. |
100 length -= int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0)) |
100 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0)) |
101 for s in util.filechunkiter(self._wsgireq, limit=length): |
101 for s in util.filechunkiter(self._wsgireq, limit=length): |
102 fp.write(s) |
102 fp.write(s) |
103 |
103 |
104 @contextlib.contextmanager |
104 @contextlib.contextmanager |
105 def mayberedirectstdio(self): |
105 def mayberedirectstdio(self): |
191 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request |
191 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request |
192 # in this case. We send an HTTP 404 for backwards compatibility reasons. |
192 # in this case. We send an HTTP 404 for backwards compatibility reasons. |
193 if req.dispatchpath: |
193 if req.dispatchpath: |
194 res = _handlehttperror( |
194 res = _handlehttperror( |
195 hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq, |
195 hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq, |
196 cmd) |
196 req, cmd) |
197 |
197 |
198 return True, res |
198 return True, res |
199 |
199 |
200 proto = httpv1protocolhandler(wsgireq, repo.ui, |
200 proto = httpv1protocolhandler(wsgireq, req, repo.ui, |
201 lambda perm: checkperm(rctx, wsgireq, perm)) |
201 lambda perm: checkperm(rctx, wsgireq, perm)) |
202 |
202 |
203 # The permissions checker should be the only thing that can raise an |
203 # The permissions checker should be the only thing that can raise an |
204 # ErrorResponse. It is kind of a layer violation to catch an hgweb |
204 # ErrorResponse. It is kind of a layer violation to catch an hgweb |
205 # exception here. So consider refactoring into a exception type that |
205 # exception here. So consider refactoring into a exception type that |
206 # is associated with the wire protocol. |
206 # is associated with the wire protocol. |
207 try: |
207 try: |
208 res = _callhttp(repo, wsgireq, proto, cmd) |
208 res = _callhttp(repo, wsgireq, req, proto, cmd) |
209 except hgwebcommon.ErrorResponse as e: |
209 except hgwebcommon.ErrorResponse as e: |
210 res = _handlehttperror(e, wsgireq, cmd) |
210 res = _handlehttperror(e, wsgireq, req, cmd) |
211 |
211 |
212 return True, res |
212 return True, res |
213 |
213 |
214 def _httpresponsetype(ui, wsgireq, prefer_uncompressed): |
214 def _httpresponsetype(ui, req, prefer_uncompressed): |
215 """Determine the appropriate response type and compression settings. |
215 """Determine the appropriate response type and compression settings. |
216 |
216 |
217 Returns a tuple of (mediatype, compengine, engineopts). |
217 Returns a tuple of (mediatype, compengine, engineopts). |
218 """ |
218 """ |
219 # Determine the response media type and compression engine based |
219 # Determine the response media type and compression engine based |
220 # on the request parameters. |
220 # on the request parameters. |
221 protocaps = decodevaluefromheaders(wsgireq, r'X-HgProto').split(' ') |
221 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ') |
222 |
222 |
223 if '0.2' in protocaps: |
223 if '0.2' in protocaps: |
224 # All clients are expected to support uncompressed data. |
224 # All clients are expected to support uncompressed data. |
225 if prefer_uncompressed: |
225 if prefer_uncompressed: |
226 return HGTYPE2, util._noopengine(), {} |
226 return HGTYPE2, util._noopengine(), {} |
249 # setting a very high compression level could lead to flooding |
249 # setting a very high compression level could lead to flooding |
250 # the server's network or CPU. |
250 # the server's network or CPU. |
251 opts = {'level': ui.configint('server', 'zliblevel')} |
251 opts = {'level': ui.configint('server', 'zliblevel')} |
252 return HGTYPE, util.compengines['zlib'], opts |
252 return HGTYPE, util.compengines['zlib'], opts |
253 |
253 |
254 def _callhttp(repo, wsgireq, proto, cmd): |
254 def _callhttp(repo, wsgireq, req, proto, cmd): |
255 def genversion2(gen, engine, engineopts): |
255 def genversion2(gen, engine, engineopts): |
256 # application/mercurial-0.2 always sends a payload header |
256 # application/mercurial-0.2 always sends a payload header |
257 # identifying the compression engine. |
257 # identifying the compression engine. |
258 name = engine.wireprotosupport().name |
258 name = engine.wireprotosupport().name |
259 assert 0 < len(name) < 256 |
259 assert 0 < len(name) < 256 |
287 gen = rsp.gen |
287 gen = rsp.gen |
288 |
288 |
289 # This code for compression should not be streamres specific. It |
289 # This code for compression should not be streamres specific. It |
290 # is here because we only compress streamres at the moment. |
290 # is here because we only compress streamres at the moment. |
291 mediatype, engine, engineopts = _httpresponsetype( |
291 mediatype, engine, engineopts = _httpresponsetype( |
292 repo.ui, wsgireq, rsp.prefer_uncompressed) |
292 repo.ui, req, rsp.prefer_uncompressed) |
293 gen = engine.compressstream(gen, engineopts) |
293 gen = engine.compressstream(gen, engineopts) |
294 |
294 |
295 if mediatype == HGTYPE2: |
295 if mediatype == HGTYPE2: |
296 gen = genversion2(gen, engine, engineopts) |
296 gen = genversion2(gen, engine, engineopts) |
297 |
297 |
312 rsp = rsp.message |
312 rsp = rsp.message |
313 wsgireq.respond(HTTP_OK, HGERRTYPE, body=rsp) |
313 wsgireq.respond(HTTP_OK, HGERRTYPE, body=rsp) |
314 return [] |
314 return [] |
315 raise error.ProgrammingError('hgweb.protocol internal failure', rsp) |
315 raise error.ProgrammingError('hgweb.protocol internal failure', rsp) |
316 |
316 |
317 def _handlehttperror(e, wsgireq, cmd): |
317 def _handlehttperror(e, wsgireq, req, cmd): |
318 """Called when an ErrorResponse is raised during HTTP request processing.""" |
318 """Called when an ErrorResponse is raised during HTTP request processing.""" |
319 |
319 |
320 # Clients using Python's httplib are stateful: the HTTP client |
320 # Clients using Python's httplib are stateful: the HTTP client |
321 # won't process an HTTP response until all request data is |
321 # won't process an HTTP response until all request data is |
322 # sent to the server. The intent of this code is to ensure |
322 # sent to the server. The intent of this code is to ensure |
325 # the HTTP response. In other words, it helps prevent deadlocks |
325 # the HTTP response. In other words, it helps prevent deadlocks |
326 # on clients using httplib. |
326 # on clients using httplib. |
327 |
327 |
328 if (wsgireq.env[r'REQUEST_METHOD'] == r'POST' and |
328 if (wsgireq.env[r'REQUEST_METHOD'] == r'POST' and |
329 # But not if Expect: 100-continue is being used. |
329 # But not if Expect: 100-continue is being used. |
330 (wsgireq.env.get('HTTP_EXPECT', |
330 (req.headers.get('Expect', '').lower() != '100-continue')): |
331 '').lower() != '100-continue')): |
|
332 wsgireq.drain() |
331 wsgireq.drain() |
333 else: |
332 else: |
334 wsgireq.headers.append((r'Connection', r'Close')) |
333 wsgireq.headers.append((r'Connection', r'Close')) |
335 |
334 |
336 # TODO This response body assumes the failed command was |
335 # TODO This response body assumes the failed command was |