##// END OF EJS Templates
wireproto: raise ProgrammingError instead of Abort...
wireproto: raise ProgrammingError instead of Abort This isn't a user-facing error and can only be caused by bad Python code. Thanks to Yuya for spotting this. Differential Revision: https://phab.mercurial-scm.org/D2777

File last commit:

r36822:b9b968e2 default
r36858:ef68493d default
Show More
wireprotoserver.py
669 lines | 22.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: 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: ability to run an SSH server until an event is set...
r36540 import threading
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
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 SSHV1 = wireprototypes.SSHV1
SSHV2 = wireprototypes.SSHV2
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 def decodevaluefromheaders(wsgireq, headerprefix):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 """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:
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 v = wsgireq.env.get(r'HTTP_%s_%d' % (prefix, i))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 if v is None:
break
chunks.append(pycompat.bytesurl(v))
i += 1
return ''.join(chunks)
Gregory Szorc
wireprototypes: move baseprotocolhandler from wireprotoserver...
r36389 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 def __init__(self, wsgireq, ui, checkperm):
self._wsgireq = wsgireq
Gregory Szorc
wireprotoserver: make attributes private...
r35884 self._ui = ui
Gregory Szorc
wireproto: formalize permissions checking as part of protocol interface...
r36819 self._checkperm = checkperm
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):
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy())
postlen = int(self._wsgireq.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
hgweb: rename req to wsgireq...
r36822 self._wsgireq.read(postlen), keep_blank_values=True))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 return args
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 argvalue = decodevaluefromheaders(self._wsgireq, 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):
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 if r'HTTP_CONTENT_LENGTH' in self._wsgireq.env:
length = int(self._wsgireq.env[r'HTTP_CONTENT_LENGTH'])
Augie Fackler
wireprotoserver: py3 helpfully calls adds HTTP_ to CONTENT_LENGTH...
r36294 else:
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 length = int(self._wsgireq.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
hgweb: rename req to wsgireq...
r36822 length -= int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0))
for s in util.filechunkiter(self._wsgireq, 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
hgweb: rename req to wsgireq...
r36822 self._wsgireq.env.get('wsgi.url_scheme') or 'http',
urlreq.quote(self._wsgireq.env.get('REMOTE_HOST', '')),
urlreq.quote(self._wsgireq.env.get('REMOTE_USER', '')))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
Gregory Szorc
wireproto: add transport specific capabilities in the transport...
r36631 def addcapabilities(self, repo, caps):
caps.append('httpheader=%d' %
repo.ui.configint('server', 'maxhttpheaderlen'))
if repo.ui.configbool('experimental', 'httppostargs'):
caps.append('httppostargs')
# FUTURE advertise 0.2rx once support is implemented
# FUTURE advertise minrx and mintx after consulting config option
caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
if compengines:
comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
for e in compengines)
caps.append('compression=%s' % comptypes)
return caps
Gregory Szorc
wireproto: formalize permissions checking as part of protocol interface...
r36819 def checkperm(self, perm):
return self._checkperm(perm)
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 all wire protocol handling logic out of hgweb...
r36830 def handlewsgirequest(rctx, wsgireq, req, checkperm):
"""Possibly process a wire protocol request.
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002
Gregory Szorc
wireprotoserver: move all wire protocol handling logic out of hgweb...
r36830 If the current request is a wire protocol request, the request is
processed by this function.
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 ``wsgireq`` is a ``wsgirequest`` instance.
Gregory Szorc
hgweb: only recognize wire protocol commands from query string (BC)...
r36828 ``req`` is a ``parsedrequest`` instance.
Gregory Szorc
wireprotoserver: move all wire protocol handling logic out of hgweb...
r36830
Returns a 2-tuple of (bool, response) where the 1st element indicates
whether the request was handled and the 2nd element is a return
value for a WSGI application (often a generator of bytes).
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002 """
Gregory Szorc
wireprotoserver: move all wire protocol handling logic out of hgweb...
r36830 # Avoid cycle involving hg module.
from .hgweb import common as hgwebcommon
Gregory Szorc
wireproto: formalize permissions checking as part of protocol interface...
r36819 repo = rctx.repo
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002 # 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.
Gregory Szorc
hgweb: only recognize wire protocol commands from query string (BC)...
r36828 if 'cmd' not in req.querystringdict:
Gregory Szorc
wireprotoserver: move all wire protocol handling logic out of hgweb...
r36830 return False, None
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002
Gregory Szorc
hgweb: only recognize wire protocol commands from query string (BC)...
r36828 cmd = req.querystringdict['cmd'][0]
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002
# 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 all wire protocol handling logic out of hgweb...
r36830 return False, None
# The "cmd" query string argument is only valid on the root path of the
# repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
# like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
# in this case. We send an HTTP 404 for backwards compatibility reasons.
if req.dispatchpath:
res = _handlehttperror(
hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq,
cmd)
return True, res
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 proto = httpv1protocolhandler(wsgireq, repo.ui,
lambda perm: checkperm(rctx, wsgireq, perm))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
Gregory Szorc
wireprotoserver: move all wire protocol handling logic out of hgweb...
r36830 # The permissions checker should be the only thing that can raise an
# ErrorResponse. It is kind of a layer violation to catch an hgweb
# exception here. So consider refactoring into a exception type that
# is associated with the wire protocol.
try:
res = _callhttp(repo, wsgireq, proto, cmd)
except hgwebcommon.ErrorResponse as e:
res = _handlehttperror(e, wsgireq, cmd)
return True, res
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 def _httpresponsetype(ui, wsgireq, prefer_uncompressed):
Gregory Szorc
wireprotoserver: move responsetype() out of http handler...
r36089 """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.
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 protocaps = decodevaluefromheaders(wsgireq, r'X-HgProto').split(' ')
Gregory Szorc
wireprotoserver: move responsetype() out of http handler...
r36089
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
hgweb: rename req to wsgireq...
r36822 def _callhttp(repo, wsgireq, 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
wireproto: function for testing if wire protocol command is available...
r36000 if not wireproto.commands.commandavailable(cmd, proto):
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 wsgireq.respond(HTTP_OK, HGERRTYPE,
body=_('requested wire protocol command is not '
'available over HTTP'))
Gregory Szorc
wireproto: function for testing if wire protocol command is available...
r36000 return []
Gregory Szorc
wireproto: formalize permissions checking as part of protocol interface...
r36819 proto.checkperm(wireproto.commands[cmd].permission)
Gregory Szorc
wireprotoserver: check permissions in main dispatch function...
r36817
Gregory Szorc
wireprotoserver: check if command available before calling it...
r36816 rsp = wireproto.dispatch(repo, proto, cmd)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 if isinstance(rsp, bytes):
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 return []
Gregory Szorc
wireproto: introduce type for raw byte responses (API)...
r36091 elif isinstance(rsp, wireprototypes.bytesresponse):
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp.data)
Gregory Szorc
wireproto: introduce type for raw byte responses (API)...
r36091 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
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 wsgireq.respond(HTTP_OK, HGTYPE)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 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(
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 repo.ui, wsgireq, 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)
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 wsgireq.respond(HTTP_OK, mediatype)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 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
hgweb: rename req to wsgireq...
r36822 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 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
hgweb: rename req to wsgireq...
r36822 wsgireq.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
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 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
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 wsgireq.respond(HTTP_OK, HGERRTYPE, body=rsp)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 return []
raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 def _handlehttperror(e, wsgireq, cmd):
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 """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.
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 if (wsgireq.env[r'REQUEST_METHOD'] == r'POST' and
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005 # But not if Expect: 100-continue is being used.
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 (wsgireq.env.get('HTTP_EXPECT',
Gregory Szorc
wireprotoserver: remove broken optimization for non-httplib client...
r36831 '').lower() != '100-continue')):
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 wsgireq.drain()
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 else:
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 wsgireq.headers.append((r'Connection', r'Close'))
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004
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.
Gregory Szorc
hgweb: rename req to wsgireq...
r36822 wsgireq.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
wireprototypes: move baseprotocolhandler from wireprotoserver...
r36389 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
Gregory Szorc
wireprotoserver: split ssh protocol handler and server...
r36082 """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: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 return wireprototypes.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):
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390 # We initially send an empty response. This tells the client it is
# OK to start sending data. If a client sees any other response, it
# interprets it as an error.
_sshv1respondbytes(self._fout, b'')
Gregory Szorc
wireprotoserver: rename getfile() to forwardpayload() (API)...
r36087 # The file is in the form:
#
# <chunk size>\n<chunk>
# ...
# 0\n
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
wireproto: add transport specific capabilities in the transport...
r36631 def addcapabilities(self, repo, caps):
return caps
Gregory Szorc
wireproto: formalize permissions checking as part of protocol interface...
r36819 def checkperm(self, perm):
pass
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: identify requests via version 2 of SSH protocol as such...
r36628 @property
def name(self):
return wireprototypes.SSHV2
Gregory Szorc
wireprotoserver: ability to run an SSH server until an event is set...
r36540 def _runsshserver(ui, repo, fin, fout, ev):
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
Gregory Szorc
wireprotoserver: ability to run an SSH server until an event is set...
r36540 while not ev.is_set():
Gregory Szorc
wireprotoserver: move SSH server operation to a standalone function...
r36232 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
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 assert proto.name == wireprototypes.SSHV1
Gregory Szorc
wireprotoserver: handle SSH protocol version 2 upgrade requests...
r36233
# 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):
Gregory Szorc
wireprotoserver: support logging SSH server I/O to a file descriptor...
r36543 def __init__(self, ui, repo, logfh=None):
Gregory Szorc
wireprotoserver: split ssh protocol handler and server...
r36082 self._ui = ui
self._repo = repo
self._fin = ui.fin
self._fout = ui.fout
Gregory Szorc
wireprotoserver: support logging SSH server I/O to a file descriptor...
r36543 # Log write I/O to stdout and stderr if configured.
if logfh:
self._fout = util.makeloggingfileobject(
logfh, self._fout, 'o', logdata=True)
ui.ferr = util.makeloggingfileobject(
logfh, ui.ferr, 'e', logdata=True)
Gregory Szorc
wireprotoserver: split ssh protocol handler and server...
r36082 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: ability to run an SSH server until an event is set...
r36540 self.serveuntil(threading.Event())
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 sys.exit(0)
Gregory Szorc
wireprotoserver: ability to run an SSH server until an event is set...
r36540
def serveuntil(self, ev):
"""Serve until a threading.Event is set."""
_runsshserver(self._ui, self._repo, self._fin, self._fout, ev)