diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -357,26 +357,18 @@ query = req.env[r'QUERY_STRING'].partition(r'&')[0] query = query.partition(r';')[0] - # The ``cmd`` request parameter is used by both the wire protocol - # and hgweb. We route all known wire protocol commands to the - # wire protocol handler, even if the command isn't available for - # this transport. That's better for machine clients in the case - # of an errant request to an unavailable protocol command. And it - # prevents hgweb from accidentally using ``cmd`` values used by - # the wire protocol. + # Route it to a wire protocol handler if it looks like a wire protocol + # request. + protohandler = wireprotoserver.parsehttprequest(rctx.repo, req, query) - # process this if it's a protocol request - # protocol bits don't need to create any URLs - # and the clients always use the old URL structure - - cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0]) - if wireprotoserver.iscmd(cmd): + if protohandler: + cmd = protohandler['cmd'] try: if query: raise ErrorResponse(HTTP_NOT_FOUND) if cmd in perms: self.check_perm(rctx, req, perms[cmd]) - return wireprotoserver.callhttp(rctx.repo, req, cmd) + return protohandler['dispatch']() except ErrorResponse as inst: # A client that sends unbundle without 100-continue will # break if we respond early. @@ -425,6 +417,8 @@ if fn.endswith(ext): req.form['node'] = [fn[:-len(ext)]] req.form['type'] = [type_] + else: + cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0]) # process the web interface request diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -208,9 +208,43 @@ def iscmd(cmd): return cmd in wireproto.commands -def callhttp(repo, req, cmd): +def parsehttprequest(repo, req, query): + """Parse the HTTP request for a wire protocol request. + + If the current request appears to be a wire protocol request, this + function returns a dict with details about that request, including + an ``abstractprotocolserver`` instance suitable for handling the + request. Otherwise, ``None`` is returned. + + ``req`` is a ``wsgirequest`` instance. + """ + # HTTP version 1 wire protocol requests are denoted by a "cmd" query + # string parameter. If it isn't present, this isn't a wire protocol + # request. + if r'cmd' not in req.form: + return None + + cmd = pycompat.sysbytes(req.form[r'cmd'][0]) + + # The "cmd" request parameter is used by both the wire protocol and hgweb. + # While not all wire protocol commands are available for all transports, + # if we see a "cmd" value that resembles a known wire protocol command, we + # route it to a protocol handler. This is better than routing possible + # wire protocol requests to hgweb because it prevents hgweb from using + # known wire protocol commands and it is less confusing for machine + # clients. + if cmd not in wireproto.commands: + return None + proto = webproto(req, repo.ui) + return { + 'cmd': cmd, + 'proto': proto, + 'dispatch': lambda: _callhttp(repo, req, proto, cmd), + } + +def _callhttp(repo, req, proto, cmd): def genversion2(gen, engine, engineopts): # application/mercurial-0.2 always sends a payload header # identifying the compression engine.