mercurial/wireprotoserver.py
changeset 36846 14f70c44af6c
parent 36813 5a3c83412f79
child 36847 ed0456fde625
equal deleted inserted replaced
36843:31581528f242 36846:14f70c44af6c
    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