Mercurial > hg
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) |