comparison mercurial/wireprotov2peer.py @ 40024:86b22a4cfab1

wireprotov2: client support for advertising redirect targets With the server now able to emit a redirect target descriptor, we can start to teach the client to recognize it. This commit implements support for filtering the advertised redirect targets against supported features and for advertising compatible redirect targets as part of command requests. It also adds the minimal boilerplate required to fail when a content redirect is seen. The server doesn't yet do anything with the advertised redirect targets. And the client can't yet follow redirects if it did. But at least we're putting bytes on the wire. Differential Revision: https://phab.mercurial-scm.org/D4776
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 26 Sep 2018 15:02:19 -0700
parents f5a05bb48116
children 7e807b8a9e56
comparison
equal deleted inserted replaced
40023:10cf8b116dd8 40024:86b22a4cfab1
11 11
12 from .i18n import _ 12 from .i18n import _
13 from . import ( 13 from . import (
14 encoding, 14 encoding,
15 error, 15 error,
16 sslutil,
16 wireprotoframing, 17 wireprotoframing,
17 ) 18 )
18 from .utils import ( 19 from .utils import (
19 cborutil, 20 cborutil,
20 ) 21 )
32 33
33 chunks.append(msg) 34 chunks.append(msg)
34 35
35 return b''.join(chunks) 36 return b''.join(chunks)
36 37
38 SUPPORTED_REDIRECT_PROTOCOLS = {
39 b'http',
40 b'https',
41 }
42
43 SUPPORTED_CONTENT_HASHES = {
44 b'sha1',
45 b'sha256',
46 }
47
48 def redirecttargetsupported(ui, target):
49 """Determine whether a redirect target entry is supported.
50
51 ``target`` should come from the capabilities data structure emitted by
52 the server.
53 """
54 if target.get(b'protocol') not in SUPPORTED_REDIRECT_PROTOCOLS:
55 ui.note(_('(remote redirect target %s uses unsupported protocol: %s)\n')
56 % (target[b'name'], target.get(b'protocol', b'')))
57 return False
58
59 if target.get(b'snirequired') and not sslutil.hassni:
60 ui.note(_('(redirect target %s requires SNI, which is unsupported)\n') %
61 target[b'name'])
62 return False
63
64 if b'tlsversions' in target:
65 tlsversions = set(target[b'tlsversions'])
66 supported = set()
67
68 for v in sslutil.supportedprotocols:
69 assert v.startswith(b'tls')
70 supported.add(v[3:])
71
72 if not tlsversions & supported:
73 ui.note(_('(remote redirect target %s requires unsupported TLS '
74 'versions: %s)\n') % (
75 target[b'name'], b', '.join(sorted(tlsversions))))
76 return False
77
78 ui.note(_('(remote redirect target %s is compatible)\n') % target[b'name'])
79
80 return True
81
82 def supportedredirects(ui, apidescriptor):
83 """Resolve the "redirect" command request key given an API descriptor.
84
85 Given an API descriptor returned by the server, returns a data structure
86 that can be used in hte "redirect" field of command requests to advertise
87 support for compatible redirect targets.
88
89 Returns None if no redirect targets are remotely advertised or if none are
90 supported.
91 """
92 if not apidescriptor or b'redirect' not in apidescriptor:
93 return None
94
95 targets = [t[b'name'] for t in apidescriptor[b'redirect'][b'targets']
96 if redirecttargetsupported(ui, t)]
97
98 hashes = [h for h in apidescriptor[b'redirect'][b'hashes']
99 if h in SUPPORTED_CONTENT_HASHES]
100
101 return {
102 b'targets': targets,
103 b'hashes': hashes,
104 }
105
37 class commandresponse(object): 106 class commandresponse(object):
38 """Represents the response to a command request. 107 """Represents the response to a command request.
39 108
40 Instances track the state of the command and hold its results. 109 Instances track the state of the command and hold its results.
41 110
85 154
86 self._serviceable.set() 155 self._serviceable.set()
87 156
88 def _handleinitial(self, o): 157 def _handleinitial(self, o):
89 self._seeninitial = True 158 self._seeninitial = True
90 if o[b'status'] == 'ok': 159 if o[b'status'] == b'ok':
91 return 160 return
161
162 elif o[b'status'] == b'redirect':
163 raise error.Abort(_('redirect responses not yet supported'))
92 164
93 atoms = [{'msg': o[b'error'][b'message']}] 165 atoms = [{'msg': o[b'error'][b'message']}]
94 if b'args' in o[b'error']: 166 if b'args' in o[b'error']:
95 atoms[0]['args'] = o[b'error'][b'args'] 167 atoms[0]['args'] = o[b'error'][b'args']
96 168
148 self._requests = {} 220 self._requests = {}
149 self._futures = {} 221 self._futures = {}
150 self._responses = {} 222 self._responses = {}
151 self._frameseof = False 223 self._frameseof = False
152 224
153 def callcommand(self, command, args, f): 225 def callcommand(self, command, args, f, redirect=None):
154 """Register a request to call a command. 226 """Register a request to call a command.
155 227
156 Returns an iterable of frames that should be sent over the wire. 228 Returns an iterable of frames that should be sent over the wire.
157 """ 229 """
158 request, action, meta = self._reactor.callcommand(command, args) 230 request, action, meta = self._reactor.callcommand(command, args,
231 redirect=redirect)
159 232
160 if action != 'noop': 233 if action != 'noop':
161 raise error.ProgrammingError('%s not yet supported' % action) 234 raise error.ProgrammingError('%s not yet supported' % action)
162 235
163 rid = request.requestid 236 rid = request.requestid