mercurial/wireprotoserver.py
changeset 43076 2372284d9457
parent 42896 7e19b640c53e
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    19     util,
    19     util,
    20     wireprototypes,
    20     wireprototypes,
    21     wireprotov1server,
    21     wireprotov1server,
    22     wireprotov2server,
    22     wireprotov2server,
    23 )
    23 )
    24 from .interfaces import (
    24 from .interfaces import util as interfaceutil
    25     util as interfaceutil,
       
    26 )
       
    27 from .utils import (
    25 from .utils import (
    28     cborutil,
    26     cborutil,
    29     compression,
    27     compression,
    30 )
    28 )
    31 
    29 
    40 HGTYPE2 = 'application/mercurial-0.2'
    38 HGTYPE2 = 'application/mercurial-0.2'
    41 HGERRTYPE = 'application/hg-error'
    39 HGERRTYPE = 'application/hg-error'
    42 
    40 
    43 SSHV1 = wireprototypes.SSHV1
    41 SSHV1 = wireprototypes.SSHV1
    44 SSHV2 = wireprototypes.SSHV2
    42 SSHV2 = wireprototypes.SSHV2
       
    43 
    45 
    44 
    46 def decodevaluefromheaders(req, headerprefix):
    45 def decodevaluefromheaders(req, headerprefix):
    47     """Decode a long value from multiple HTTP request headers.
    46     """Decode a long value from multiple HTTP request headers.
    48 
    47 
    49     Returns the value as a bytes, not a str.
    48     Returns the value as a bytes, not a str.
    56             break
    55             break
    57         chunks.append(pycompat.bytesurl(v))
    56         chunks.append(pycompat.bytesurl(v))
    58         i += 1
    57         i += 1
    59 
    58 
    60     return ''.join(chunks)
    59     return ''.join(chunks)
       
    60 
    61 
    61 
    62 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
    62 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
    63 class httpv1protocolhandler(object):
    63 class httpv1protocolhandler(object):
    64     def __init__(self, req, ui, checkperm):
    64     def __init__(self, req, ui, checkperm):
    65         self._req = req
    65         self._req = req
    88 
    88 
    89     def _args(self):
    89     def _args(self):
    90         args = self._req.qsparams.asdictoflists()
    90         args = self._req.qsparams.asdictoflists()
    91         postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
    91         postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
    92         if postlen:
    92         if postlen:
    93             args.update(urlreq.parseqs(
    93             args.update(
    94                 self._req.bodyfh.read(postlen), keep_blank_values=True))
    94                 urlreq.parseqs(
       
    95                     self._req.bodyfh.read(postlen), keep_blank_values=True
       
    96                 )
       
    97             )
    95             return args
    98             return args
    96 
    99 
    97         argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
   100         argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
    98         args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
   101         args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
    99         return args
   102         return args
   130 
   133 
   131     def client(self):
   134     def client(self):
   132         return 'remote:%s:%s:%s' % (
   135         return 'remote:%s:%s:%s' % (
   133             self._req.urlscheme,
   136             self._req.urlscheme,
   134             urlreq.quote(self._req.remotehost or ''),
   137             urlreq.quote(self._req.remotehost or ''),
   135             urlreq.quote(self._req.remoteuser or ''))
   138             urlreq.quote(self._req.remoteuser or ''),
       
   139         )
   136 
   140 
   137     def addcapabilities(self, repo, caps):
   141     def addcapabilities(self, repo, caps):
   138         caps.append(b'batch')
   142         caps.append(b'batch')
   139 
   143 
   140         caps.append('httpheader=%d' %
   144         caps.append(
   141                     repo.ui.configint('server', 'maxhttpheaderlen'))
   145             'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen')
       
   146         )
   142         if repo.ui.configbool('experimental', 'httppostargs'):
   147         if repo.ui.configbool('experimental', 'httppostargs'):
   143             caps.append('httppostargs')
   148             caps.append('httppostargs')
   144 
   149 
   145         # FUTURE advertise 0.2rx once support is implemented
   150         # FUTURE advertise 0.2rx once support is implemented
   146         # FUTURE advertise minrx and mintx after consulting config option
   151         # FUTURE advertise minrx and mintx after consulting config option
   147         caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
   152         caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
   148 
   153 
   149         compengines = wireprototypes.supportedcompengines(repo.ui,
   154         compengines = wireprototypes.supportedcompengines(
   150             compression.SERVERROLE)
   155             repo.ui, compression.SERVERROLE
       
   156         )
   151         if compengines:
   157         if compengines:
   152             comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
   158             comptypes = ','.join(
   153                                  for e in compengines)
   159                 urlreq.quote(e.wireprotosupport().name) for e in compengines
       
   160             )
   154             caps.append('compression=%s' % comptypes)
   161             caps.append('compression=%s' % comptypes)
   155 
   162 
   156         return caps
   163         return caps
   157 
   164 
   158     def checkperm(self, perm):
   165     def checkperm(self, perm):
   159         return self._checkperm(perm)
   166         return self._checkperm(perm)
       
   167 
   160 
   168 
   161 # This method exists mostly so that extensions like remotefilelog can
   169 # This method exists mostly so that extensions like remotefilelog can
   162 # disable a kludgey legacy method only over http. As of early 2018,
   170 # disable a kludgey legacy method only over http. As of early 2018,
   163 # there are no other known users, so with any luck we can discard this
   171 # there are no other known users, so with any luck we can discard this
   164 # hook if remotefilelog becomes a first-party extension.
   172 # hook if remotefilelog becomes a first-party extension.
   165 def iscmd(cmd):
   173 def iscmd(cmd):
   166     return cmd in wireprotov1server.commands
   174     return cmd in wireprotov1server.commands
       
   175 
   167 
   176 
   168 def handlewsgirequest(rctx, req, res, checkperm):
   177 def handlewsgirequest(rctx, req, res, checkperm):
   169     """Possibly process a wire protocol request.
   178     """Possibly process a wire protocol request.
   170 
   179 
   171     If the current request is a wire protocol request, the request is
   180     If the current request is a wire protocol request, the request is
   210         # TODO This is not a good response to issue for this request. This
   219         # TODO This is not a good response to issue for this request. This
   211         # is mostly for BC for now.
   220         # is mostly for BC for now.
   212         res.setbodybytes('0\n%s\n' % b'Not Found')
   221         res.setbodybytes('0\n%s\n' % b'Not Found')
   213         return True
   222         return True
   214 
   223 
   215     proto = httpv1protocolhandler(req, repo.ui,
   224     proto = httpv1protocolhandler(
   216                                   lambda perm: checkperm(rctx, req, perm))
   225         req, repo.ui, lambda perm: checkperm(rctx, req, perm)
       
   226     )
   217 
   227 
   218     # The permissions checker should be the only thing that can raise an
   228     # The permissions checker should be the only thing that can raise an
   219     # ErrorResponse. It is kind of a layer violation to catch an hgweb
   229     # ErrorResponse. It is kind of a layer violation to catch an hgweb
   220     # exception here. So consider refactoring into a exception type that
   230     # exception here. So consider refactoring into a exception type that
   221     # is associated with the wire protocol.
   231     # is associated with the wire protocol.
   229         # "unbundle." That assumption is not always valid.
   239         # "unbundle." That assumption is not always valid.
   230         res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
   240         res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
   231 
   241 
   232     return True
   242     return True
   233 
   243 
       
   244 
   234 def _availableapis(repo):
   245 def _availableapis(repo):
   235     apis = set()
   246     apis = set()
   236 
   247 
   237     # Registered APIs are made available via config options of the name of
   248     # Registered APIs are made available via config options of the name of
   238     # the protocol.
   249     # the protocol.
   240         section, option = v['config']
   251         section, option = v['config']
   241         if repo.ui.configbool(section, option):
   252         if repo.ui.configbool(section, option):
   242             apis.add(k)
   253             apis.add(k)
   243 
   254 
   244     return apis
   255     return apis
       
   256 
   245 
   257 
   246 def handlewsgiapirequest(rctx, req, res, checkperm):
   258 def handlewsgiapirequest(rctx, req, res, checkperm):
   247     """Handle requests to /api/*."""
   259     """Handle requests to /api/*."""
   248     assert req.dispatchparts[0] == b'api'
   260     assert req.dispatchparts[0] == b'api'
   249 
   261 
   264 
   276 
   265     # Requests to /api/ list available APIs.
   277     # Requests to /api/ list available APIs.
   266     if req.dispatchparts == [b'api']:
   278     if req.dispatchparts == [b'api']:
   267         res.status = b'200 OK'
   279         res.status = b'200 OK'
   268         res.headers[b'Content-Type'] = b'text/plain'
   280         res.headers[b'Content-Type'] = b'text/plain'
   269         lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
   281         lines = [
   270                    'one of the following:\n')]
   282             _(
       
   283                 'APIs can be accessed at /api/<name>, where <name> can be '
       
   284                 'one of the following:\n'
       
   285             )
       
   286         ]
   271         if availableapis:
   287         if availableapis:
   272             lines.extend(sorted(availableapis))
   288             lines.extend(sorted(availableapis))
   273         else:
   289         else:
   274             lines.append(_('(no available APIs)\n'))
   290             lines.append(_('(no available APIs)\n'))
   275         res.setbodybytes(b'\n'.join(lines))
   291         res.setbodybytes(b'\n'.join(lines))
   278     proto = req.dispatchparts[1]
   294     proto = req.dispatchparts[1]
   279 
   295 
   280     if proto not in API_HANDLERS:
   296     if proto not in API_HANDLERS:
   281         res.status = b'404 Not Found'
   297         res.status = b'404 Not Found'
   282         res.headers[b'Content-Type'] = b'text/plain'
   298         res.headers[b'Content-Type'] = b'text/plain'
   283         res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
   299         res.setbodybytes(
   284             proto, b', '.join(sorted(availableapis))))
   300             _('Unknown API: %s\nKnown APIs: %s')
       
   301             % (proto, b', '.join(sorted(availableapis)))
       
   302         )
   285         return
   303         return
   286 
   304 
   287     if proto not in availableapis:
   305     if proto not in availableapis:
   288         res.status = b'404 Not Found'
   306         res.status = b'404 Not Found'
   289         res.headers[b'Content-Type'] = b'text/plain'
   307         res.headers[b'Content-Type'] = b'text/plain'
   290         res.setbodybytes(_('API %s not enabled\n') % proto)
   308         res.setbodybytes(_('API %s not enabled\n') % proto)
   291         return
   309         return
   292 
   310 
   293     API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
   311     API_HANDLERS[proto]['handler'](
   294                                    req.dispatchparts[2:])
   312         rctx, req, res, checkperm, req.dispatchparts[2:]
       
   313     )
       
   314 
   295 
   315 
   296 # Maps API name to metadata so custom API can be registered.
   316 # Maps API name to metadata so custom API can be registered.
   297 # Keys are:
   317 # Keys are:
   298 #
   318 #
   299 # config
   319 # config
   310         'handler': wireprotov2server.handlehttpv2request,
   330         'handler': wireprotov2server.handlehttpv2request,
   311         'apidescriptor': wireprotov2server.httpv2apidescriptor,
   331         'apidescriptor': wireprotov2server.httpv2apidescriptor,
   312     },
   332     },
   313 }
   333 }
   314 
   334 
       
   335 
   315 def _httpresponsetype(ui, proto, prefer_uncompressed):
   336 def _httpresponsetype(ui, proto, prefer_uncompressed):
   316     """Determine the appropriate response type and compression settings.
   337     """Determine the appropriate response type and compression settings.
   317 
   338 
   318     Returns a tuple of (mediatype, compengine, engineopts).
   339     Returns a tuple of (mediatype, compengine, engineopts).
   319     """
   340     """
   325         if prefer_uncompressed:
   346         if prefer_uncompressed:
   326             return HGTYPE2, compression._noopengine(), {}
   347             return HGTYPE2, compression._noopengine(), {}
   327 
   348 
   328         # Now find an agreed upon compression format.
   349         # Now find an agreed upon compression format.
   329         compformats = wireprotov1server.clientcompressionsupport(proto)
   350         compformats = wireprotov1server.clientcompressionsupport(proto)
   330         for engine in wireprototypes.supportedcompengines(ui,
   351         for engine in wireprototypes.supportedcompengines(
   331                 compression.SERVERROLE):
   352             ui, compression.SERVERROLE
       
   353         ):
   332             if engine.wireprotosupport().name in compformats:
   354             if engine.wireprotosupport().name in compformats:
   333                 opts = {}
   355                 opts = {}
   334                 level = ui.configint('server', '%slevel' % engine.name())
   356                 level = ui.configint('server', '%slevel' % engine.name())
   335                 if level is not None:
   357                 if level is not None:
   336                     opts['level'] = level
   358                     opts['level'] = level
   343     # Don't allow untrusted settings because disabling compression or
   365     # Don't allow untrusted settings because disabling compression or
   344     # setting a very high compression level could lead to flooding
   366     # setting a very high compression level could lead to flooding
   345     # the server's network or CPU.
   367     # the server's network or CPU.
   346     opts = {'level': ui.configint('server', 'zliblevel')}
   368     opts = {'level': ui.configint('server', 'zliblevel')}
   347     return HGTYPE, util.compengines['zlib'], opts
   369     return HGTYPE, util.compengines['zlib'], opts
       
   370 
   348 
   371 
   349 def processcapabilitieshandshake(repo, req, res, proto):
   372 def processcapabilitieshandshake(repo, req, res, proto):
   350     """Called during a ?cmd=capabilities request.
   373     """Called during a ?cmd=capabilities request.
   351 
   374 
   352     If the client is advertising support for a newer protocol, we send
   375     If the client is advertising support for a newer protocol, we send
   392     res.headers[b'Content-Type'] = b'application/mercurial-cbor'
   415     res.headers[b'Content-Type'] = b'application/mercurial-cbor'
   393     res.setbodybytes(b''.join(cborutil.streamencode(m)))
   416     res.setbodybytes(b''.join(cborutil.streamencode(m)))
   394 
   417 
   395     return True
   418     return True
   396 
   419 
       
   420 
   397 def _callhttp(repo, req, res, proto, cmd):
   421 def _callhttp(repo, req, res, proto, cmd):
   398     # Avoid cycle involving hg module.
   422     # Avoid cycle involving hg module.
   399     from .hgweb import common as hgwebcommon
   423     from .hgweb import common as hgwebcommon
   400 
   424 
   401     def genversion2(gen, engine, engineopts):
   425     def genversion2(gen, engine, engineopts):
   421             res.setbodybytes(bodybytes)
   445             res.setbodybytes(bodybytes)
   422         if bodygen is not None:
   446         if bodygen is not None:
   423             res.setbodygen(bodygen)
   447             res.setbodygen(bodygen)
   424 
   448 
   425     if not wireprotov1server.commands.commandavailable(cmd, proto):
   449     if not wireprotov1server.commands.commandavailable(cmd, proto):
   426         setresponse(HTTP_OK, HGERRTYPE,
   450         setresponse(
   427                     _('requested wire protocol command is not available over '
   451             HTTP_OK,
   428                       'HTTP'))
   452             HGERRTYPE,
       
   453             _('requested wire protocol command is not available over ' 'HTTP'),
       
   454         )
   429         return
   455         return
   430 
   456 
   431     proto.checkperm(wireprotov1server.commands[cmd].permission)
   457     proto.checkperm(wireprotov1server.commands[cmd].permission)
   432 
   458 
   433     # Possibly handle a modern client wanting to switch protocols.
   459     # Possibly handle a modern client wanting to switch protocols.
   434     if (cmd == 'capabilities' and
   460     if cmd == 'capabilities' and processcapabilitieshandshake(
   435         processcapabilitieshandshake(repo, req, res, proto)):
   461         repo, req, res, proto
       
   462     ):
   436 
   463 
   437         return
   464         return
   438 
   465 
   439     rsp = wireprotov1server.dispatch(repo, proto, cmd)
   466     rsp = wireprotov1server.dispatch(repo, proto, cmd)
   440 
   467 
   448         gen = rsp.gen
   475         gen = rsp.gen
   449 
   476 
   450         # This code for compression should not be streamres specific. It
   477         # This code for compression should not be streamres specific. It
   451         # is here because we only compress streamres at the moment.
   478         # is here because we only compress streamres at the moment.
   452         mediatype, engine, engineopts = _httpresponsetype(
   479         mediatype, engine, engineopts = _httpresponsetype(
   453             repo.ui, proto, rsp.prefer_uncompressed)
   480             repo.ui, proto, rsp.prefer_uncompressed
       
   481         )
   454         gen = engine.compressstream(gen, engineopts)
   482         gen = engine.compressstream(gen, engineopts)
   455 
   483 
   456         if mediatype == HGTYPE2:
   484         if mediatype == HGTYPE2:
   457             gen = genversion2(gen, engine, engineopts)
   485             gen = genversion2(gen, engine, engineopts)
   458 
   486 
   467     elif isinstance(rsp, wireprototypes.ooberror):
   495     elif isinstance(rsp, wireprototypes.ooberror):
   468         setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
   496         setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
   469     else:
   497     else:
   470         raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
   498         raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
   471 
   499 
       
   500 
   472 def _sshv1respondbytes(fout, value):
   501 def _sshv1respondbytes(fout, value):
   473     """Send a bytes response for protocol version 1."""
   502     """Send a bytes response for protocol version 1."""
   474     fout.write('%d\n' % len(value))
   503     fout.write('%d\n' % len(value))
   475     fout.write(value)
   504     fout.write(value)
   476     fout.flush()
   505     fout.flush()
   477 
   506 
       
   507 
   478 def _sshv1respondstream(fout, source):
   508 def _sshv1respondstream(fout, source):
   479     write = fout.write
   509     write = fout.write
   480     for chunk in source.gen:
   510     for chunk in source.gen:
   481         write(chunk)
   511         write(chunk)
   482     fout.flush()
   512     fout.flush()
   483 
   513 
       
   514 
   484 def _sshv1respondooberror(fout, ferr, rsp):
   515 def _sshv1respondooberror(fout, ferr, rsp):
   485     ferr.write(b'%s\n-\n' % rsp)
   516     ferr.write(b'%s\n-\n' % rsp)
   486     ferr.flush()
   517     ferr.flush()
   487     fout.write(b'\n')
   518     fout.write(b'\n')
   488     fout.flush()
   519     fout.flush()
   489 
   520 
       
   521 
   490 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
   522 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
   491 class sshv1protocolhandler(object):
   523 class sshv1protocolhandler(object):
   492     """Handler for requests services via version 1 of SSH protocol."""
   524     """Handler for requests services via version 1 of SSH protocol."""
       
   525 
   493     def __init__(self, ui, fin, fout):
   526     def __init__(self, ui, fin, fout):
   494         self._ui = ui
   527         self._ui = ui
   495         self._fin = fin
   528         self._fin = fin
   496         self._fout = fout
   529         self._fout = fout
   497         self._protocaps = set()
   530         self._protocaps = set()
   555         return caps
   588         return caps
   556 
   589 
   557     def checkperm(self, perm):
   590     def checkperm(self, perm):
   558         pass
   591         pass
   559 
   592 
       
   593 
   560 class sshv2protocolhandler(sshv1protocolhandler):
   594 class sshv2protocolhandler(sshv1protocolhandler):
   561     """Protocol handler for version 2 of the SSH protocol."""
   595     """Protocol handler for version 2 of the SSH protocol."""
   562 
   596 
   563     @property
   597     @property
   564     def name(self):
   598     def name(self):
   565         return wireprototypes.SSHV2
   599         return wireprototypes.SSHV2
   566 
   600 
   567     def addcapabilities(self, repo, caps):
   601     def addcapabilities(self, repo, caps):
   568         return caps
   602         return caps
       
   603 
   569 
   604 
   570 def _runsshserver(ui, repo, fin, fout, ev):
   605 def _runsshserver(ui, repo, fin, fout, ev):
   571     # This function operates like a state machine of sorts. The following
   606     # This function operates like a state machine of sorts. The following
   572     # states are defined:
   607     # states are defined:
   573     #
   608     #
   636 
   671 
   637             # It looks like a protocol upgrade request. Transition state to
   672             # It looks like a protocol upgrade request. Transition state to
   638             # handle it.
   673             # handle it.
   639             if request.startswith(b'upgrade '):
   674             if request.startswith(b'upgrade '):
   640                 if protoswitched:
   675                 if protoswitched:
   641                     _sshv1respondooberror(fout, ui.ferr,
   676                     _sshv1respondooberror(
   642                                           b'cannot upgrade protocols multiple '
   677                         fout,
   643                                           b'times')
   678                         ui.ferr,
       
   679                         b'cannot upgrade protocols multiple ' b'times',
       
   680                     )
   644                     state = 'shutdown'
   681                     state = 'shutdown'
   645                     continue
   682                     continue
   646 
   683 
   647                 state = 'upgrade-initial'
   684                 state = 'upgrade-initial'
   648                 continue
   685                 continue
   649 
   686 
   650             available = wireprotov1server.commands.commandavailable(
   687             available = wireprotov1server.commands.commandavailable(
   651                 request, proto)
   688                 request, proto
       
   689             )
   652 
   690 
   653             # This command isn't available. Send an empty response and go
   691             # This command isn't available. Send an empty response and go
   654             # back to waiting for a new command.
   692             # back to waiting for a new command.
   655             if not available:
   693             if not available:
   656                 _sshv1respondbytes(fout, b'')
   694                 _sshv1respondbytes(fout, b'')
   674             elif isinstance(rsp, wireprototypes.pusherr):
   712             elif isinstance(rsp, wireprototypes.pusherr):
   675                 _sshv1respondbytes(fout, rsp.res)
   713                 _sshv1respondbytes(fout, rsp.res)
   676             elif isinstance(rsp, wireprototypes.ooberror):
   714             elif isinstance(rsp, wireprototypes.ooberror):
   677                 _sshv1respondooberror(fout, ui.ferr, rsp.message)
   715                 _sshv1respondooberror(fout, ui.ferr, rsp.message)
   678             else:
   716             else:
   679                 raise error.ProgrammingError('unhandled response type from '
   717                 raise error.ProgrammingError(
   680                                              'wire protocol command: %s' % rsp)
   718                     'unhandled response type from '
       
   719                     'wire protocol command: %s' % rsp
       
   720                 )
   681 
   721 
   682         # For now, protocol version 2 serving just goes back to version 1.
   722         # For now, protocol version 2 serving just goes back to version 1.
   683         elif state == 'protov2-serving':
   723         elif state == 'protov2-serving':
   684             state = 'protov1-serving'
   724             state = 'protov1-serving'
   685             continue
   725             continue
   739             ok = True
   779             ok = True
   740             for line in (b'hello', b'between', b'pairs 81'):
   780             for line in (b'hello', b'between', b'pairs 81'):
   741                 request = fin.readline()[:-1]
   781                 request = fin.readline()[:-1]
   742 
   782 
   743                 if request != line:
   783                 if request != line:
   744                     _sshv1respondooberror(fout, ui.ferr,
   784                     _sshv1respondooberror(
   745                                           b'malformed handshake protocol: '
   785                         fout,
   746                                           b'missing %s' % line)
   786                         ui.ferr,
       
   787                         b'malformed handshake protocol: ' b'missing %s' % line,
       
   788                     )
   747                     ok = False
   789                     ok = False
   748                     state = 'shutdown'
   790                     state = 'shutdown'
   749                     break
   791                     break
   750 
   792 
   751             if not ok:
   793             if not ok:
   752                 continue
   794                 continue
   753 
   795 
   754             request = fin.read(81)
   796             request = fin.read(81)
   755             if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
   797             if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
   756                 _sshv1respondooberror(fout, ui.ferr,
   798                 _sshv1respondooberror(
   757                                       b'malformed handshake protocol: '
   799                     fout,
   758                                       b'missing between argument value')
   800                     ui.ferr,
       
   801                     b'malformed handshake protocol: '
       
   802                     b'missing between argument value',
       
   803                 )
   759                 state = 'shutdown'
   804                 state = 'shutdown'
   760                 continue
   805                 continue
   761 
   806 
   762             state = 'upgrade-v2-finish'
   807             state = 'upgrade-v2-finish'
   763             continue
   808             continue
   778 
   823 
   779         elif state == 'shutdown':
   824         elif state == 'shutdown':
   780             break
   825             break
   781 
   826 
   782         else:
   827         else:
   783             raise error.ProgrammingError('unhandled ssh server state: %s' %
   828             raise error.ProgrammingError(
   784                                          state)
   829                 'unhandled ssh server state: %s' % state
       
   830             )
       
   831 
   785 
   832 
   786 class sshserver(object):
   833 class sshserver(object):
   787     def __init__(self, ui, repo, logfh=None):
   834     def __init__(self, ui, repo, logfh=None):
   788         self._ui = ui
   835         self._ui = ui
   789         self._repo = repo
   836         self._repo = repo
   790         self._fin, self._fout = ui.protectfinout()
   837         self._fin, self._fout = ui.protectfinout()
   791 
   838 
   792         # Log write I/O to stdout and stderr if configured.
   839         # Log write I/O to stdout and stderr if configured.
   793         if logfh:
   840         if logfh:
   794             self._fout = util.makeloggingfileobject(
   841             self._fout = util.makeloggingfileobject(
   795                 logfh, self._fout, 'o', logdata=True)
   842                 logfh, self._fout, 'o', logdata=True
       
   843             )
   796             ui.ferr = util.makeloggingfileobject(
   844             ui.ferr = util.makeloggingfileobject(
   797                 logfh, ui.ferr, 'e', logdata=True)
   845                 logfh, ui.ferr, 'e', logdata=True
       
   846             )
   798 
   847 
   799     def serve_forever(self):
   848     def serve_forever(self):
   800         self.serveuntil(threading.Event())
   849         self.serveuntil(threading.Event())
   801         self._ui.restorefinout(self._fin, self._fout)
   850         self._ui.restorefinout(self._fin, self._fout)
   802         sys.exit(0)
   851         sys.exit(0)