##// END OF EJS Templates
wireprotoserver: remove broken optimization for non-httplib client...
Gregory Szorc -
r36831:5a3c8341 default
parent child Browse files
Show More
@@ -1,672 +1,669
1 1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 3 #
4 4 # This software may be used and distributed according to the terms of the
5 5 # GNU General Public License version 2 or any later version.
6 6
7 7 from __future__ import absolute_import
8 8
9 9 import contextlib
10 10 import struct
11 11 import sys
12 12 import threading
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 encoding,
17 17 error,
18 18 hook,
19 19 pycompat,
20 20 util,
21 21 wireproto,
22 22 wireprototypes,
23 23 )
24 24
25 25 stringio = util.stringio
26 26
27 27 urlerr = util.urlerr
28 28 urlreq = util.urlreq
29 29
30 30 HTTP_OK = 200
31 31
32 32 HGTYPE = 'application/mercurial-0.1'
33 33 HGTYPE2 = 'application/mercurial-0.2'
34 34 HGERRTYPE = 'application/hg-error'
35 35
36 36 SSHV1 = wireprototypes.SSHV1
37 37 SSHV2 = wireprototypes.SSHV2
38 38
39 39 def decodevaluefromheaders(wsgireq, headerprefix):
40 40 """Decode a long value from multiple HTTP request headers.
41 41
42 42 Returns the value as a bytes, not a str.
43 43 """
44 44 chunks = []
45 45 i = 1
46 46 prefix = headerprefix.upper().replace(r'-', r'_')
47 47 while True:
48 48 v = wsgireq.env.get(r'HTTP_%s_%d' % (prefix, i))
49 49 if v is None:
50 50 break
51 51 chunks.append(pycompat.bytesurl(v))
52 52 i += 1
53 53
54 54 return ''.join(chunks)
55 55
56 56 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
57 57 def __init__(self, wsgireq, ui, checkperm):
58 58 self._wsgireq = wsgireq
59 59 self._ui = ui
60 60 self._checkperm = checkperm
61 61
62 62 @property
63 63 def name(self):
64 64 return 'http-v1'
65 65
66 66 def getargs(self, args):
67 67 knownargs = self._args()
68 68 data = {}
69 69 keys = args.split()
70 70 for k in keys:
71 71 if k == '*':
72 72 star = {}
73 73 for key in knownargs.keys():
74 74 if key != 'cmd' and key not in keys:
75 75 star[key] = knownargs[key][0]
76 76 data['*'] = star
77 77 else:
78 78 data[k] = knownargs[k][0]
79 79 return [data[k] for k in keys]
80 80
81 81 def _args(self):
82 82 args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy())
83 83 postlen = int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0))
84 84 if postlen:
85 85 args.update(urlreq.parseqs(
86 86 self._wsgireq.read(postlen), keep_blank_values=True))
87 87 return args
88 88
89 89 argvalue = decodevaluefromheaders(self._wsgireq, r'X-HgArg')
90 90 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
91 91 return args
92 92
93 93 def forwardpayload(self, fp):
94 94 if r'HTTP_CONTENT_LENGTH' in self._wsgireq.env:
95 95 length = int(self._wsgireq.env[r'HTTP_CONTENT_LENGTH'])
96 96 else:
97 97 length = int(self._wsgireq.env[r'CONTENT_LENGTH'])
98 98 # If httppostargs is used, we need to read Content-Length
99 99 # minus the amount that was consumed by args.
100 100 length -= int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0))
101 101 for s in util.filechunkiter(self._wsgireq, limit=length):
102 102 fp.write(s)
103 103
104 104 @contextlib.contextmanager
105 105 def mayberedirectstdio(self):
106 106 oldout = self._ui.fout
107 107 olderr = self._ui.ferr
108 108
109 109 out = util.stringio()
110 110
111 111 try:
112 112 self._ui.fout = out
113 113 self._ui.ferr = out
114 114 yield out
115 115 finally:
116 116 self._ui.fout = oldout
117 117 self._ui.ferr = olderr
118 118
119 119 def client(self):
120 120 return 'remote:%s:%s:%s' % (
121 121 self._wsgireq.env.get('wsgi.url_scheme') or 'http',
122 122 urlreq.quote(self._wsgireq.env.get('REMOTE_HOST', '')),
123 123 urlreq.quote(self._wsgireq.env.get('REMOTE_USER', '')))
124 124
125 125 def addcapabilities(self, repo, caps):
126 126 caps.append('httpheader=%d' %
127 127 repo.ui.configint('server', 'maxhttpheaderlen'))
128 128 if repo.ui.configbool('experimental', 'httppostargs'):
129 129 caps.append('httppostargs')
130 130
131 131 # FUTURE advertise 0.2rx once support is implemented
132 132 # FUTURE advertise minrx and mintx after consulting config option
133 133 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
134 134
135 135 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
136 136 if compengines:
137 137 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
138 138 for e in compengines)
139 139 caps.append('compression=%s' % comptypes)
140 140
141 141 return caps
142 142
143 143 def checkperm(self, perm):
144 144 return self._checkperm(perm)
145 145
146 146 # This method exists mostly so that extensions like remotefilelog can
147 147 # disable a kludgey legacy method only over http. As of early 2018,
148 148 # there are no other known users, so with any luck we can discard this
149 149 # hook if remotefilelog becomes a first-party extension.
150 150 def iscmd(cmd):
151 151 return cmd in wireproto.commands
152 152
153 153 def handlewsgirequest(rctx, wsgireq, req, checkperm):
154 154 """Possibly process a wire protocol request.
155 155
156 156 If the current request is a wire protocol request, the request is
157 157 processed by this function.
158 158
159 159 ``wsgireq`` is a ``wsgirequest`` instance.
160 160 ``req`` is a ``parsedrequest`` instance.
161 161
162 162 Returns a 2-tuple of (bool, response) where the 1st element indicates
163 163 whether the request was handled and the 2nd element is a return
164 164 value for a WSGI application (often a generator of bytes).
165 165 """
166 166 # Avoid cycle involving hg module.
167 167 from .hgweb import common as hgwebcommon
168 168
169 169 repo = rctx.repo
170 170
171 171 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
172 172 # string parameter. If it isn't present, this isn't a wire protocol
173 173 # request.
174 174 if 'cmd' not in req.querystringdict:
175 175 return False, None
176 176
177 177 cmd = req.querystringdict['cmd'][0]
178 178
179 179 # The "cmd" request parameter is used by both the wire protocol and hgweb.
180 180 # While not all wire protocol commands are available for all transports,
181 181 # if we see a "cmd" value that resembles a known wire protocol command, we
182 182 # route it to a protocol handler. This is better than routing possible
183 183 # wire protocol requests to hgweb because it prevents hgweb from using
184 184 # known wire protocol commands and it is less confusing for machine
185 185 # clients.
186 186 if not iscmd(cmd):
187 187 return False, None
188 188
189 189 # The "cmd" query string argument is only valid on the root path of the
190 190 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
191 191 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
192 192 # in this case. We send an HTTP 404 for backwards compatibility reasons.
193 193 if req.dispatchpath:
194 194 res = _handlehttperror(
195 195 hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq,
196 196 cmd)
197 197
198 198 return True, res
199 199
200 200 proto = httpv1protocolhandler(wsgireq, repo.ui,
201 201 lambda perm: checkperm(rctx, wsgireq, perm))
202 202
203 203 # The permissions checker should be the only thing that can raise an
204 204 # ErrorResponse. It is kind of a layer violation to catch an hgweb
205 205 # exception here. So consider refactoring into a exception type that
206 206 # is associated with the wire protocol.
207 207 try:
208 208 res = _callhttp(repo, wsgireq, proto, cmd)
209 209 except hgwebcommon.ErrorResponse as e:
210 210 res = _handlehttperror(e, wsgireq, cmd)
211 211
212 212 return True, res
213 213
214 214 def _httpresponsetype(ui, wsgireq, prefer_uncompressed):
215 215 """Determine the appropriate response type and compression settings.
216 216
217 217 Returns a tuple of (mediatype, compengine, engineopts).
218 218 """
219 219 # Determine the response media type and compression engine based
220 220 # on the request parameters.
221 221 protocaps = decodevaluefromheaders(wsgireq, r'X-HgProto').split(' ')
222 222
223 223 if '0.2' in protocaps:
224 224 # All clients are expected to support uncompressed data.
225 225 if prefer_uncompressed:
226 226 return HGTYPE2, util._noopengine(), {}
227 227
228 228 # Default as defined by wire protocol spec.
229 229 compformats = ['zlib', 'none']
230 230 for cap in protocaps:
231 231 if cap.startswith('comp='):
232 232 compformats = cap[5:].split(',')
233 233 break
234 234
235 235 # Now find an agreed upon compression format.
236 236 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
237 237 if engine.wireprotosupport().name in compformats:
238 238 opts = {}
239 239 level = ui.configint('server', '%slevel' % engine.name())
240 240 if level is not None:
241 241 opts['level'] = level
242 242
243 243 return HGTYPE2, engine, opts
244 244
245 245 # No mutually supported compression format. Fall back to the
246 246 # legacy protocol.
247 247
248 248 # Don't allow untrusted settings because disabling compression or
249 249 # setting a very high compression level could lead to flooding
250 250 # the server's network or CPU.
251 251 opts = {'level': ui.configint('server', 'zliblevel')}
252 252 return HGTYPE, util.compengines['zlib'], opts
253 253
254 254 def _callhttp(repo, wsgireq, proto, cmd):
255 255 def genversion2(gen, engine, engineopts):
256 256 # application/mercurial-0.2 always sends a payload header
257 257 # identifying the compression engine.
258 258 name = engine.wireprotosupport().name
259 259 assert 0 < len(name) < 256
260 260 yield struct.pack('B', len(name))
261 261 yield name
262 262
263 263 for chunk in gen:
264 264 yield chunk
265 265
266 266 if not wireproto.commands.commandavailable(cmd, proto):
267 267 wsgireq.respond(HTTP_OK, HGERRTYPE,
268 268 body=_('requested wire protocol command is not '
269 269 'available over HTTP'))
270 270 return []
271 271
272 272 proto.checkperm(wireproto.commands[cmd].permission)
273 273
274 274 rsp = wireproto.dispatch(repo, proto, cmd)
275 275
276 276 if isinstance(rsp, bytes):
277 277 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
278 278 return []
279 279 elif isinstance(rsp, wireprototypes.bytesresponse):
280 280 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp.data)
281 281 return []
282 282 elif isinstance(rsp, wireprototypes.streamreslegacy):
283 283 gen = rsp.gen
284 284 wsgireq.respond(HTTP_OK, HGTYPE)
285 285 return gen
286 286 elif isinstance(rsp, wireprototypes.streamres):
287 287 gen = rsp.gen
288 288
289 289 # This code for compression should not be streamres specific. It
290 290 # is here because we only compress streamres at the moment.
291 291 mediatype, engine, engineopts = _httpresponsetype(
292 292 repo.ui, wsgireq, rsp.prefer_uncompressed)
293 293 gen = engine.compressstream(gen, engineopts)
294 294
295 295 if mediatype == HGTYPE2:
296 296 gen = genversion2(gen, engine, engineopts)
297 297
298 298 wsgireq.respond(HTTP_OK, mediatype)
299 299 return gen
300 300 elif isinstance(rsp, wireprototypes.pushres):
301 301 rsp = '%d\n%s' % (rsp.res, rsp.output)
302 302 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
303 303 return []
304 304 elif isinstance(rsp, wireprototypes.pusherr):
305 305 # This is the httplib workaround documented in _handlehttperror().
306 306 wsgireq.drain()
307 307
308 308 rsp = '0\n%s\n' % rsp.res
309 309 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
310 310 return []
311 311 elif isinstance(rsp, wireprototypes.ooberror):
312 312 rsp = rsp.message
313 313 wsgireq.respond(HTTP_OK, HGERRTYPE, body=rsp)
314 314 return []
315 315 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
316 316
317 317 def _handlehttperror(e, wsgireq, cmd):
318 318 """Called when an ErrorResponse is raised during HTTP request processing."""
319 319
320 320 # Clients using Python's httplib are stateful: the HTTP client
321 321 # won't process an HTTP response until all request data is
322 322 # sent to the server. The intent of this code is to ensure
323 323 # we always read HTTP request data from the client, thus
324 324 # ensuring httplib transitions to a state that allows it to read
325 325 # the HTTP response. In other words, it helps prevent deadlocks
326 326 # on clients using httplib.
327 327
328 328 if (wsgireq.env[r'REQUEST_METHOD'] == r'POST' and
329 329 # But not if Expect: 100-continue is being used.
330 330 (wsgireq.env.get('HTTP_EXPECT',
331 '').lower() != '100-continue') or
332 # Or the non-httplib HTTP library is being advertised by
333 # the client.
334 wsgireq.env.get('X-HgHttp2', '')):
331 '').lower() != '100-continue')):
335 332 wsgireq.drain()
336 333 else:
337 334 wsgireq.headers.append((r'Connection', r'Close'))
338 335
339 336 # TODO This response body assumes the failed command was
340 337 # "unbundle." That assumption is not always valid.
341 338 wsgireq.respond(e, HGTYPE, body='0\n%s\n' % pycompat.bytestr(e))
342 339
343 340 return ''
344 341
345 342 def _sshv1respondbytes(fout, value):
346 343 """Send a bytes response for protocol version 1."""
347 344 fout.write('%d\n' % len(value))
348 345 fout.write(value)
349 346 fout.flush()
350 347
351 348 def _sshv1respondstream(fout, source):
352 349 write = fout.write
353 350 for chunk in source.gen:
354 351 write(chunk)
355 352 fout.flush()
356 353
357 354 def _sshv1respondooberror(fout, ferr, rsp):
358 355 ferr.write(b'%s\n-\n' % rsp)
359 356 ferr.flush()
360 357 fout.write(b'\n')
361 358 fout.flush()
362 359
363 360 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
364 361 """Handler for requests services via version 1 of SSH protocol."""
365 362 def __init__(self, ui, fin, fout):
366 363 self._ui = ui
367 364 self._fin = fin
368 365 self._fout = fout
369 366
370 367 @property
371 368 def name(self):
372 369 return wireprototypes.SSHV1
373 370
374 371 def getargs(self, args):
375 372 data = {}
376 373 keys = args.split()
377 374 for n in xrange(len(keys)):
378 375 argline = self._fin.readline()[:-1]
379 376 arg, l = argline.split()
380 377 if arg not in keys:
381 378 raise error.Abort(_("unexpected parameter %r") % arg)
382 379 if arg == '*':
383 380 star = {}
384 381 for k in xrange(int(l)):
385 382 argline = self._fin.readline()[:-1]
386 383 arg, l = argline.split()
387 384 val = self._fin.read(int(l))
388 385 star[arg] = val
389 386 data['*'] = star
390 387 else:
391 388 val = self._fin.read(int(l))
392 389 data[arg] = val
393 390 return [data[k] for k in keys]
394 391
395 392 def forwardpayload(self, fpout):
396 393 # We initially send an empty response. This tells the client it is
397 394 # OK to start sending data. If a client sees any other response, it
398 395 # interprets it as an error.
399 396 _sshv1respondbytes(self._fout, b'')
400 397
401 398 # The file is in the form:
402 399 #
403 400 # <chunk size>\n<chunk>
404 401 # ...
405 402 # 0\n
406 403 count = int(self._fin.readline())
407 404 while count:
408 405 fpout.write(self._fin.read(count))
409 406 count = int(self._fin.readline())
410 407
411 408 @contextlib.contextmanager
412 409 def mayberedirectstdio(self):
413 410 yield None
414 411
415 412 def client(self):
416 413 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
417 414 return 'remote:ssh:' + client
418 415
419 416 def addcapabilities(self, repo, caps):
420 417 return caps
421 418
422 419 def checkperm(self, perm):
423 420 pass
424 421
425 422 class sshv2protocolhandler(sshv1protocolhandler):
426 423 """Protocol handler for version 2 of the SSH protocol."""
427 424
428 425 @property
429 426 def name(self):
430 427 return wireprototypes.SSHV2
431 428
432 429 def _runsshserver(ui, repo, fin, fout, ev):
433 430 # This function operates like a state machine of sorts. The following
434 431 # states are defined:
435 432 #
436 433 # protov1-serving
437 434 # Server is in protocol version 1 serving mode. Commands arrive on
438 435 # new lines. These commands are processed in this state, one command
439 436 # after the other.
440 437 #
441 438 # protov2-serving
442 439 # Server is in protocol version 2 serving mode.
443 440 #
444 441 # upgrade-initial
445 442 # The server is going to process an upgrade request.
446 443 #
447 444 # upgrade-v2-filter-legacy-handshake
448 445 # The protocol is being upgraded to version 2. The server is expecting
449 446 # the legacy handshake from version 1.
450 447 #
451 448 # upgrade-v2-finish
452 449 # The upgrade to version 2 of the protocol is imminent.
453 450 #
454 451 # shutdown
455 452 # The server is shutting down, possibly in reaction to a client event.
456 453 #
457 454 # And here are their transitions:
458 455 #
459 456 # protov1-serving -> shutdown
460 457 # When server receives an empty request or encounters another
461 458 # error.
462 459 #
463 460 # protov1-serving -> upgrade-initial
464 461 # An upgrade request line was seen.
465 462 #
466 463 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
467 464 # Upgrade to version 2 in progress. Server is expecting to
468 465 # process a legacy handshake.
469 466 #
470 467 # upgrade-v2-filter-legacy-handshake -> shutdown
471 468 # Client did not fulfill upgrade handshake requirements.
472 469 #
473 470 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
474 471 # Client fulfilled version 2 upgrade requirements. Finishing that
475 472 # upgrade.
476 473 #
477 474 # upgrade-v2-finish -> protov2-serving
478 475 # Protocol upgrade to version 2 complete. Server can now speak protocol
479 476 # version 2.
480 477 #
481 478 # protov2-serving -> protov1-serving
482 479 # Ths happens by default since protocol version 2 is the same as
483 480 # version 1 except for the handshake.
484 481
485 482 state = 'protov1-serving'
486 483 proto = sshv1protocolhandler(ui, fin, fout)
487 484 protoswitched = False
488 485
489 486 while not ev.is_set():
490 487 if state == 'protov1-serving':
491 488 # Commands are issued on new lines.
492 489 request = fin.readline()[:-1]
493 490
494 491 # Empty lines signal to terminate the connection.
495 492 if not request:
496 493 state = 'shutdown'
497 494 continue
498 495
499 496 # It looks like a protocol upgrade request. Transition state to
500 497 # handle it.
501 498 if request.startswith(b'upgrade '):
502 499 if protoswitched:
503 500 _sshv1respondooberror(fout, ui.ferr,
504 501 b'cannot upgrade protocols multiple '
505 502 b'times')
506 503 state = 'shutdown'
507 504 continue
508 505
509 506 state = 'upgrade-initial'
510 507 continue
511 508
512 509 available = wireproto.commands.commandavailable(request, proto)
513 510
514 511 # This command isn't available. Send an empty response and go
515 512 # back to waiting for a new command.
516 513 if not available:
517 514 _sshv1respondbytes(fout, b'')
518 515 continue
519 516
520 517 rsp = wireproto.dispatch(repo, proto, request)
521 518
522 519 if isinstance(rsp, bytes):
523 520 _sshv1respondbytes(fout, rsp)
524 521 elif isinstance(rsp, wireprototypes.bytesresponse):
525 522 _sshv1respondbytes(fout, rsp.data)
526 523 elif isinstance(rsp, wireprototypes.streamres):
527 524 _sshv1respondstream(fout, rsp)
528 525 elif isinstance(rsp, wireprototypes.streamreslegacy):
529 526 _sshv1respondstream(fout, rsp)
530 527 elif isinstance(rsp, wireprototypes.pushres):
531 528 _sshv1respondbytes(fout, b'')
532 529 _sshv1respondbytes(fout, b'%d' % rsp.res)
533 530 elif isinstance(rsp, wireprototypes.pusherr):
534 531 _sshv1respondbytes(fout, rsp.res)
535 532 elif isinstance(rsp, wireprototypes.ooberror):
536 533 _sshv1respondooberror(fout, ui.ferr, rsp.message)
537 534 else:
538 535 raise error.ProgrammingError('unhandled response type from '
539 536 'wire protocol command: %s' % rsp)
540 537
541 538 # For now, protocol version 2 serving just goes back to version 1.
542 539 elif state == 'protov2-serving':
543 540 state = 'protov1-serving'
544 541 continue
545 542
546 543 elif state == 'upgrade-initial':
547 544 # We should never transition into this state if we've switched
548 545 # protocols.
549 546 assert not protoswitched
550 547 assert proto.name == wireprototypes.SSHV1
551 548
552 549 # Expected: upgrade <token> <capabilities>
553 550 # If we get something else, the request is malformed. It could be
554 551 # from a future client that has altered the upgrade line content.
555 552 # We treat this as an unknown command.
556 553 try:
557 554 token, caps = request.split(b' ')[1:]
558 555 except ValueError:
559 556 _sshv1respondbytes(fout, b'')
560 557 state = 'protov1-serving'
561 558 continue
562 559
563 560 # Send empty response if we don't support upgrading protocols.
564 561 if not ui.configbool('experimental', 'sshserver.support-v2'):
565 562 _sshv1respondbytes(fout, b'')
566 563 state = 'protov1-serving'
567 564 continue
568 565
569 566 try:
570 567 caps = urlreq.parseqs(caps)
571 568 except ValueError:
572 569 _sshv1respondbytes(fout, b'')
573 570 state = 'protov1-serving'
574 571 continue
575 572
576 573 # We don't see an upgrade request to protocol version 2. Ignore
577 574 # the upgrade request.
578 575 wantedprotos = caps.get(b'proto', [b''])[0]
579 576 if SSHV2 not in wantedprotos:
580 577 _sshv1respondbytes(fout, b'')
581 578 state = 'protov1-serving'
582 579 continue
583 580
584 581 # It looks like we can honor this upgrade request to protocol 2.
585 582 # Filter the rest of the handshake protocol request lines.
586 583 state = 'upgrade-v2-filter-legacy-handshake'
587 584 continue
588 585
589 586 elif state == 'upgrade-v2-filter-legacy-handshake':
590 587 # Client should have sent legacy handshake after an ``upgrade``
591 588 # request. Expected lines:
592 589 #
593 590 # hello
594 591 # between
595 592 # pairs 81
596 593 # 0000...-0000...
597 594
598 595 ok = True
599 596 for line in (b'hello', b'between', b'pairs 81'):
600 597 request = fin.readline()[:-1]
601 598
602 599 if request != line:
603 600 _sshv1respondooberror(fout, ui.ferr,
604 601 b'malformed handshake protocol: '
605 602 b'missing %s' % line)
606 603 ok = False
607 604 state = 'shutdown'
608 605 break
609 606
610 607 if not ok:
611 608 continue
612 609
613 610 request = fin.read(81)
614 611 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
615 612 _sshv1respondooberror(fout, ui.ferr,
616 613 b'malformed handshake protocol: '
617 614 b'missing between argument value')
618 615 state = 'shutdown'
619 616 continue
620 617
621 618 state = 'upgrade-v2-finish'
622 619 continue
623 620
624 621 elif state == 'upgrade-v2-finish':
625 622 # Send the upgrade response.
626 623 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
627 624 servercaps = wireproto.capabilities(repo, proto)
628 625 rsp = b'capabilities: %s' % servercaps.data
629 626 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
630 627 fout.flush()
631 628
632 629 proto = sshv2protocolhandler(ui, fin, fout)
633 630 protoswitched = True
634 631
635 632 state = 'protov2-serving'
636 633 continue
637 634
638 635 elif state == 'shutdown':
639 636 break
640 637
641 638 else:
642 639 raise error.ProgrammingError('unhandled ssh server state: %s' %
643 640 state)
644 641
645 642 class sshserver(object):
646 643 def __init__(self, ui, repo, logfh=None):
647 644 self._ui = ui
648 645 self._repo = repo
649 646 self._fin = ui.fin
650 647 self._fout = ui.fout
651 648
652 649 # Log write I/O to stdout and stderr if configured.
653 650 if logfh:
654 651 self._fout = util.makeloggingfileobject(
655 652 logfh, self._fout, 'o', logdata=True)
656 653 ui.ferr = util.makeloggingfileobject(
657 654 logfh, ui.ferr, 'e', logdata=True)
658 655
659 656 hook.redirect(True)
660 657 ui.fout = repo.ui.fout = ui.ferr
661 658
662 659 # Prevent insertion/deletion of CRs
663 660 util.setbinary(self._fin)
664 661 util.setbinary(self._fout)
665 662
666 663 def serve_forever(self):
667 664 self.serveuntil(threading.Event())
668 665 sys.exit(0)
669 666
670 667 def serveuntil(self, ev):
671 668 """Serve until a threading.Event is set."""
672 669 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
General Comments 0
You need to be logged in to leave comments. Login now