Mercurial > public > mercurial-scm > hg
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 |