##// END OF EJS Templates
fancyopts: add support for custom multi-arg opts in fancyopts.py...
fancyopts: add support for custom multi-arg opts in fancyopts.py This allows for more complex multi-arg opt logic, such as "--sum 1 --sum 2" -> 3, "--csv alice,bob --csv charlie" -> ["alice","bob","charlie"]. The current support for callables is insufficient for this. This is done by introducing a 'customopt' class which can be extended for more powerful opts logic. All existing opt-types are converted to use this class, simplifying the fancyopts() logic. Differential Revision: https://phab.mercurial-scm.org/D2090

File last commit:

r36294:685bcdd2 default
r36373:cc9d0763 default
Show More
wireprotoserver.py
651 lines | 21.9 KiB | text/x-python | PythonLexer
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
Gregory Szorc
wireprotoserver: make abstractserverproto a proper abstract base class...
r35890 import abc
Gregory Szorc
wireprotoserver: add context manager mechanism for redirecting stdio...
r36083 import contextlib
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 import struct
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 import sys
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 from .i18n import _
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 from . import (
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 encoding,
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 error,
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 hook,
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 pycompat,
util,
wireproto,
Gregory Szorc
wireprototypes: move wire protocol response types to new module...
r36090 wireprototypes,
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 )
stringio = util.stringio
urlerr = util.urlerr
urlreq = util.urlreq
Gregory Szorc
wireprotoserver: don't import symbol from hgweb.common...
r35876 HTTP_OK = 200
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 HGTYPE = 'application/mercurial-0.1'
HGTYPE2 = 'application/mercurial-0.2'
HGERRTYPE = 'application/hg-error'
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # Names of the SSH protocol implementations.
SSHV1 = 'ssh-v1'
# This is advertised over the wire. Incremental the counter at the end
# to reflect BC breakages.
SSHV2 = 'exp-ssh-v2-0001'
Gregory Szorc
wireprotoserver: rename abstractserverproto and improve docstring...
r36006 class baseprotocolhandler(object):
"""Abstract base class for wire protocol handlers.
Gregory Szorc
wireprotoserver: move abstractserverproto class from wireproto...
r35878
Gregory Szorc
wireprotoserver: rename abstractserverproto and improve docstring...
r36006 A wire protocol handler serves as an interface between protocol command
handlers and the wire protocol transport layer. Protocol handlers provide
methods to read command arguments, redirect stdio for the duration of
the request, handle response types, etc.
Gregory Szorc
wireprotoserver: move abstractserverproto class from wireproto...
r35878 """
Gregory Szorc
wireprotoserver: make abstractserverproto a proper abstract base class...
r35890 __metaclass__ = abc.ABCMeta
Gregory Szorc
wireprotoserver: make name part of protocol interface...
r35891 @abc.abstractproperty
def name(self):
"""The name of the protocol implementation.
Used for uniquely identifying the transport type.
"""
Gregory Szorc
wireprotoserver: make abstractserverproto a proper abstract base class...
r35890 @abc.abstractmethod
Gregory Szorc
wireprotoserver: move abstractserverproto class from wireproto...
r35878 def getargs(self, args):
"""return the value for arguments in <args>
returns a list of values (same order as <args>)"""
Gregory Szorc
wireprotoserver: make abstractserverproto a proper abstract base class...
r35890 @abc.abstractmethod
Gregory Szorc
wireprotoserver: rename getfile() to forwardpayload() (API)...
r36087 def forwardpayload(self, fp):
"""Read the raw payload and forward to a file.
Gregory Szorc
wireprotoserver: move abstractserverproto class from wireproto...
r35878
Gregory Szorc
wireprotoserver: rename getfile() to forwardpayload() (API)...
r36087 The payload is read in full before the function returns.
Gregory Szorc
wireprotoserver: move abstractserverproto class from wireproto...
r35878 """
Gregory Szorc
wireprotoserver: make abstractserverproto a proper abstract base class...
r35890 @abc.abstractmethod
Gregory Szorc
wireprotoserver: add context manager mechanism for redirecting stdio...
r36083 def mayberedirectstdio(self):
"""Context manager to possibly redirect stdio.
The context manager yields a file-object like object that receives
stdout and stderr output when the context manager is active. Or it
yields ``None`` if no I/O redirection occurs.
The intent of this context manager is to capture stdio output
so it may be sent in the response. Some transports support streaming
stdio to the client in real time. For these transports, stdio output
won't be captured.
"""
Gregory Szorc
wireprotoserver: rename _client to client (API)...
r36086 @abc.abstractmethod
def client(self):
"""Returns a string representation of this client (as bytes)."""
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def decodevaluefromheaders(req, headerprefix):
"""Decode a long value from multiple HTTP request headers.
Returns the value as a bytes, not a str.
"""
chunks = []
i = 1
prefix = headerprefix.upper().replace(r'-', r'_')
while True:
v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
if v is None:
break
chunks.append(pycompat.bytesurl(v))
i += 1
return ''.join(chunks)
Gregory Szorc
wireprotoserver: rename webproto to httpv1protocolhandler...
r36240 class httpv1protocolhandler(baseprotocolhandler):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def __init__(self, req, ui):
Gregory Szorc
wireprotoserver: make attributes private...
r35884 self._req = req
self._ui = ui
Gregory Szorc
wireprotoserver: make name part of protocol interface...
r35891
@property
def name(self):
Gregory Szorc
wireprotoserver: add version to HTTP protocol name (API)...
r36241 return 'http-v1'
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
def getargs(self, args):
knownargs = self._args()
data = {}
keys = args.split()
for k in keys:
if k == '*':
star = {}
for key in knownargs.keys():
if key != 'cmd' and key not in keys:
star[key] = knownargs[key][0]
data['*'] = star
else:
data[k] = knownargs[k][0]
return [data[k] for k in keys]
Gregory Szorc
wireprotoserver: add some blank lines between methods...
r35881
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def _args(self):
Yuya Nishihara
py3: factor out helpers to apply string conversion recursively
r35918 args = util.rapply(pycompat.bytesurl, self._req.form.copy())
Gregory Szorc
wireprotoserver: make attributes private...
r35884 postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 if postlen:
Gregory Szorc
wireprotoserver: define and use parse_qs from urllib...
r36094 args.update(urlreq.parseqs(
Gregory Szorc
wireprotoserver: make attributes private...
r35884 self._req.read(postlen), keep_blank_values=True))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 return args
Gregory Szorc
wireprotoserver: make attributes private...
r35884 argvalue = decodevaluefromheaders(self._req, r'X-HgArg')
Gregory Szorc
wireprotoserver: define and use parse_qs from urllib...
r36094 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 return args
Gregory Szorc
wireprotoserver: add some blank lines between methods...
r35881
Gregory Szorc
wireprotoserver: rename getfile() to forwardpayload() (API)...
r36087 def forwardpayload(self, fp):
Augie Fackler
wireprotoserver: py3 helpfully calls adds HTTP_ to CONTENT_LENGTH...
r36294 if r'HTTP_CONTENT_LENGTH' in self._req.env:
length = int(self._req.env[r'HTTP_CONTENT_LENGTH'])
else:
length = int(self._req.env[r'CONTENT_LENGTH'])
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 # If httppostargs is used, we need to read Content-Length
# minus the amount that was consumed by args.
Gregory Szorc
wireprotoserver: make attributes private...
r35884 length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
for s in util.filechunkiter(self._req, limit=length):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 fp.write(s)
Gregory Szorc
wireprotoserver: add some blank lines between methods...
r35881
Gregory Szorc
wireprotoserver: add context manager mechanism for redirecting stdio...
r36083 @contextlib.contextmanager
def mayberedirectstdio(self):
oldout = self._ui.fout
olderr = self._ui.ferr
out = util.stringio()
try:
self._ui.fout = out
self._ui.ferr = out
yield out
finally:
self._ui.fout = oldout
self._ui.ferr = olderr
Gregory Szorc
wireprotoserver: rename _client to client (API)...
r36086 def client(self):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 return 'remote:%s:%s:%s' % (
Gregory Szorc
wireprotoserver: make attributes private...
r35884 self._req.env.get('wsgi.url_scheme') or 'http',
urlreq.quote(self._req.env.get('REMOTE_HOST', '')),
urlreq.quote(self._req.env.get('REMOTE_USER', '')))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
Augie Fackler
wireprotoserver: return to using iscmd() method...
r36249 # This method exists mostly so that extensions like remotefilelog can
# disable a kludgey legacy method only over http. As of early 2018,
# there are no other known users, so with any luck we can discard this
# hook if remotefilelog becomes a first-party extension.
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def iscmd(cmd):
return cmd in wireproto.commands
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002 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.
Augie Fackler
wireprotoserver: return to using iscmd() method...
r36249 if not iscmd(cmd):
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002 return None
Gregory Szorc
wireprotoserver: rename webproto to httpv1protocolhandler...
r36240 proto = httpv1protocolhandler(req, repo.ui)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002 return {
'cmd': cmd,
'proto': proto,
'dispatch': lambda: _callhttp(repo, req, proto, cmd),
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 'handleerror': lambda ex: _handlehttperror(ex, req, cmd),
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002 }
Gregory Szorc
wireprotoserver: move responsetype() out of http handler...
r36089 def _httpresponsetype(ui, req, prefer_uncompressed):
"""Determine the appropriate response type and compression settings.
Returns a tuple of (mediatype, compengine, engineopts).
"""
# Determine the response media type and compression engine based
# on the request parameters.
protocaps = decodevaluefromheaders(req, r'X-HgProto').split(' ')
if '0.2' in protocaps:
# All clients are expected to support uncompressed data.
if prefer_uncompressed:
return HGTYPE2, util._noopengine(), {}
# Default as defined by wire protocol spec.
compformats = ['zlib', 'none']
for cap in protocaps:
if cap.startswith('comp='):
compformats = cap[5:].split(',')
break
# Now find an agreed upon compression format.
for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
if engine.wireprotosupport().name in compformats:
opts = {}
level = ui.configint('server', '%slevel' % engine.name())
if level is not None:
opts['level'] = level
return HGTYPE2, engine, opts
# No mutually supported compression format. Fall back to the
# legacy protocol.
# Don't allow untrusted settings because disabling compression or
# setting a very high compression level could lead to flooding
# the server's network or CPU.
opts = {'level': ui.configint('server', 'zliblevel')}
return HGTYPE, util.compengines['zlib'], opts
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002 def _callhttp(repo, req, proto, cmd):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def genversion2(gen, engine, engineopts):
# application/mercurial-0.2 always sends a payload header
# identifying the compression engine.
name = engine.wireprotosupport().name
assert 0 < len(name) < 256
yield struct.pack('B', len(name))
yield name
for chunk in gen:
yield chunk
Gregory Szorc
wireprotoserver: rename p to proto...
r35882 rsp = wireproto.dispatch(repo, proto, cmd)
Gregory Szorc
wireproto: function for testing if wire protocol command is available...
r36000
if not wireproto.commands.commandavailable(cmd, proto):
req.respond(HTTP_OK, HGERRTYPE,
body=_('requested wire protocol command is not available '
'over HTTP'))
return []
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 if isinstance(rsp, bytes):
req.respond(HTTP_OK, HGTYPE, body=rsp)
return []
Gregory Szorc
wireproto: introduce type for raw byte responses (API)...
r36091 elif isinstance(rsp, wireprototypes.bytesresponse):
req.respond(HTTP_OK, HGTYPE, body=rsp.data)
return []
Gregory Szorc
wireprototypes: move wire protocol response types to new module...
r36090 elif isinstance(rsp, wireprototypes.streamreslegacy):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 gen = rsp.gen
req.respond(HTTP_OK, HGTYPE)
return gen
Gregory Szorc
wireprototypes: move wire protocol response types to new module...
r36090 elif isinstance(rsp, wireprototypes.streamres):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 gen = rsp.gen
# This code for compression should not be streamres specific. It
# is here because we only compress streamres at the moment.
Gregory Szorc
wireprotoserver: move responsetype() out of http handler...
r36089 mediatype, engine, engineopts = _httpresponsetype(
repo.ui, req, rsp.prefer_uncompressed)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 gen = engine.compressstream(gen, engineopts)
if mediatype == HGTYPE2:
gen = genversion2(gen, engine, engineopts)
req.respond(HTTP_OK, mediatype)
return gen
Gregory Szorc
wireprototypes: move wire protocol response types to new module...
r36090 elif isinstance(rsp, wireprototypes.pushres):
Gregory Szorc
wireproto: use maybecapturestdio() for push responses (API)...
r36084 rsp = '%d\n%s' % (rsp.res, rsp.output)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 req.respond(HTTP_OK, HGTYPE, body=rsp)
return []
Gregory Szorc
wireprototypes: move wire protocol response types to new module...
r36090 elif isinstance(rsp, wireprototypes.pusherr):
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005 # This is the httplib workaround documented in _handlehttperror().
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 req.drain()
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 rsp = '0\n%s\n' % rsp.res
req.respond(HTTP_OK, HGTYPE, body=rsp)
return []
Gregory Szorc
wireprototypes: move wire protocol response types to new module...
r36090 elif isinstance(rsp, wireprototypes.ooberror):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 rsp = rsp.message
req.respond(HTTP_OK, HGERRTYPE, body=rsp)
return []
raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 def _handlehttperror(e, req, cmd):
"""Called when an ErrorResponse is raised during HTTP request processing."""
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005
# Clients using Python's httplib are stateful: the HTTP client
# won't process an HTTP response until all request data is
# sent to the server. The intent of this code is to ensure
# we always read HTTP request data from the client, thus
# ensuring httplib transitions to a state that allows it to read
# the HTTP response. In other words, it helps prevent deadlocks
# on clients using httplib.
if (req.env[r'REQUEST_METHOD'] == r'POST' and
# But not if Expect: 100-continue is being used.
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 (req.env.get('HTTP_EXPECT',
'').lower() != '100-continue') or
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005 # Or the non-httplib HTTP library is being advertised by
# the client.
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 req.env.get('X-HgHttp2', '')):
req.drain()
else:
req.headers.append((r'Connection', r'Close'))
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005 # TODO This response body assumes the failed command was
# "unbundle." That assumption is not always valid.
Augie Fackler
py3: get bytes-repr of network errors portably...
r36272 req.respond(e, HGTYPE, body='0\n%s\n' % pycompat.bytestr(e))
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004
return ''
Gregory Szorc
wireprotoserver: extract SSH response handling functions...
r36081 def _sshv1respondbytes(fout, value):
"""Send a bytes response for protocol version 1."""
fout.write('%d\n' % len(value))
fout.write(value)
fout.flush()
def _sshv1respondstream(fout, source):
write = fout.write
for chunk in source.gen:
write(chunk)
fout.flush()
def _sshv1respondooberror(fout, ferr, rsp):
ferr.write(b'%s\n-\n' % rsp)
ferr.flush()
fout.write(b'\n')
fout.flush()
Gregory Szorc
wireprotoserver: split ssh protocol handler and server...
r36082 class sshv1protocolhandler(baseprotocolhandler):
"""Handler for requests services via version 1 of SSH protocol."""
def __init__(self, ui, fin, fout):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 self._ui = ui
Gregory Szorc
wireprotoserver: split ssh protocol handler and server...
r36082 self._fin = fin
self._fout = fout
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
Gregory Szorc
wireprotoserver: make name part of protocol interface...
r35891 @property
def name(self):
Gregory Szorc
wireprotoserver: add version to SSH protocol names (API)...
r36092 return SSHV1
Gregory Szorc
wireprotoserver: make name part of protocol interface...
r35891
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 def getargs(self, args):
data = {}
keys = args.split()
for n in xrange(len(keys)):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 argline = self._fin.readline()[:-1]
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 arg, l = argline.split()
if arg not in keys:
raise error.Abort(_("unexpected parameter %r") % arg)
if arg == '*':
star = {}
for k in xrange(int(l)):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 argline = self._fin.readline()[:-1]
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 arg, l = argline.split()
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 val = self._fin.read(int(l))
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 star[arg] = val
data['*'] = star
else:
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 val = self._fin.read(int(l))
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 data[arg] = val
return [data[k] for k in keys]
Gregory Szorc
wireprotoserver: rename getfile() to forwardpayload() (API)...
r36087 def forwardpayload(self, fpout):
# The file is in the form:
#
# <chunk size>\n<chunk>
# ...
# 0\n
Gregory Szorc
wireprotoserver: extract SSH response handling functions...
r36081 _sshv1respondbytes(self._fout, b'')
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 count = int(self._fin.readline())
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 while count:
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 fpout.write(self._fin.read(count))
count = int(self._fin.readline())
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
Gregory Szorc
wireprotoserver: add context manager mechanism for redirecting stdio...
r36083 @contextlib.contextmanager
def mayberedirectstdio(self):
yield None
Gregory Szorc
wireprotoserver: rename _client to client (API)...
r36086 def client(self):
Gregory Szorc
wireprotoserver: split ssh protocol handler and server...
r36082 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
return 'remote:ssh:' + client
Gregory Szorc
wireprotoserver: handle SSH protocol version 2 upgrade requests...
r36233 class sshv2protocolhandler(sshv1protocolhandler):
"""Protocol handler for version 2 of the SSH protocol."""
Gregory Szorc
wireprotoserver: move SSH server operation to a standalone function...
r36232 def _runsshserver(ui, repo, fin, fout):
Gregory Szorc
wireprotoserver: handle SSH protocol version 2 upgrade requests...
r36233 # This function operates like a state machine of sorts. The following
# states are defined:
#
# protov1-serving
# Server is in protocol version 1 serving mode. Commands arrive on
# 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.
#
# And here are their transitions:
#
# 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.
Gregory Szorc
wireprotoserver: move SSH server operation to a standalone function...
r36232 state = 'protov1-serving'
proto = sshv1protocolhandler(ui, fin, fout)
Gregory Szorc
wireprotoserver: handle SSH protocol version 2 upgrade requests...
r36233 protoswitched = False
Gregory Szorc
wireprotoserver: move SSH server operation to a standalone function...
r36232
while True:
if state == 'protov1-serving':
# Commands are issued on new lines.
request = fin.readline()[:-1]
# Empty lines signal to terminate the connection.
if not request:
state = 'shutdown'
continue
Gregory Szorc
wireprotoserver: handle SSH protocol version 2 upgrade requests...
r36233 # 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 '
b'times')
state = 'shutdown'
continue
state = 'upgrade-initial'
continue
Gregory Szorc
wireprotoserver: move SSH server operation to a standalone function...
r36232 available = wireproto.commands.commandavailable(request, proto)
# This command isn't available. Send an empty response and go
# back to waiting for a new command.
if not available:
_sshv1respondbytes(fout, b'')
continue
rsp = wireproto.dispatch(repo, proto, request)
if isinstance(rsp, bytes):
_sshv1respondbytes(fout, rsp)
elif isinstance(rsp, wireprototypes.bytesresponse):
_sshv1respondbytes(fout, rsp.data)
elif isinstance(rsp, wireprototypes.streamres):
_sshv1respondstream(fout, rsp)
elif isinstance(rsp, wireprototypes.streamreslegacy):
_sshv1respondstream(fout, rsp)
elif isinstance(rsp, wireprototypes.pushres):
_sshv1respondbytes(fout, b'')
_sshv1respondbytes(fout, b'%d' % rsp.res)
elif isinstance(rsp, wireprototypes.pusherr):
_sshv1respondbytes(fout, rsp.res)
elif isinstance(rsp, wireprototypes.ooberror):
_sshv1respondooberror(fout, ui.ferr, rsp.message)
else:
raise error.ProgrammingError('unhandled response type from '
'wire protocol command: %s' % rsp)
Gregory Szorc
wireprotoserver: handle SSH protocol version 2 upgrade requests...
r36233 # For now, protocol version 2 serving just goes back to version 1.
elif state == 'protov2-serving':
state = 'protov1-serving'
continue
elif state == 'upgrade-initial':
# We should never transition into this state if we've switched
# protocols.
assert not protoswitched
assert proto.name == 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 = 'protov1-serving'
continue
# Send empty response if we don't support upgrading protocols.
if not ui.configbool('experimental', 'sshserver.support-v2'):
_sshv1respondbytes(fout, b'')
state = 'protov1-serving'
continue
try:
caps = urlreq.parseqs(caps)
except ValueError:
_sshv1respondbytes(fout, b'')
state = '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 = '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 = 'upgrade-v2-filter-legacy-handshake'
continue
elif state == '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: '
b'missing %s' % line)
ok = False
state = '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 = 'shutdown'
continue
state = 'upgrade-v2-finish'
continue
elif state == 'upgrade-v2-finish':
# Send the upgrade response.
fout.write(b'upgraded %s %s\n' % (token, SSHV2))
servercaps = wireproto.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 = 'protov2-serving'
continue
Gregory Szorc
wireprotoserver: move SSH server operation to a standalone function...
r36232 elif state == 'shutdown':
break
else:
raise error.ProgrammingError('unhandled ssh server state: %s' %
state)
Gregory Szorc
wireprotoserver: split ssh protocol handler and server...
r36082 class sshserver(object):
def __init__(self, ui, repo):
self._ui = ui
self._repo = repo
self._fin = ui.fin
self._fout = ui.fout
hook.redirect(True)
ui.fout = repo.ui.fout = ui.ferr
# Prevent insertion/deletion of CRs
util.setbinary(self._fin)
util.setbinary(self._fout)
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 def serve_forever(self):
Gregory Szorc
wireprotoserver: move SSH server operation to a standalone function...
r36232 _runsshserver(self._ui, self._repo, self._fin, self._fout)
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 sys.exit(0)