diff -r 57875cf423c9 -r 2372284d9457 mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py Sat Oct 05 10:29:34 2019 -0400 +++ b/mercurial/wireprotoserver.py Sun Oct 06 09:45:02 2019 -0400 @@ -21,9 +21,7 @@ wireprotov1server, wireprotov2server, ) -from .interfaces import ( - util as interfaceutil, -) +from .interfaces import util as interfaceutil from .utils import ( cborutil, compression, @@ -43,6 +41,7 @@ SSHV1 = wireprototypes.SSHV1 SSHV2 = wireprototypes.SSHV2 + def decodevaluefromheaders(req, headerprefix): """Decode a long value from multiple HTTP request headers. @@ -59,6 +58,7 @@ return ''.join(chunks) + @interfaceutil.implementer(wireprototypes.baseprotocolhandler) class httpv1protocolhandler(object): def __init__(self, req, ui, checkperm): @@ -90,8 +90,11 @@ args = self._req.qsparams.asdictoflists() postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0)) if postlen: - args.update(urlreq.parseqs( - self._req.bodyfh.read(postlen), keep_blank_values=True)) + args.update( + urlreq.parseqs( + self._req.bodyfh.read(postlen), keep_blank_values=True + ) + ) return args argvalue = decodevaluefromheaders(self._req, b'X-HgArg') @@ -132,13 +135,15 @@ return 'remote:%s:%s:%s' % ( self._req.urlscheme, urlreq.quote(self._req.remotehost or ''), - urlreq.quote(self._req.remoteuser or '')) + urlreq.quote(self._req.remoteuser or ''), + ) def addcapabilities(self, repo, caps): caps.append(b'batch') - caps.append('httpheader=%d' % - repo.ui.configint('server', 'maxhttpheaderlen')) + caps.append( + 'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen') + ) if repo.ui.configbool('experimental', 'httppostargs'): caps.append('httppostargs') @@ -146,11 +151,13 @@ # FUTURE advertise minrx and mintx after consulting config option caps.append('httpmediatype=0.1rx,0.1tx,0.2tx') - compengines = wireprototypes.supportedcompengines(repo.ui, - compression.SERVERROLE) + compengines = wireprototypes.supportedcompengines( + repo.ui, compression.SERVERROLE + ) if compengines: - comptypes = ','.join(urlreq.quote(e.wireprotosupport().name) - for e in compengines) + comptypes = ','.join( + urlreq.quote(e.wireprotosupport().name) for e in compengines + ) caps.append('compression=%s' % comptypes) return caps @@ -158,6 +165,7 @@ def checkperm(self, perm): return self._checkperm(perm) + # 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 @@ -165,6 +173,7 @@ def iscmd(cmd): return cmd in wireprotov1server.commands + def handlewsgirequest(rctx, req, res, checkperm): """Possibly process a wire protocol request. @@ -212,8 +221,9 @@ res.setbodybytes('0\n%s\n' % b'Not Found') return True - proto = httpv1protocolhandler(req, repo.ui, - lambda perm: checkperm(rctx, req, perm)) + proto = httpv1protocolhandler( + req, repo.ui, lambda perm: checkperm(rctx, req, perm) + ) # The permissions checker should be the only thing that can raise an # ErrorResponse. It is kind of a layer violation to catch an hgweb @@ -231,6 +241,7 @@ return True + def _availableapis(repo): apis = set() @@ -243,6 +254,7 @@ return apis + def handlewsgiapirequest(rctx, req, res, checkperm): """Handle requests to /api/*.""" assert req.dispatchparts[0] == b'api' @@ -266,8 +278,12 @@ if req.dispatchparts == [b'api']: res.status = b'200 OK' res.headers[b'Content-Type'] = b'text/plain' - lines = [_('APIs can be accessed at /api/, where can be ' - 'one of the following:\n')] + lines = [ + _( + 'APIs can be accessed at /api/, where can be ' + 'one of the following:\n' + ) + ] if availableapis: lines.extend(sorted(availableapis)) else: @@ -280,8 +296,10 @@ if proto not in API_HANDLERS: res.status = b'404 Not Found' res.headers[b'Content-Type'] = b'text/plain' - res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % ( - proto, b', '.join(sorted(availableapis)))) + res.setbodybytes( + _('Unknown API: %s\nKnown APIs: %s') + % (proto, b', '.join(sorted(availableapis))) + ) return if proto not in availableapis: @@ -290,8 +308,10 @@ res.setbodybytes(_('API %s not enabled\n') % proto) return - API_HANDLERS[proto]['handler'](rctx, req, res, checkperm, - req.dispatchparts[2:]) + API_HANDLERS[proto]['handler']( + rctx, req, res, checkperm, req.dispatchparts[2:] + ) + # Maps API name to metadata so custom API can be registered. # Keys are: @@ -312,6 +332,7 @@ }, } + def _httpresponsetype(ui, proto, prefer_uncompressed): """Determine the appropriate response type and compression settings. @@ -327,8 +348,9 @@ # Now find an agreed upon compression format. compformats = wireprotov1server.clientcompressionsupport(proto) - for engine in wireprototypes.supportedcompengines(ui, - compression.SERVERROLE): + for engine in wireprototypes.supportedcompengines( + ui, compression.SERVERROLE + ): if engine.wireprotosupport().name in compformats: opts = {} level = ui.configint('server', '%slevel' % engine.name()) @@ -346,6 +368,7 @@ opts = {'level': ui.configint('server', 'zliblevel')} return HGTYPE, util.compengines['zlib'], opts + def processcapabilitieshandshake(repo, req, res, proto): """Called during a ?cmd=capabilities request. @@ -394,6 +417,7 @@ return True + def _callhttp(repo, req, res, proto, cmd): # Avoid cycle involving hg module. from .hgweb import common as hgwebcommon @@ -423,16 +447,19 @@ res.setbodygen(bodygen) if not wireprotov1server.commands.commandavailable(cmd, proto): - setresponse(HTTP_OK, HGERRTYPE, - _('requested wire protocol command is not available over ' - 'HTTP')) + setresponse( + HTTP_OK, + HGERRTYPE, + _('requested wire protocol command is not available over ' 'HTTP'), + ) return proto.checkperm(wireprotov1server.commands[cmd].permission) # Possibly handle a modern client wanting to switch protocols. - if (cmd == 'capabilities' and - processcapabilitieshandshake(repo, req, res, proto)): + if cmd == 'capabilities' and processcapabilitieshandshake( + repo, req, res, proto + ): return @@ -450,7 +477,8 @@ # This code for compression should not be streamres specific. It # is here because we only compress streamres at the moment. mediatype, engine, engineopts = _httpresponsetype( - repo.ui, proto, rsp.prefer_uncompressed) + repo.ui, proto, rsp.prefer_uncompressed + ) gen = engine.compressstream(gen, engineopts) if mediatype == HGTYPE2: @@ -469,27 +497,32 @@ else: raise error.ProgrammingError('hgweb.protocol internal failure', rsp) + 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() + @interfaceutil.implementer(wireprototypes.baseprotocolhandler) class sshv1protocolhandler(object): """Handler for requests services via version 1 of SSH protocol.""" + def __init__(self, ui, fin, fout): self._ui = ui self._fin = fin @@ -557,6 +590,7 @@ def checkperm(self, perm): pass + class sshv2protocolhandler(sshv1protocolhandler): """Protocol handler for version 2 of the SSH protocol.""" @@ -567,6 +601,7 @@ 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: @@ -638,9 +673,11 @@ # handle it. if request.startswith(b'upgrade '): if protoswitched: - _sshv1respondooberror(fout, ui.ferr, - b'cannot upgrade protocols multiple ' - b'times') + _sshv1respondooberror( + fout, + ui.ferr, + b'cannot upgrade protocols multiple ' b'times', + ) state = 'shutdown' continue @@ -648,7 +685,8 @@ continue available = wireprotov1server.commands.commandavailable( - request, proto) + request, proto + ) # This command isn't available. Send an empty response and go # back to waiting for a new command. @@ -676,8 +714,10 @@ elif isinstance(rsp, wireprototypes.ooberror): _sshv1respondooberror(fout, ui.ferr, rsp.message) else: - raise error.ProgrammingError('unhandled response type from ' - 'wire protocol command: %s' % rsp) + raise error.ProgrammingError( + 'unhandled response type from ' + 'wire protocol command: %s' % rsp + ) # For now, protocol version 2 serving just goes back to version 1. elif state == 'protov2-serving': @@ -741,9 +781,11 @@ request = fin.readline()[:-1] if request != line: - _sshv1respondooberror(fout, ui.ferr, - b'malformed handshake protocol: ' - b'missing %s' % line) + _sshv1respondooberror( + fout, + ui.ferr, + b'malformed handshake protocol: ' b'missing %s' % line, + ) ok = False state = 'shutdown' break @@ -753,9 +795,12 @@ 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') + _sshv1respondooberror( + fout, + ui.ferr, + b'malformed handshake protocol: ' + b'missing between argument value', + ) state = 'shutdown' continue @@ -780,8 +825,10 @@ break else: - raise error.ProgrammingError('unhandled ssh server state: %s' % - state) + raise error.ProgrammingError( + 'unhandled ssh server state: %s' % state + ) + class sshserver(object): def __init__(self, ui, repo, logfh=None): @@ -792,9 +839,11 @@ # Log write I/O to stdout and stderr if configured. if logfh: self._fout = util.makeloggingfileobject( - logfh, self._fout, 'o', logdata=True) + logfh, self._fout, 'o', logdata=True + ) ui.ferr = util.makeloggingfileobject( - logfh, ui.ferr, 'e', logdata=True) + logfh, ui.ferr, 'e', logdata=True + ) def serve_forever(self): self.serveuntil(threading.Event())