comparison mercurial/wireprotoserver.py @ 43077:687b865b95ad

formatting: byteify all mercurial/ and hgext/ string literals Done with python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py') black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**') # skip-blame mass-reformatting only Differential Revision: https://phab.mercurial-scm.org/D6972
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:48:39 -0400
parents 2372284d9457
children 8ff1ecfadcd1
comparison
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
32 urlerr = util.urlerr 32 urlerr = util.urlerr
33 urlreq = util.urlreq 33 urlreq = util.urlreq
34 34
35 HTTP_OK = 200 35 HTTP_OK = 200
36 36
37 HGTYPE = 'application/mercurial-0.1' 37 HGTYPE = b'application/mercurial-0.1'
38 HGTYPE2 = 'application/mercurial-0.2' 38 HGTYPE2 = b'application/mercurial-0.2'
39 HGERRTYPE = 'application/hg-error' 39 HGERRTYPE = b'application/hg-error'
40 40
41 SSHV1 = wireprototypes.SSHV1 41 SSHV1 = wireprototypes.SSHV1
42 SSHV2 = wireprototypes.SSHV2 42 SSHV2 = wireprototypes.SSHV2
43 43
44 44
54 if v is None: 54 if v is None:
55 break 55 break
56 chunks.append(pycompat.bytesurl(v)) 56 chunks.append(pycompat.bytesurl(v))
57 i += 1 57 i += 1
58 58
59 return ''.join(chunks) 59 return b''.join(chunks)
60 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):
67 self._checkperm = checkperm 67 self._checkperm = checkperm
68 self._protocaps = None 68 self._protocaps = None
69 69
70 @property 70 @property
71 def name(self): 71 def name(self):
72 return 'http-v1' 72 return b'http-v1'
73 73
74 def getargs(self, args): 74 def getargs(self, args):
75 knownargs = self._args() 75 knownargs = self._args()
76 data = {} 76 data = {}
77 keys = args.split() 77 keys = args.split()
78 for k in keys: 78 for k in keys:
79 if k == '*': 79 if k == b'*':
80 star = {} 80 star = {}
81 for key in knownargs.keys(): 81 for key in knownargs.keys():
82 if key != 'cmd' and key not in keys: 82 if key != b'cmd' and key not in keys:
83 star[key] = knownargs[key][0] 83 star[key] = knownargs[key][0]
84 data['*'] = star 84 data[b'*'] = star
85 else: 85 else:
86 data[k] = knownargs[k][0] 86 data[k] = knownargs[k][0]
87 return [data[k] for k in keys] 87 return [data[k] for k in keys]
88 88
89 def _args(self): 89 def _args(self):
102 return args 102 return args
103 103
104 def getprotocaps(self): 104 def getprotocaps(self):
105 if self._protocaps is None: 105 if self._protocaps is None:
106 value = decodevaluefromheaders(self._req, b'X-HgProto') 106 value = decodevaluefromheaders(self._req, b'X-HgProto')
107 self._protocaps = set(value.split(' ')) 107 self._protocaps = set(value.split(b' '))
108 return self._protocaps 108 return self._protocaps
109 109
110 def getpayload(self): 110 def getpayload(self):
111 # Existing clients *always* send Content-Length. 111 # Existing clients *always* send Content-Length.
112 length = int(self._req.headers[b'Content-Length']) 112 length = int(self._req.headers[b'Content-Length'])
130 finally: 130 finally:
131 self._ui.fout = oldout 131 self._ui.fout = oldout
132 self._ui.ferr = olderr 132 self._ui.ferr = olderr
133 133
134 def client(self): 134 def client(self):
135 return 'remote:%s:%s:%s' % ( 135 return b'remote:%s:%s:%s' % (
136 self._req.urlscheme, 136 self._req.urlscheme,
137 urlreq.quote(self._req.remotehost or ''), 137 urlreq.quote(self._req.remotehost or b''),
138 urlreq.quote(self._req.remoteuser or ''), 138 urlreq.quote(self._req.remoteuser or b''),
139 ) 139 )
140 140
141 def addcapabilities(self, repo, caps): 141 def addcapabilities(self, repo, caps):
142 caps.append(b'batch') 142 caps.append(b'batch')
143 143
144 caps.append( 144 caps.append(
145 'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen') 145 b'httpheader=%d' % repo.ui.configint(b'server', b'maxhttpheaderlen')
146 ) 146 )
147 if repo.ui.configbool('experimental', 'httppostargs'): 147 if repo.ui.configbool(b'experimental', b'httppostargs'):
148 caps.append('httppostargs') 148 caps.append(b'httppostargs')
149 149
150 # FUTURE advertise 0.2rx once support is implemented 150 # FUTURE advertise 0.2rx once support is implemented
151 # FUTURE advertise minrx and mintx after consulting config option 151 # FUTURE advertise minrx and mintx after consulting config option
152 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx') 152 caps.append(b'httpmediatype=0.1rx,0.1tx,0.2tx')
153 153
154 compengines = wireprototypes.supportedcompengines( 154 compengines = wireprototypes.supportedcompengines(
155 repo.ui, compression.SERVERROLE 155 repo.ui, compression.SERVERROLE
156 ) 156 )
157 if compengines: 157 if compengines:
158 comptypes = ','.join( 158 comptypes = b','.join(
159 urlreq.quote(e.wireprotosupport().name) for e in compengines 159 urlreq.quote(e.wireprotosupport().name) for e in compengines
160 ) 160 )
161 caps.append('compression=%s' % comptypes) 161 caps.append(b'compression=%s' % comptypes)
162 162
163 return caps 163 return caps
164 164
165 def checkperm(self, perm): 165 def checkperm(self, perm):
166 return self._checkperm(perm) 166 return self._checkperm(perm)
192 repo = rctx.repo 192 repo = rctx.repo
193 193
194 # HTTP version 1 wire protocol requests are denoted by a "cmd" query 194 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
195 # string parameter. If it isn't present, this isn't a wire protocol 195 # string parameter. If it isn't present, this isn't a wire protocol
196 # request. 196 # request.
197 if 'cmd' not in req.qsparams: 197 if b'cmd' not in req.qsparams:
198 return False 198 return False
199 199
200 cmd = req.qsparams['cmd'] 200 cmd = req.qsparams[b'cmd']
201 201
202 # The "cmd" request parameter is used by both the wire protocol and hgweb. 202 # The "cmd" request parameter is used by both the wire protocol and hgweb.
203 # While not all wire protocol commands are available for all transports, 203 # While not all wire protocol commands are available for all transports,
204 # if we see a "cmd" value that resembles a known wire protocol command, we 204 # if we see a "cmd" value that resembles a known wire protocol command, we
205 # route it to a protocol handler. This is better than routing possible 205 # route it to a protocol handler. This is better than routing possible
213 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo 213 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
214 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request 214 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
215 # in this case. We send an HTTP 404 for backwards compatibility reasons. 215 # in this case. We send an HTTP 404 for backwards compatibility reasons.
216 if req.dispatchpath: 216 if req.dispatchpath:
217 res.status = hgwebcommon.statusmessage(404) 217 res.status = hgwebcommon.statusmessage(404)
218 res.headers['Content-Type'] = HGTYPE 218 res.headers[b'Content-Type'] = HGTYPE
219 # 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
220 # is mostly for BC for now. 220 # is mostly for BC for now.
221 res.setbodybytes('0\n%s\n' % b'Not Found') 221 res.setbodybytes(b'0\n%s\n' % b'Not Found')
222 return True 222 return True
223 223
224 proto = httpv1protocolhandler( 224 proto = httpv1protocolhandler(
225 req, repo.ui, lambda perm: checkperm(rctx, req, perm) 225 req, repo.ui, lambda perm: checkperm(rctx, req, perm)
226 ) 226 )
235 for k, v in e.headers: 235 for k, v in e.headers:
236 res.headers[k] = v 236 res.headers[k] = v
237 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e)) 237 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
238 # TODO This response body assumes the failed command was 238 # TODO This response body assumes the failed command was
239 # "unbundle." That assumption is not always valid. 239 # "unbundle." That assumption is not always valid.
240 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e)) 240 res.setbodybytes(b'0\n%s\n' % pycompat.bytestr(e))
241 241
242 return True 242 return True
243 243
244 244
245 def _availableapis(repo): 245 def _availableapis(repo):
246 apis = set() 246 apis = set()
247 247
248 # 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
249 # the protocol. 249 # the protocol.
250 for k, v in API_HANDLERS.items(): 250 for k, v in API_HANDLERS.items():
251 section, option = v['config'] 251 section, option = v[b'config']
252 if repo.ui.configbool(section, option): 252 if repo.ui.configbool(section, option):
253 apis.add(k) 253 apis.add(k)
254 254
255 return apis 255 return apis
256 256
261 261
262 repo = rctx.repo 262 repo = rctx.repo
263 263
264 # This whole URL space is experimental for now. But we want to 264 # This whole URL space is experimental for now. But we want to
265 # reserve the URL space. So, 404 all URLs if the feature isn't enabled. 265 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
266 if not repo.ui.configbool('experimental', 'web.apiserver'): 266 if not repo.ui.configbool(b'experimental', b'web.apiserver'):
267 res.status = b'404 Not Found' 267 res.status = b'404 Not Found'
268 res.headers[b'Content-Type'] = b'text/plain' 268 res.headers[b'Content-Type'] = b'text/plain'
269 res.setbodybytes(_('Experimental API server endpoint not enabled')) 269 res.setbodybytes(_(b'Experimental API server endpoint not enabled'))
270 return 270 return
271 271
272 # The URL space is /api/<protocol>/*. The structure of URLs under varies 272 # The URL space is /api/<protocol>/*. The structure of URLs under varies
273 # by <protocol>. 273 # by <protocol>.
274 274
278 if req.dispatchparts == [b'api']: 278 if req.dispatchparts == [b'api']:
279 res.status = b'200 OK' 279 res.status = b'200 OK'
280 res.headers[b'Content-Type'] = b'text/plain' 280 res.headers[b'Content-Type'] = b'text/plain'
281 lines = [ 281 lines = [
282 _( 282 _(
283 'APIs can be accessed at /api/<name>, where <name> can be ' 283 b'APIs can be accessed at /api/<name>, where <name> can be '
284 'one of the following:\n' 284 b'one of the following:\n'
285 ) 285 )
286 ] 286 ]
287 if availableapis: 287 if availableapis:
288 lines.extend(sorted(availableapis)) 288 lines.extend(sorted(availableapis))
289 else: 289 else:
290 lines.append(_('(no available APIs)\n')) 290 lines.append(_(b'(no available APIs)\n'))
291 res.setbodybytes(b'\n'.join(lines)) 291 res.setbodybytes(b'\n'.join(lines))
292 return 292 return
293 293
294 proto = req.dispatchparts[1] 294 proto = req.dispatchparts[1]
295 295
296 if proto not in API_HANDLERS: 296 if proto not in API_HANDLERS:
297 res.status = b'404 Not Found' 297 res.status = b'404 Not Found'
298 res.headers[b'Content-Type'] = b'text/plain' 298 res.headers[b'Content-Type'] = b'text/plain'
299 res.setbodybytes( 299 res.setbodybytes(
300 _('Unknown API: %s\nKnown APIs: %s') 300 _(b'Unknown API: %s\nKnown APIs: %s')
301 % (proto, b', '.join(sorted(availableapis))) 301 % (proto, b', '.join(sorted(availableapis)))
302 ) 302 )
303 return 303 return
304 304
305 if proto not in availableapis: 305 if proto not in availableapis:
306 res.status = b'404 Not Found' 306 res.status = b'404 Not Found'
307 res.headers[b'Content-Type'] = b'text/plain' 307 res.headers[b'Content-Type'] = b'text/plain'
308 res.setbodybytes(_('API %s not enabled\n') % proto) 308 res.setbodybytes(_(b'API %s not enabled\n') % proto)
309 return 309 return
310 310
311 API_HANDLERS[proto]['handler']( 311 API_HANDLERS[proto][b'handler'](
312 rctx, req, res, checkperm, req.dispatchparts[2:] 312 rctx, req, res, checkperm, req.dispatchparts[2:]
313 ) 313 )
314 314
315 315
316 # Maps API name to metadata so custom API can be registered. 316 # Maps API name to metadata so custom API can be registered.
324 # apidescriptor 324 # apidescriptor
325 # Callable receiving (req, repo) that is called to obtain an API 325 # Callable receiving (req, repo) that is called to obtain an API
326 # descriptor for this service. The response must be serializable to CBOR. 326 # descriptor for this service. The response must be serializable to CBOR.
327 API_HANDLERS = { 327 API_HANDLERS = {
328 wireprotov2server.HTTP_WIREPROTO_V2: { 328 wireprotov2server.HTTP_WIREPROTO_V2: {
329 'config': ('experimental', 'web.api.http-v2'), 329 b'config': (b'experimental', b'web.api.http-v2'),
330 'handler': wireprotov2server.handlehttpv2request, 330 b'handler': wireprotov2server.handlehttpv2request,
331 'apidescriptor': wireprotov2server.httpv2apidescriptor, 331 b'apidescriptor': wireprotov2server.httpv2apidescriptor,
332 }, 332 },
333 } 333 }
334 334
335 335
336 def _httpresponsetype(ui, proto, prefer_uncompressed): 336 def _httpresponsetype(ui, proto, prefer_uncompressed):
339 Returns a tuple of (mediatype, compengine, engineopts). 339 Returns a tuple of (mediatype, compengine, engineopts).
340 """ 340 """
341 # Determine the response media type and compression engine based 341 # Determine the response media type and compression engine based
342 # on the request parameters. 342 # on the request parameters.
343 343
344 if '0.2' in proto.getprotocaps(): 344 if b'0.2' in proto.getprotocaps():
345 # All clients are expected to support uncompressed data. 345 # All clients are expected to support uncompressed data.
346 if prefer_uncompressed: 346 if prefer_uncompressed:
347 return HGTYPE2, compression._noopengine(), {} 347 return HGTYPE2, compression._noopengine(), {}
348 348
349 # Now find an agreed upon compression format. 349 # Now find an agreed upon compression format.
351 for engine in wireprototypes.supportedcompengines( 351 for engine in wireprototypes.supportedcompengines(
352 ui, compression.SERVERROLE 352 ui, compression.SERVERROLE
353 ): 353 ):
354 if engine.wireprotosupport().name in compformats: 354 if engine.wireprotosupport().name in compformats:
355 opts = {} 355 opts = {}
356 level = ui.configint('server', '%slevel' % engine.name()) 356 level = ui.configint(b'server', b'%slevel' % engine.name())
357 if level is not None: 357 if level is not None:
358 opts['level'] = level 358 opts[b'level'] = level
359 359
360 return HGTYPE2, engine, opts 360 return HGTYPE2, engine, opts
361 361
362 # No mutually supported compression format. Fall back to the 362 # No mutually supported compression format. Fall back to the
363 # legacy protocol. 363 # legacy protocol.
364 364
365 # Don't allow untrusted settings because disabling compression or 365 # Don't allow untrusted settings because disabling compression or
366 # setting a very high compression level could lead to flooding 366 # setting a very high compression level could lead to flooding
367 # the server's network or CPU. 367 # the server's network or CPU.
368 opts = {'level': ui.configint('server', 'zliblevel')} 368 opts = {b'level': ui.configint(b'server', b'zliblevel')}
369 return HGTYPE, util.compengines['zlib'], opts 369 return HGTYPE, util.compengines[b'zlib'], opts
370 370
371 371
372 def processcapabilitieshandshake(repo, req, res, proto): 372 def processcapabilitieshandshake(repo, req, res, proto):
373 """Called during a ?cmd=capabilities request. 373 """Called during a ?cmd=capabilities request.
374 374
375 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
376 a CBOR response with information about available services. If no 376 a CBOR response with information about available services. If no
377 advertised services are available, we don't handle the request. 377 advertised services are available, we don't handle the request.
378 """ 378 """
379 # Fall back to old behavior unless the API server is enabled. 379 # Fall back to old behavior unless the API server is enabled.
380 if not repo.ui.configbool('experimental', 'web.apiserver'): 380 if not repo.ui.configbool(b'experimental', b'web.apiserver'):
381 return False 381 return False
382 382
383 clientapis = decodevaluefromheaders(req, b'X-HgUpgrade') 383 clientapis = decodevaluefromheaders(req, b'X-HgUpgrade')
384 protocaps = decodevaluefromheaders(req, b'X-HgProto') 384 protocaps = decodevaluefromheaders(req, b'X-HgProto')
385 if not clientapis or not protocaps: 385 if not clientapis or not protocaps:
386 return False 386 return False
387 387
388 # We currently only support CBOR responses. 388 # We currently only support CBOR responses.
389 protocaps = set(protocaps.split(' ')) 389 protocaps = set(protocaps.split(b' '))
390 if b'cbor' not in protocaps: 390 if b'cbor' not in protocaps:
391 return False 391 return False
392 392
393 descriptors = {} 393 descriptors = {}
394 394
395 for api in sorted(set(clientapis.split()) & _availableapis(repo)): 395 for api in sorted(set(clientapis.split()) & _availableapis(repo)):
396 handler = API_HANDLERS[api] 396 handler = API_HANDLERS[api]
397 397
398 descriptorfn = handler.get('apidescriptor') 398 descriptorfn = handler.get(b'apidescriptor')
399 if not descriptorfn: 399 if not descriptorfn:
400 continue 400 continue
401 401
402 descriptors[api] = descriptorfn(req, repo) 402 descriptors[api] = descriptorfn(req, repo)
403 403
404 v1caps = wireprotov1server.dispatch(repo, proto, 'capabilities') 404 v1caps = wireprotov1server.dispatch(repo, proto, b'capabilities')
405 assert isinstance(v1caps, wireprototypes.bytesresponse) 405 assert isinstance(v1caps, wireprototypes.bytesresponse)
406 406
407 m = { 407 m = {
408 # TODO allow this to be configurable. 408 # TODO allow this to be configurable.
409 'apibase': 'api/', 409 b'apibase': b'api/',
410 'apis': descriptors, 410 b'apis': descriptors,
411 'v1capabilities': v1caps.data, 411 b'v1capabilities': v1caps.data,
412 } 412 }
413 413
414 res.status = b'200 OK' 414 res.status = b'200 OK'
415 res.headers[b'Content-Type'] = b'application/mercurial-cbor' 415 res.headers[b'Content-Type'] = b'application/mercurial-cbor'
416 res.setbodybytes(b''.join(cborutil.streamencode(m))) 416 res.setbodybytes(b''.join(cborutil.streamencode(m)))
425 def genversion2(gen, engine, engineopts): 425 def genversion2(gen, engine, engineopts):
426 # application/mercurial-0.2 always sends a payload header 426 # application/mercurial-0.2 always sends a payload header
427 # identifying the compression engine. 427 # identifying the compression engine.
428 name = engine.wireprotosupport().name 428 name = engine.wireprotosupport().name
429 assert 0 < len(name) < 256 429 assert 0 < len(name) < 256
430 yield struct.pack('B', len(name)) 430 yield struct.pack(b'B', len(name))
431 yield name 431 yield name
432 432
433 for chunk in gen: 433 for chunk in gen:
434 yield chunk 434 yield chunk
435 435
436 def setresponse(code, contenttype, bodybytes=None, bodygen=None): 436 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
437 if code == HTTP_OK: 437 if code == HTTP_OK:
438 res.status = '200 Script output follows' 438 res.status = b'200 Script output follows'
439 else: 439 else:
440 res.status = hgwebcommon.statusmessage(code) 440 res.status = hgwebcommon.statusmessage(code)
441 441
442 res.headers['Content-Type'] = contenttype 442 res.headers[b'Content-Type'] = contenttype
443 443
444 if bodybytes is not None: 444 if bodybytes is not None:
445 res.setbodybytes(bodybytes) 445 res.setbodybytes(bodybytes)
446 if bodygen is not None: 446 if bodygen is not None:
447 res.setbodygen(bodygen) 447 res.setbodygen(bodygen)
448 448
449 if not wireprotov1server.commands.commandavailable(cmd, proto): 449 if not wireprotov1server.commands.commandavailable(cmd, proto):
450 setresponse( 450 setresponse(
451 HTTP_OK, 451 HTTP_OK,
452 HGERRTYPE, 452 HGERRTYPE,
453 _('requested wire protocol command is not available over ' 'HTTP'), 453 _(
454 b'requested wire protocol command is not available over '
455 b'HTTP'
456 ),
454 ) 457 )
455 return 458 return
456 459
457 proto.checkperm(wireprotov1server.commands[cmd].permission) 460 proto.checkperm(wireprotov1server.commands[cmd].permission)
458 461
459 # Possibly handle a modern client wanting to switch protocols. 462 # Possibly handle a modern client wanting to switch protocols.
460 if cmd == 'capabilities' and processcapabilitieshandshake( 463 if cmd == b'capabilities' and processcapabilitieshandshake(
461 repo, req, res, proto 464 repo, req, res, proto
462 ): 465 ):
463 466
464 return 467 return
465 468
484 if mediatype == HGTYPE2: 487 if mediatype == HGTYPE2:
485 gen = genversion2(gen, engine, engineopts) 488 gen = genversion2(gen, engine, engineopts)
486 489
487 setresponse(HTTP_OK, mediatype, bodygen=gen) 490 setresponse(HTTP_OK, mediatype, bodygen=gen)
488 elif isinstance(rsp, wireprototypes.pushres): 491 elif isinstance(rsp, wireprototypes.pushres):
489 rsp = '%d\n%s' % (rsp.res, rsp.output) 492 rsp = b'%d\n%s' % (rsp.res, rsp.output)
490 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp) 493 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
491 elif isinstance(rsp, wireprototypes.pusherr): 494 elif isinstance(rsp, wireprototypes.pusherr):
492 rsp = '0\n%s\n' % rsp.res 495 rsp = b'0\n%s\n' % rsp.res
493 res.drain = True 496 res.drain = True
494 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp) 497 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
495 elif isinstance(rsp, wireprototypes.ooberror): 498 elif isinstance(rsp, wireprototypes.ooberror):
496 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message) 499 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
497 else: 500 else:
498 raise error.ProgrammingError('hgweb.protocol internal failure', rsp) 501 raise error.ProgrammingError(b'hgweb.protocol internal failure', rsp)
499 502
500 503
501 def _sshv1respondbytes(fout, value): 504 def _sshv1respondbytes(fout, value):
502 """Send a bytes response for protocol version 1.""" 505 """Send a bytes response for protocol version 1."""
503 fout.write('%d\n' % len(value)) 506 fout.write(b'%d\n' % len(value))
504 fout.write(value) 507 fout.write(value)
505 fout.flush() 508 fout.flush()
506 509
507 510
508 def _sshv1respondstream(fout, source): 511 def _sshv1respondstream(fout, source):
538 keys = args.split() 541 keys = args.split()
539 for n in pycompat.xrange(len(keys)): 542 for n in pycompat.xrange(len(keys)):
540 argline = self._fin.readline()[:-1] 543 argline = self._fin.readline()[:-1]
541 arg, l = argline.split() 544 arg, l = argline.split()
542 if arg not in keys: 545 if arg not in keys:
543 raise error.Abort(_("unexpected parameter %r") % arg) 546 raise error.Abort(_(b"unexpected parameter %r") % arg)
544 if arg == '*': 547 if arg == b'*':
545 star = {} 548 star = {}
546 for k in pycompat.xrange(int(l)): 549 for k in pycompat.xrange(int(l)):
547 argline = self._fin.readline()[:-1] 550 argline = self._fin.readline()[:-1]
548 arg, l = argline.split() 551 arg, l = argline.split()
549 val = self._fin.read(int(l)) 552 val = self._fin.read(int(l))
550 star[arg] = val 553 star[arg] = val
551 data['*'] = star 554 data[b'*'] = star
552 else: 555 else:
553 val = self._fin.read(int(l)) 556 val = self._fin.read(int(l))
554 data[arg] = val 557 data[arg] = val
555 return [data[k] for k in keys] 558 return [data[k] for k in keys]
556 559
576 @contextlib.contextmanager 579 @contextlib.contextmanager
577 def mayberedirectstdio(self): 580 def mayberedirectstdio(self):
578 yield None 581 yield None
579 582
580 def client(self): 583 def client(self):
581 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] 584 client = encoding.environ.get(b'SSH_CLIENT', b'').split(b' ', 1)[0]
582 return 'remote:ssh:' + client 585 return b'remote:ssh:' + client
583 586
584 def addcapabilities(self, repo, caps): 587 def addcapabilities(self, repo, caps):
585 if self.name == wireprototypes.SSHV1: 588 if self.name == wireprototypes.SSHV1:
586 caps.append(b'protocaps') 589 caps.append(b'protocaps')
587 caps.append(b'batch') 590 caps.append(b'batch')
653 # 656 #
654 # protov2-serving -> protov1-serving 657 # protov2-serving -> protov1-serving
655 # Ths happens by default since protocol version 2 is the same as 658 # Ths happens by default since protocol version 2 is the same as
656 # version 1 except for the handshake. 659 # version 1 except for the handshake.
657 660
658 state = 'protov1-serving' 661 state = b'protov1-serving'
659 proto = sshv1protocolhandler(ui, fin, fout) 662 proto = sshv1protocolhandler(ui, fin, fout)
660 protoswitched = False 663 protoswitched = False
661 664
662 while not ev.is_set(): 665 while not ev.is_set():
663 if state == 'protov1-serving': 666 if state == b'protov1-serving':
664 # Commands are issued on new lines. 667 # Commands are issued on new lines.
665 request = fin.readline()[:-1] 668 request = fin.readline()[:-1]
666 669
667 # Empty lines signal to terminate the connection. 670 # Empty lines signal to terminate the connection.
668 if not request: 671 if not request:
669 state = 'shutdown' 672 state = b'shutdown'
670 continue 673 continue
671 674
672 # It looks like a protocol upgrade request. Transition state to 675 # It looks like a protocol upgrade request. Transition state to
673 # handle it. 676 # handle it.
674 if request.startswith(b'upgrade '): 677 if request.startswith(b'upgrade '):
676 _sshv1respondooberror( 679 _sshv1respondooberror(
677 fout, 680 fout,
678 ui.ferr, 681 ui.ferr,
679 b'cannot upgrade protocols multiple ' b'times', 682 b'cannot upgrade protocols multiple ' b'times',
680 ) 683 )
681 state = 'shutdown' 684 state = b'shutdown'
682 continue 685 continue
683 686
684 state = 'upgrade-initial' 687 state = b'upgrade-initial'
685 continue 688 continue
686 689
687 available = wireprotov1server.commands.commandavailable( 690 available = wireprotov1server.commands.commandavailable(
688 request, proto 691 request, proto
689 ) 692 )
713 _sshv1respondbytes(fout, rsp.res) 716 _sshv1respondbytes(fout, rsp.res)
714 elif isinstance(rsp, wireprototypes.ooberror): 717 elif isinstance(rsp, wireprototypes.ooberror):
715 _sshv1respondooberror(fout, ui.ferr, rsp.message) 718 _sshv1respondooberror(fout, ui.ferr, rsp.message)
716 else: 719 else:
717 raise error.ProgrammingError( 720 raise error.ProgrammingError(
718 'unhandled response type from ' 721 b'unhandled response type from '
719 'wire protocol command: %s' % rsp 722 b'wire protocol command: %s' % rsp
720 ) 723 )
721 724
722 # For now, protocol version 2 serving just goes back to version 1. 725 # For now, protocol version 2 serving just goes back to version 1.
723 elif state == 'protov2-serving': 726 elif state == b'protov2-serving':
724 state = 'protov1-serving' 727 state = b'protov1-serving'
725 continue 728 continue
726 729
727 elif state == 'upgrade-initial': 730 elif state == b'upgrade-initial':
728 # We should never transition into this state if we've switched 731 # We should never transition into this state if we've switched
729 # protocols. 732 # protocols.
730 assert not protoswitched 733 assert not protoswitched
731 assert proto.name == wireprototypes.SSHV1 734 assert proto.name == wireprototypes.SSHV1
732 735
736 # We treat this as an unknown command. 739 # We treat this as an unknown command.
737 try: 740 try:
738 token, caps = request.split(b' ')[1:] 741 token, caps = request.split(b' ')[1:]
739 except ValueError: 742 except ValueError:
740 _sshv1respondbytes(fout, b'') 743 _sshv1respondbytes(fout, b'')
741 state = 'protov1-serving' 744 state = b'protov1-serving'
742 continue 745 continue
743 746
744 # Send empty response if we don't support upgrading protocols. 747 # Send empty response if we don't support upgrading protocols.
745 if not ui.configbool('experimental', 'sshserver.support-v2'): 748 if not ui.configbool(b'experimental', b'sshserver.support-v2'):
746 _sshv1respondbytes(fout, b'') 749 _sshv1respondbytes(fout, b'')
747 state = 'protov1-serving' 750 state = b'protov1-serving'
748 continue 751 continue
749 752
750 try: 753 try:
751 caps = urlreq.parseqs(caps) 754 caps = urlreq.parseqs(caps)
752 except ValueError: 755 except ValueError:
753 _sshv1respondbytes(fout, b'') 756 _sshv1respondbytes(fout, b'')
754 state = 'protov1-serving' 757 state = b'protov1-serving'
755 continue 758 continue
756 759
757 # We don't see an upgrade request to protocol version 2. Ignore 760 # We don't see an upgrade request to protocol version 2. Ignore
758 # the upgrade request. 761 # the upgrade request.
759 wantedprotos = caps.get(b'proto', [b''])[0] 762 wantedprotos = caps.get(b'proto', [b''])[0]
760 if SSHV2 not in wantedprotos: 763 if SSHV2 not in wantedprotos:
761 _sshv1respondbytes(fout, b'') 764 _sshv1respondbytes(fout, b'')
762 state = 'protov1-serving' 765 state = b'protov1-serving'
763 continue 766 continue
764 767
765 # It looks like we can honor this upgrade request to protocol 2. 768 # It looks like we can honor this upgrade request to protocol 2.
766 # Filter the rest of the handshake protocol request lines. 769 # Filter the rest of the handshake protocol request lines.
767 state = 'upgrade-v2-filter-legacy-handshake' 770 state = b'upgrade-v2-filter-legacy-handshake'
768 continue 771 continue
769 772
770 elif state == 'upgrade-v2-filter-legacy-handshake': 773 elif state == b'upgrade-v2-filter-legacy-handshake':
771 # Client should have sent legacy handshake after an ``upgrade`` 774 # Client should have sent legacy handshake after an ``upgrade``
772 # request. Expected lines: 775 # request. Expected lines:
773 # 776 #
774 # hello 777 # hello
775 # between 778 # between
785 fout, 788 fout,
786 ui.ferr, 789 ui.ferr,
787 b'malformed handshake protocol: ' b'missing %s' % line, 790 b'malformed handshake protocol: ' b'missing %s' % line,
788 ) 791 )
789 ok = False 792 ok = False
790 state = 'shutdown' 793 state = b'shutdown'
791 break 794 break
792 795
793 if not ok: 796 if not ok:
794 continue 797 continue
795 798
799 fout, 802 fout,
800 ui.ferr, 803 ui.ferr,
801 b'malformed handshake protocol: ' 804 b'malformed handshake protocol: '
802 b'missing between argument value', 805 b'missing between argument value',
803 ) 806 )
804 state = 'shutdown' 807 state = b'shutdown'
805 continue 808 continue
806 809
807 state = 'upgrade-v2-finish' 810 state = b'upgrade-v2-finish'
808 continue 811 continue
809 812
810 elif state == 'upgrade-v2-finish': 813 elif state == b'upgrade-v2-finish':
811 # Send the upgrade response. 814 # Send the upgrade response.
812 fout.write(b'upgraded %s %s\n' % (token, SSHV2)) 815 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
813 servercaps = wireprotov1server.capabilities(repo, proto) 816 servercaps = wireprotov1server.capabilities(repo, proto)
814 rsp = b'capabilities: %s' % servercaps.data 817 rsp = b'capabilities: %s' % servercaps.data
815 fout.write(b'%d\n%s\n' % (len(rsp), rsp)) 818 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
816 fout.flush() 819 fout.flush()
817 820
818 proto = sshv2protocolhandler(ui, fin, fout) 821 proto = sshv2protocolhandler(ui, fin, fout)
819 protoswitched = True 822 protoswitched = True
820 823
821 state = 'protov2-serving' 824 state = b'protov2-serving'
822 continue 825 continue
823 826
824 elif state == 'shutdown': 827 elif state == b'shutdown':
825 break 828 break
826 829
827 else: 830 else:
828 raise error.ProgrammingError( 831 raise error.ProgrammingError(
829 'unhandled ssh server state: %s' % state 832 b'unhandled ssh server state: %s' % state
830 ) 833 )
831 834
832 835
833 class sshserver(object): 836 class sshserver(object):
834 def __init__(self, ui, repo, logfh=None): 837 def __init__(self, ui, repo, logfh=None):
837 self._fin, self._fout = ui.protectfinout() 840 self._fin, self._fout = ui.protectfinout()
838 841
839 # Log write I/O to stdout and stderr if configured. 842 # Log write I/O to stdout and stderr if configured.
840 if logfh: 843 if logfh:
841 self._fout = util.makeloggingfileobject( 844 self._fout = util.makeloggingfileobject(
842 logfh, self._fout, 'o', logdata=True 845 logfh, self._fout, b'o', logdata=True
843 ) 846 )
844 ui.ferr = util.makeloggingfileobject( 847 ui.ferr = util.makeloggingfileobject(
845 logfh, ui.ferr, 'e', logdata=True 848 logfh, ui.ferr, b'e', logdata=True
846 ) 849 )
847 850
848 def serve_forever(self): 851 def serve_forever(self):
849 self.serveuntil(threading.Event()) 852 self.serveuntil(threading.Event())
850 self._ui.restorefinout(self._fin, self._fout) 853 self._ui.restorefinout(self._fin, self._fout)