--- a/mercurial/wireprotoserver.py Thu Dec 30 13:25:44 2021 +0100
+++ b/mercurial/wireprotoserver.py Tue Dec 07 16:44:22 2021 +0100
@@ -18,11 +18,9 @@
util,
wireprototypes,
wireprotov1server,
- wireprotov2server,
)
from .interfaces import util as interfaceutil
from .utils import (
- cborutil,
compression,
stringutil,
)
@@ -39,7 +37,6 @@
HGERRTYPE = b'application/hg-error'
SSHV1 = wireprototypes.SSHV1
-SSHV2 = wireprototypes.SSHV2
def decodevaluefromheaders(req, headerprefix):
@@ -244,97 +241,6 @@
return True
-def _availableapis(repo):
- apis = set()
-
- # Registered APIs are made available via config options of the name of
- # the protocol.
- for k, v in API_HANDLERS.items():
- section, option = v[b'config'] # pytype: disable=attribute-error
- if repo.ui.configbool(section, option):
- apis.add(k)
-
- return apis
-
-
-def handlewsgiapirequest(rctx, req, res, checkperm):
- """Handle requests to /api/*."""
- assert req.dispatchparts[0] == b'api'
-
- repo = rctx.repo
-
- # This whole URL space is experimental for now. But we want to
- # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
- if not repo.ui.configbool(b'experimental', b'web.apiserver'):
- res.status = b'404 Not Found'
- res.headers[b'Content-Type'] = b'text/plain'
- res.setbodybytes(_(b'Experimental API server endpoint not enabled'))
- return
-
- # The URL space is /api/<protocol>/*. The structure of URLs under varies
- # by <protocol>.
-
- availableapis = _availableapis(repo)
-
- # Requests to /api/ list available APIs.
- if req.dispatchparts == [b'api']:
- res.status = b'200 OK'
- res.headers[b'Content-Type'] = b'text/plain'
- lines = [
- _(
- b'APIs can be accessed at /api/<name>, where <name> can be '
- b'one of the following:\n'
- )
- ]
- if availableapis:
- lines.extend(sorted(availableapis))
- else:
- lines.append(_(b'(no available APIs)\n'))
- res.setbodybytes(b'\n'.join(lines))
- return
-
- proto = req.dispatchparts[1]
-
- if proto not in API_HANDLERS:
- res.status = b'404 Not Found'
- res.headers[b'Content-Type'] = b'text/plain'
- res.setbodybytes(
- _(b'Unknown API: %s\nKnown APIs: %s')
- % (proto, b', '.join(sorted(availableapis)))
- )
- return
-
- if proto not in availableapis:
- res.status = b'404 Not Found'
- res.headers[b'Content-Type'] = b'text/plain'
- res.setbodybytes(_(b'API %s not enabled\n') % proto)
- return
-
- API_HANDLERS[proto][b'handler'](
- rctx, req, res, checkperm, req.dispatchparts[2:]
- )
-
-
-# Maps API name to metadata so custom API can be registered.
-# Keys are:
-#
-# config
-# Config option that controls whether service is enabled.
-# handler
-# Callable receiving (rctx, req, res, checkperm, urlparts) that is called
-# when a request to this API is received.
-# apidescriptor
-# Callable receiving (req, repo) that is called to obtain an API
-# descriptor for this service. The response must be serializable to CBOR.
-API_HANDLERS = {
- wireprotov2server.HTTP_WIREPROTO_V2: {
- b'config': (b'experimental', b'web.api.http-v2'),
- b'handler': wireprotov2server.handlehttpv2request,
- b'apidescriptor': wireprotov2server.httpv2apidescriptor,
- },
-}
-
-
def _httpresponsetype(ui, proto, prefer_uncompressed):
"""Determine the appropriate response type and compression settings.
@@ -371,55 +277,6 @@
return HGTYPE, util.compengines[b'zlib'], opts
-def processcapabilitieshandshake(repo, req, res, proto):
- """Called during a ?cmd=capabilities request.
-
- If the client is advertising support for a newer protocol, we send
- a CBOR response with information about available services. If no
- advertised services are available, we don't handle the request.
- """
- # Fall back to old behavior unless the API server is enabled.
- if not repo.ui.configbool(b'experimental', b'web.apiserver'):
- return False
-
- clientapis = decodevaluefromheaders(req, b'X-HgUpgrade')
- protocaps = decodevaluefromheaders(req, b'X-HgProto')
- if not clientapis or not protocaps:
- return False
-
- # We currently only support CBOR responses.
- protocaps = set(protocaps.split(b' '))
- if b'cbor' not in protocaps:
- return False
-
- descriptors = {}
-
- for api in sorted(set(clientapis.split()) & _availableapis(repo)):
- handler = API_HANDLERS[api]
-
- descriptorfn = handler.get(b'apidescriptor')
- if not descriptorfn:
- continue
-
- descriptors[api] = descriptorfn(req, repo)
-
- v1caps = wireprotov1server.dispatch(repo, proto, b'capabilities')
- assert isinstance(v1caps, wireprototypes.bytesresponse)
-
- m = {
- # TODO allow this to be configurable.
- b'apibase': b'api/',
- b'apis': descriptors,
- b'v1capabilities': v1caps.data,
- }
-
- res.status = b'200 OK'
- res.headers[b'Content-Type'] = b'application/mercurial-cbor'
- res.setbodybytes(b''.join(cborutil.streamencode(m)))
-
- return True
-
-
def _callhttp(repo, req, res, proto, cmd):
# Avoid cycle involving hg module.
from .hgweb import common as hgwebcommon
@@ -461,13 +318,6 @@
proto.checkperm(wireprotov1server.commands[cmd].permission)
- # Possibly handle a modern client wanting to switch protocols.
- if cmd == b'capabilities' and processcapabilitieshandshake(
- repo, req, res, proto
- ):
-
- return
-
rsp = wireprotov1server.dispatch(repo, proto, cmd)
if isinstance(rsp, bytes):
@@ -596,17 +446,6 @@
pass
-class sshv2protocolhandler(sshv1protocolhandler):
- """Protocol handler for version 2 of the SSH protocol."""
-
- @property
- def name(self):
- return wireprototypes.SSHV2
-
- def addcapabilities(self, repo, caps):
- return caps
-
-
def _runsshserver(ui, repo, fin, fout, ev):
# This function operates like a state machine of sorts. The following
# states are defined:
@@ -616,19 +455,6 @@
# new lines. These commands are processed in this state, one command
# after the other.
#
- # protov2-serving
- # Server is in protocol version 2 serving mode.
- #
- # upgrade-initial
- # The server is going to process an upgrade request.
- #
- # upgrade-v2-filter-legacy-handshake
- # The protocol is being upgraded to version 2. The server is expecting
- # the legacy handshake from version 1.
- #
- # upgrade-v2-finish
- # The upgrade to version 2 of the protocol is imminent.
- #
# shutdown
# The server is shutting down, possibly in reaction to a client event.
#
@@ -637,32 +463,9 @@
# protov1-serving -> shutdown
# When server receives an empty request or encounters another
# error.
- #
- # protov1-serving -> upgrade-initial
- # An upgrade request line was seen.
- #
- # upgrade-initial -> upgrade-v2-filter-legacy-handshake
- # Upgrade to version 2 in progress. Server is expecting to
- # process a legacy handshake.
- #
- # upgrade-v2-filter-legacy-handshake -> shutdown
- # Client did not fulfill upgrade handshake requirements.
- #
- # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
- # Client fulfilled version 2 upgrade requirements. Finishing that
- # upgrade.
- #
- # upgrade-v2-finish -> protov2-serving
- # Protocol upgrade to version 2 complete. Server can now speak protocol
- # version 2.
- #
- # protov2-serving -> protov1-serving
- # Ths happens by default since protocol version 2 is the same as
- # version 1 except for the handshake.
state = b'protov1-serving'
proto = sshv1protocolhandler(ui, fin, fout)
- protoswitched = False
while not ev.is_set():
if state == b'protov1-serving':
@@ -674,21 +477,6 @@
state = b'shutdown'
continue
- # It looks like a protocol upgrade request. Transition state to
- # handle it.
- if request.startswith(b'upgrade '):
- if protoswitched:
- _sshv1respondooberror(
- fout,
- ui.ferr,
- b'cannot upgrade protocols multiple times',
- )
- state = b'shutdown'
- continue
-
- state = b'upgrade-initial'
- continue
-
available = wireprotov1server.commands.commandavailable(
request, proto
)
@@ -724,108 +512,6 @@
b'wire protocol command: %s' % rsp
)
- # For now, protocol version 2 serving just goes back to version 1.
- elif state == b'protov2-serving':
- state = b'protov1-serving'
- continue
-
- elif state == b'upgrade-initial':
- # We should never transition into this state if we've switched
- # protocols.
- assert not protoswitched
- assert proto.name == wireprototypes.SSHV1
-
- # Expected: upgrade <token> <capabilities>
- # If we get something else, the request is malformed. It could be
- # from a future client that has altered the upgrade line content.
- # We treat this as an unknown command.
- try:
- token, caps = request.split(b' ')[1:]
- except ValueError:
- _sshv1respondbytes(fout, b'')
- state = b'protov1-serving'
- continue
-
- # Send empty response if we don't support upgrading protocols.
- if not ui.configbool(b'experimental', b'sshserver.support-v2'):
- _sshv1respondbytes(fout, b'')
- state = b'protov1-serving'
- continue
-
- try:
- caps = urlreq.parseqs(caps)
- except ValueError:
- _sshv1respondbytes(fout, b'')
- state = b'protov1-serving'
- continue
-
- # We don't see an upgrade request to protocol version 2. Ignore
- # the upgrade request.
- wantedprotos = caps.get(b'proto', [b''])[0]
- if SSHV2 not in wantedprotos:
- _sshv1respondbytes(fout, b'')
- state = b'protov1-serving'
- continue
-
- # It looks like we can honor this upgrade request to protocol 2.
- # Filter the rest of the handshake protocol request lines.
- state = b'upgrade-v2-filter-legacy-handshake'
- continue
-
- elif state == b'upgrade-v2-filter-legacy-handshake':
- # Client should have sent legacy handshake after an ``upgrade``
- # request. Expected lines:
- #
- # hello
- # between
- # pairs 81
- # 0000...-0000...
-
- ok = True
- for line in (b'hello', b'between', b'pairs 81'):
- request = fin.readline()[:-1]
-
- if request != line:
- _sshv1respondooberror(
- fout,
- ui.ferr,
- b'malformed handshake protocol: missing %s' % line,
- )
- ok = False
- state = b'shutdown'
- break
-
- if not ok:
- continue
-
- request = fin.read(81)
- if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
- _sshv1respondooberror(
- fout,
- ui.ferr,
- b'malformed handshake protocol: '
- b'missing between argument value',
- )
- state = b'shutdown'
- continue
-
- state = b'upgrade-v2-finish'
- continue
-
- elif state == b'upgrade-v2-finish':
- # Send the upgrade response.
- fout.write(b'upgraded %s %s\n' % (token, SSHV2))
- servercaps = wireprotov1server.capabilities(repo, proto)
- rsp = b'capabilities: %s' % servercaps.data
- fout.write(b'%d\n%s\n' % (len(rsp), rsp))
- fout.flush()
-
- proto = sshv2protocolhandler(ui, fin, fout)
- protoswitched = True
-
- state = b'protov2-serving'
- continue
-
elif state == b'shutdown':
break