wireproto: function for testing if wire protocol command is available
authorGregory Szorc <gregory.szorc@gmail.com>
Tue, 30 Jan 2018 18:41:44 -0800
changeset 35982 5a56bf4180ad
parent 35981 ef683a0fd21f
child 35983 f540b6448738
wireproto: function for testing if wire protocol command is available Currently, we perform simple membership testing for whether a wire command is available. In the future, not all wire protocol commands will be available on all transports. For example, a legacy transport may not support newer commands. In preparation of this, teach the protocol handlers to call into a function to determine if a wire protocol command is available. That function currently does membership testing like before, so behavior should be identical. In the case of the HTTP server, behavior is a bit wonkier. "cmd" is used by both the wire protocol and hgweb. We do want the protocol handler to handle requests for all commands that look like wire protocol commands, even if they aren't available. Otherwise, the fallback to hgweb would only confuse automated clients and make it easier for hgweb to accidentally implement a "cmd" that is identical to wire protocol commands (since they aren't centrally registered). Differential Revision: https://phab.mercurial-scm.org/D1999
mercurial/hgweb/hgweb_mod.py
mercurial/wireproto.py
mercurial/wireprotoserver.py
--- a/mercurial/hgweb/hgweb_mod.py	Wed Jan 31 14:05:11 2018 -0800
+++ b/mercurial/hgweb/hgweb_mod.py	Tue Jan 30 18:41:44 2018 -0800
@@ -357,6 +357,14 @@
             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.
+
         # 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
--- a/mercurial/wireproto.py	Wed Jan 31 14:05:11 2018 -0800
+++ b/mercurial/wireproto.py	Tue Jan 30 18:41:44 2018 -0800
@@ -691,6 +691,12 @@
 
         return super(commanddict, self).__setitem__(k, v)
 
+    def commandavailable(self, command, proto):
+        """Determine if a command is available for the requested protocol."""
+        # For now, commands are available for all protocols. So do a simple
+        # membership test.
+        return command in self
+
 commands = commanddict()
 
 def wireprotocommand(name, args=''):
--- a/mercurial/wireprotoserver.py	Wed Jan 31 14:05:11 2018 -0800
+++ b/mercurial/wireprotoserver.py	Tue Jan 30 18:41:44 2018 -0800
@@ -223,6 +223,13 @@
             yield chunk
 
     rsp = wireproto.dispatch(repo, proto, cmd)
+
+    if not wireproto.commands.commandavailable(cmd, proto):
+        req.respond(HTTP_OK, HGERRTYPE,
+                    body=_('requested wire protocol command is not available '
+                           'over HTTP'))
+        return []
+
     if isinstance(rsp, bytes):
         req.respond(HTTP_OK, HGTYPE, body=rsp)
         return []
@@ -351,7 +358,7 @@
 
     def serve_one(self):
         cmd = self._fin.readline()[:-1]
-        if cmd and cmd in wireproto.commands:
+        if cmd and wireproto.commands.commandavailable(cmd, self):
             rsp = wireproto.dispatch(self._repo, self, cmd)
             self._handlers[rsp.__class__](self, rsp)
         elif cmd: