Merge lp:~jelmer/brz/kill-pycurl into lp:brz
- kill-pycurl
- Merge into trunk
Proposed by
Jelmer Vernooij
Status: | Merged | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Jelmer Vernooij | ||||||||||||||||||||||||
Approved revision: | no longer in the source branch. | ||||||||||||||||||||||||
Merge reported by: | The Breezy Bot | ||||||||||||||||||||||||
Merged at revision: | not available | ||||||||||||||||||||||||
Proposed branch: | lp:~jelmer/brz/kill-pycurl | ||||||||||||||||||||||||
Merge into: | lp:brz | ||||||||||||||||||||||||
Diff against target: |
991 lines (+20/-691) 9 files modified
breezy/tests/features.py (+0/-1) breezy/tests/http_server.py (+0/-22) breezy/tests/https_server.py (+0/-14) breezy/tests/test_bzrdir.py (+0/-12) breezy/tests/test_http.py (+11/-169) breezy/transport/__init__.py (+4/-18) breezy/transport/http/__init__.py (+1/-1) breezy/transport/http/_pycurl.py (+0/-454) doc/en/release-notes/brz-3.0.txt (+4/-0) |
||||||||||||||||||||||||
To merge this branch: | bzr merge lp:~jelmer/brz/kill-pycurl | ||||||||||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Packman | Approve | ||
Review via email: mp+325340@code.launchpad.net |
Commit message
Remove pycurl support.
Description of the change
Remove pycurl, go back to a single http implementation.
Remove the http+urllib:// bits, just use plain http URLs everywhere. This addresses several bugs.
To post a comment you must log in.
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
A commit message must be set
http://
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'breezy/tests/features.py' |
2 | --- breezy/tests/features.py 2017-05-22 00:56:52 +0000 |
3 | +++ breezy/tests/features.py 2017-06-08 20:56:03 +0000 |
4 | @@ -350,7 +350,6 @@ |
5 | lzma = ModuleAvailableFeature('lzma') |
6 | meliae = ModuleAvailableFeature('meliae.scanner') |
7 | paramiko = ModuleAvailableFeature('paramiko') |
8 | -pycurl = ModuleAvailableFeature('pycurl') |
9 | pywintypes = ModuleAvailableFeature('pywintypes') |
10 | subunit = ModuleAvailableFeature('subunit') |
11 | testtools = ModuleAvailableFeature('testtools') |
12 | |
13 | === modified file 'breezy/tests/http_server.py' |
14 | --- breezy/tests/http_server.py 2017-05-24 20:47:30 +0000 |
15 | +++ breezy/tests/http_server.py 2017-06-08 20:56:03 +0000 |
16 | @@ -67,14 +67,6 @@ |
17 | self.headers.get('referer', '-'), |
18 | self.headers.get('user-agent', '-')) |
19 | |
20 | - def handle(self): |
21 | - SimpleHTTPServer.SimpleHTTPRequestHandler.handle(self) |
22 | - # Some client (pycurl, I'm looking at you) are more picky than others |
23 | - # and require that the socket itself is closed |
24 | - # (SocketServer.StreamRequestHandler only close the two associated |
25 | - # 'makefile' objects) |
26 | - self.connection.close() |
27 | - |
28 | def handle_one_request(self): |
29 | """Handle a single HTTP request. |
30 | |
31 | @@ -502,17 +494,3 @@ |
32 | |
33 | # urls returned by this server should require the urllib client impl |
34 | _url_protocol = 'http+urllib' |
35 | - |
36 | - |
37 | -class HttpServer_PyCurl(HttpServer): |
38 | - """Subclass of HttpServer that gives http+pycurl urls. |
39 | - |
40 | - This is for use in testing: connections to this server will always go |
41 | - through pycurl where possible. |
42 | - """ |
43 | - |
44 | - # We don't care about checking the pycurl availability as |
45 | - # this server will be required only when pycurl is present |
46 | - |
47 | - # urls returned by this server should require the pycurl client impl |
48 | - _url_protocol = 'http+pycurl' |
49 | |
50 | === modified file 'breezy/tests/https_server.py' |
51 | --- breezy/tests/https_server.py 2017-05-22 00:56:52 +0000 |
52 | +++ breezy/tests/https_server.py 2017-06-08 20:56:03 +0000 |
53 | @@ -134,17 +134,3 @@ |
54 | |
55 | # urls returned by this server should require the urllib client impl |
56 | _url_protocol = 'https+urllib' |
57 | - |
58 | - |
59 | -class HTTPSServer_PyCurl(HTTPSServer): |
60 | - """Subclass of HTTPSServer that gives http+pycurl urls. |
61 | - |
62 | - This is for use in testing: connections to this server will always go |
63 | - through pycurl where possible. |
64 | - """ |
65 | - |
66 | - # We don't care about checking the pycurl availability as |
67 | - # this server will be required only when pycurl is present |
68 | - |
69 | - # urls returned by this server should require the pycurl client impl |
70 | - _url_protocol = 'https+pycurl' |
71 | |
72 | === modified file 'breezy/tests/test_bzrdir.py' |
73 | --- breezy/tests/test_bzrdir.py 2017-06-04 22:57:11 +0000 |
74 | +++ breezy/tests/test_bzrdir.py 2017-06-08 20:56:03 +0000 |
75 | @@ -61,7 +61,6 @@ |
76 | http_server, |
77 | http_utils, |
78 | ) |
79 | -from .test_http import TestWithTransport_pycurl |
80 | from ..transport import ( |
81 | memory, |
82 | pathfilter, |
83 | @@ -1179,17 +1178,6 @@ |
84 | |
85 | |
86 | |
87 | -class TestHTTPRedirections_pycurl(TestWithTransport_pycurl, |
88 | - TestHTTPRedirections, |
89 | - http_utils.TestCaseWithTwoWebservers): |
90 | - """Tests redirections for pycurl implementation""" |
91 | - |
92 | - def _qualified_url(self, host, port): |
93 | - result = 'http+pycurl://%s:%s' % (host, port) |
94 | - self.permit_url(result) |
95 | - return result |
96 | - |
97 | - |
98 | class TestHTTPRedirections_nosmart(TestHTTPRedirections, |
99 | http_utils.TestCaseWithTwoWebservers): |
100 | """Tests redirections for the nosmart decorator""" |
101 | |
102 | === modified file 'breezy/tests/test_http.py' |
103 | --- breezy/tests/test_http.py 2017-05-25 01:35:55 +0000 |
104 | +++ breezy/tests/test_http.py 2017-06-08 20:56:03 +0000 |
105 | @@ -63,25 +63,16 @@ |
106 | ) |
107 | |
108 | |
109 | -if features.pycurl.available(): |
110 | - from ..transport.http._pycurl import PyCurlTransport |
111 | - |
112 | - |
113 | load_tests = load_tests_apply_scenarios |
114 | |
115 | |
116 | def vary_by_http_client_implementation(): |
117 | - """Test the two libraries we can use, pycurl and urllib.""" |
118 | + """Test the libraries we can use, currently just urllib.""" |
119 | transport_scenarios = [ |
120 | ('urllib', dict(_transport=_urllib.HttpTransport_urllib, |
121 | _server=http_server.HttpServer_urllib, |
122 | _url_protocol='http+urllib',)), |
123 | ] |
124 | - if features.pycurl.available(): |
125 | - transport_scenarios.append( |
126 | - ('pycurl', dict(_transport=PyCurlTransport, |
127 | - _server=http_server.HttpServer_PyCurl, |
128 | - _url_protocol='http+pycurl',))) |
129 | return transport_scenarios |
130 | |
131 | |
132 | @@ -128,14 +119,10 @@ |
133 | ('urllib,http', dict(_activity_server=ActivityHTTPServer, |
134 | _transport=_urllib.HttpTransport_urllib,)), |
135 | ] |
136 | - if features.pycurl.available(): |
137 | - activity_scenarios.append( |
138 | - ('pycurl,http', dict(_activity_server=ActivityHTTPServer, |
139 | - _transport=PyCurlTransport,)),) |
140 | if features.HTTPSServerFeature.available(): |
141 | # FIXME: Until we have a better way to handle self-signed certificates |
142 | # (like allowing them in a test specific authentication.conf for |
143 | - # example), we need some specialized pycurl/urllib transport for tests. |
144 | + # example), we need some specialized urllib transport for tests. |
145 | # -- vila 2012-01-20 |
146 | from . import ( |
147 | ssl_certs, |
148 | @@ -150,17 +137,6 @@ |
149 | activity_scenarios.append( |
150 | ('urllib,https', dict(_activity_server=ActivityHTTPSServer, |
151 | _transport=HTTPS_urllib_transport,)),) |
152 | - if features.pycurl.available(): |
153 | - class HTTPS_pycurl_transport(PyCurlTransport): |
154 | - |
155 | - def __init__(self, base, _from_transport=None): |
156 | - super(HTTPS_pycurl_transport, self).__init__( |
157 | - base, _from_transport) |
158 | - self.cabundle = str(ssl_certs.build_path('ca.crt')) |
159 | - |
160 | - activity_scenarios.append( |
161 | - ('pycurl,https', dict(_activity_server=ActivityHTTPSServer, |
162 | - _transport=HTTPS_pycurl_transport,)),) |
163 | return activity_scenarios |
164 | |
165 | |
166 | @@ -385,16 +361,6 @@ |
167 | http_server.TestingHTTPServer) |
168 | |
169 | |
170 | -class TestWithTransport_pycurl(object): |
171 | - """Test case to inherit from if pycurl is present""" |
172 | - |
173 | - def _get_pycurl_maybe(self): |
174 | - self.requireFeature(features.pycurl) |
175 | - return PyCurlTransport |
176 | - |
177 | - _transport = property(_get_pycurl_maybe) |
178 | - |
179 | - |
180 | class TestHttpTransportUrls(tests.TestCase): |
181 | """Test the http urls.""" |
182 | |
183 | @@ -435,42 +401,6 @@ |
184 | server.stop_server() |
185 | |
186 | |
187 | -class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase): |
188 | - |
189 | - # TODO: This should really be moved into another pycurl |
190 | - # specific test. When https tests will be implemented, take |
191 | - # this one into account. |
192 | - def test_pycurl_without_https_support(self): |
193 | - """Test that pycurl without SSL do not fail with a traceback. |
194 | - |
195 | - For the purpose of the test, we force pycurl to ignore |
196 | - https by supplying a fake version_info that do not |
197 | - support it. |
198 | - """ |
199 | - self.requireFeature(features.pycurl) |
200 | - # Import the module locally now that we now it's available. |
201 | - pycurl = features.pycurl.module |
202 | - |
203 | - self.overrideAttr(pycurl, 'version_info', |
204 | - # Fake the pycurl version_info This was taken from |
205 | - # a windows pycurl without SSL (thanks to bialix) |
206 | - lambda : (2, |
207 | - '7.13.2', |
208 | - 462082, |
209 | - 'i386-pc-win32', |
210 | - 2576, |
211 | - None, |
212 | - 0, |
213 | - None, |
214 | - ('ftp', 'gopher', 'telnet', |
215 | - 'dict', 'ldap', 'http', 'file'), |
216 | - None, |
217 | - 0, |
218 | - None)) |
219 | - self.assertRaises(errors.DependencyNotPresent, self._transport, |
220 | - 'https://launchpad.net') |
221 | - |
222 | - |
223 | class TestHTTPConnections(http_utils.TestCaseWithWebserver): |
224 | """Test the http connections.""" |
225 | |
226 | @@ -613,11 +543,6 @@ |
227 | server._url_protocol = self._url_protocol |
228 | return server |
229 | |
230 | - def _testing_pycurl(self): |
231 | - # TODO: This is duplicated for lots of the classes in this file |
232 | - return (features.pycurl.available() |
233 | - and self._transport == PyCurlTransport) |
234 | - |
235 | |
236 | class WallRequestHandler(http_server.TestingHTTPRequestHandler): |
237 | """Whatever request comes in, close the connection""" |
238 | @@ -638,10 +563,9 @@ |
239 | # for details) make no distinction between a closed |
240 | # socket and badly formatted status line, so we can't |
241 | # just test for ConnectionError, we have to test |
242 | - # InvalidHttpResponse too. And pycurl may raise ConnectionReset |
243 | - # instead of ConnectionError too. |
244 | - self.assertRaises(( errors.ConnectionError, errors.ConnectionReset, |
245 | - errors.InvalidHttpResponse), |
246 | + # InvalidHttpResponse too. |
247 | + self.assertRaises((errors.ConnectionError, |
248 | + errors.InvalidHttpResponse), |
249 | t.has, 'foo/bar') |
250 | |
251 | def test_http_get(self): |
252 | @@ -733,12 +657,6 @@ |
253 | |
254 | _req_handler_class = BadProtocolRequestHandler |
255 | |
256 | - def setUp(self): |
257 | - if self._testing_pycurl(): |
258 | - raise tests.TestNotApplicable( |
259 | - "pycurl doesn't check the protocol version") |
260 | - super(TestBadProtocolServer, self).setUp() |
261 | - |
262 | def test_http_has(self): |
263 | t = self.get_readonly_transport() |
264 | self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar') |
265 | @@ -1047,16 +965,12 @@ |
266 | ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1)))) |
267 | self.assertEqual((0, '0'), next(ireadv)) |
268 | self.assertEqual((2, '2'), next(ireadv)) |
269 | - if not self._testing_pycurl(): |
270 | - # Only one request have been issued so far (except for pycurl that |
271 | - # try to read the whole response at once) |
272 | - self.assertEqual(1, server.GET_request_nb) |
273 | + # Only one request have been issued so far |
274 | + self.assertEqual(1, server.GET_request_nb) |
275 | self.assertEqual((4, '45'), next(ireadv)) |
276 | self.assertEqual((9, '9'), next(ireadv)) |
277 | - # Both implementations issue 3 requests but: |
278 | - # - urllib does two multiple (4 ranges, then 2 ranges) then a single |
279 | - # range, |
280 | - # - pycurl does two multiple (4 ranges, 4 ranges) then a single range |
281 | + # We issue 3 requests: two multiple (4 ranges, then 2 ranges) then a |
282 | + # single range. |
283 | self.assertEqual(3, server.GET_request_nb) |
284 | # Finally the client have tried a single range request and stays in |
285 | # that mode |
286 | @@ -1276,21 +1190,9 @@ |
287 | # Let's setup some attributes for tests |
288 | server = self.get_readonly_server() |
289 | self.server_host_port = '%s:%d' % (server.host, server.port) |
290 | - if self._testing_pycurl(): |
291 | - # Oh my ! pycurl does not check for the port as part of |
292 | - # no_proxy :-( So we just test the host part |
293 | - self.no_proxy_host = server.host |
294 | - else: |
295 | - self.no_proxy_host = self.server_host_port |
296 | + self.no_proxy_host = self.server_host_port |
297 | # The secondary server is the proxy |
298 | self.proxy_url = self.get_secondary_url() |
299 | - if self._testing_pycurl(): |
300 | - self.proxy_url = self.proxy_url.replace('+pycurl', '') |
301 | - |
302 | - def _testing_pycurl(self): |
303 | - # TODO: This is duplicated for lots of the classes in this file |
304 | - return (features.pycurl.available() |
305 | - and self._transport == PyCurlTransport) |
306 | |
307 | def assertProxied(self): |
308 | t = self.get_readonly_transport() |
309 | @@ -1305,12 +1207,6 @@ |
310 | self.assertProxied() |
311 | |
312 | def test_HTTP_PROXY(self): |
313 | - if self._testing_pycurl(): |
314 | - # pycurl does not check HTTP_PROXY for security reasons |
315 | - # (for use in a CGI context that we do not care |
316 | - # about. Should we ?) |
317 | - raise tests.TestNotApplicable( |
318 | - 'pycurl does not check HTTP_PROXY for security reasons') |
319 | self.overrideEnv('HTTP_PROXY', self.proxy_url) |
320 | self.assertProxied() |
321 | |
322 | @@ -1328,9 +1224,6 @@ |
323 | self.assertNotProxied() |
324 | |
325 | def test_HTTP_PROXY_with_NO_PROXY(self): |
326 | - if self._testing_pycurl(): |
327 | - raise tests.TestNotApplicable( |
328 | - 'pycurl does not check HTTP_PROXY for security reasons') |
329 | self.overrideEnv('NO_PROXY', self.no_proxy_host) |
330 | self.overrideEnv('HTTP_PROXY', self.proxy_url) |
331 | self.assertNotProxied() |
332 | @@ -1347,13 +1240,7 @@ |
333 | |
334 | def test_http_proxy_without_scheme(self): |
335 | self.overrideEnv('http_proxy', self.server_host_port) |
336 | - if self._testing_pycurl(): |
337 | - # pycurl *ignores* invalid proxy env variables. If that ever change |
338 | - # in the future, this test will fail indicating that pycurl do not |
339 | - # ignore anymore such variables. |
340 | - self.assertNotProxied() |
341 | - else: |
342 | - self.assertRaises(errors.InvalidURL, self.assertProxied) |
343 | + self.assertRaises(errors.InvalidURL, self.assertProxied) |
344 | |
345 | |
346 | class TestRanges(http_utils.TestCaseWithWebserver): |
347 | @@ -1475,11 +1362,6 @@ |
348 | do not redirect at all in fact). The mechanism is still in |
349 | place at the _urllib2_wrappers.Request level and these tests |
350 | exercise it. |
351 | - |
352 | - For the pycurl implementation |
353 | - the redirection have been deleted as we may deprecate pycurl |
354 | - and I have no place to keep a working implementation. |
355 | - -- vila 20070212 |
356 | """ |
357 | |
358 | scenarios = multiply_scenarios( |
359 | @@ -1488,10 +1370,6 @@ |
360 | ) |
361 | |
362 | def setUp(self): |
363 | - if (features.pycurl.available() |
364 | - and self._transport == PyCurlTransport): |
365 | - raise tests.TestNotApplicable( |
366 | - "pycurl doesn't redirect silently anymore") |
367 | super(TestHTTPSilentRedirections, self).setUp() |
368 | install_redirected_request(self) |
369 | cleanup_http_redirection_connections(self) |
370 | @@ -1635,11 +1513,6 @@ |
371 | server._url_protocol = self._url_protocol |
372 | return server |
373 | |
374 | - def _testing_pycurl(self): |
375 | - # TODO: This is duplicated for lots of the classes in this file |
376 | - return (features.pycurl.available() |
377 | - and self._transport == PyCurlTransport) |
378 | - |
379 | def get_user_url(self, user, password): |
380 | """Build an url embedding user and password""" |
381 | url = '%s://' % self.server._url_protocol |
382 | @@ -1695,11 +1568,6 @@ |
383 | self.assertEqual(2, self.server.auth_required_errors) |
384 | |
385 | def test_prompt_for_username(self): |
386 | - if self._testing_pycurl(): |
387 | - raise tests.TestNotApplicable( |
388 | - 'pycurl cannot prompt, it handles auth by embedding' |
389 | - ' user:pass in urls only') |
390 | - |
391 | self.server.add_user('joe', 'foo') |
392 | t = self.get_user_transport(None, None) |
393 | ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n') |
394 | @@ -1715,11 +1583,6 @@ |
395 | stderr.readline()) |
396 | |
397 | def test_prompt_for_password(self): |
398 | - if self._testing_pycurl(): |
399 | - raise tests.TestNotApplicable( |
400 | - 'pycurl cannot prompt, it handles auth by embedding' |
401 | - ' user:pass in urls only') |
402 | - |
403 | self.server.add_user('joe', 'foo') |
404 | t = self.get_user_transport('joe', None) |
405 | ui.ui_factory = tests.TestUIFactory(stdin='foo\n') |
406 | @@ -1754,11 +1617,6 @@ |
407 | self.server.auth_realm)) |
408 | |
409 | def test_no_prompt_for_password_when_using_auth_config(self): |
410 | - if self._testing_pycurl(): |
411 | - raise tests.TestNotApplicable( |
412 | - 'pycurl does not support authentication.conf' |
413 | - ' since it cannot prompt') |
414 | - |
415 | user =' joe' |
416 | password = 'foo' |
417 | stdin_content = 'bar\n' # Not the right password |
418 | @@ -1779,9 +1637,6 @@ |
419 | if self._auth_server not in (http_utils.HTTPDigestAuthServer, |
420 | http_utils.ProxyDigestAuthServer): |
421 | raise tests.TestNotApplicable('HTTP/proxy auth digest only test') |
422 | - if self._testing_pycurl(): |
423 | - self.knownFailure( |
424 | - 'pycurl does not handle a nonce change') |
425 | self.server.add_user('joe', 'foo') |
426 | t = self.get_user_transport('joe', 'foo') |
427 | self.assertEqual('contents of a\n', t.get('a').read()) |
428 | @@ -1797,9 +1652,6 @@ |
429 | self.assertEqual(2, self.server.auth_required_errors) |
430 | |
431 | def test_user_from_auth_conf(self): |
432 | - if self._testing_pycurl(): |
433 | - raise tests.TestNotApplicable( |
434 | - 'pycurl does not support authentication.conf') |
435 | user = 'joe' |
436 | password = 'foo' |
437 | self.server.add_user(user, password) |
438 | @@ -1862,19 +1714,9 @@ |
439 | |
440 | def get_user_transport(self, user, password): |
441 | proxy_url = self.get_user_url(user, password) |
442 | - if self._testing_pycurl(): |
443 | - proxy_url = proxy_url.replace('+pycurl', '') |
444 | self.overrideEnv('all_proxy', proxy_url) |
445 | return TestAuth.get_user_transport(self, user, password) |
446 | |
447 | - def test_empty_pass(self): |
448 | - if self._testing_pycurl(): |
449 | - import pycurl |
450 | - if pycurl.version_info()[1] < '7.16.0': |
451 | - self.knownFailure( |
452 | - 'pycurl < 7.16.0 does not handle empty proxy passwords') |
453 | - super(TestProxyAuth, self).test_empty_pass() |
454 | - |
455 | |
456 | class NonClosingBytesIO(io.BytesIO): |
457 | |
458 | |
459 | === modified file 'breezy/transport/__init__.py' |
460 | --- breezy/transport/__init__.py 2017-05-30 19:32:13 +0000 |
461 | +++ breezy/transport/__init__.py 2017-06-08 20:56:03 +0000 |
462 | @@ -103,10 +103,10 @@ |
463 | register_transport_provider( ) ( and the "lazy" variant ) |
464 | |
465 | This is needed because: |
466 | - a) a single provider can support multiple protocols ( like the ftp |
467 | - provider which supports both the ftp:// and the aftp:// protocols ) |
468 | - b) a single protocol can have multiple providers ( like the http:// |
469 | - protocol which is supported by both the urllib and pycurl provider ) |
470 | + a) a single provider can support multiple protocols (like the ftp |
471 | + provider which supports both the ftp:// and the aftp:// protocols) |
472 | + b) a single protocol can have multiple providers (like the http:// |
473 | + protocol which was supported by both the urllib and pycurl providers) |
474 | """ |
475 | |
476 | def register_transport_provider(self, key, obj): |
477 | @@ -1752,28 +1752,14 @@ |
478 | register_netloc=True) |
479 | register_lazy_transport(b'https+urllib://', 'breezy.transport.http._urllib', |
480 | 'HttpTransport_urllib') |
481 | -register_transport_proto(b'http+pycurl://', |
482 | -# help="Read-only access of branches exported on the web." |
483 | - register_netloc=True) |
484 | -register_lazy_transport(b'http+pycurl://', 'breezy.transport.http._pycurl', |
485 | - 'PyCurlTransport') |
486 | -register_transport_proto(b'https+pycurl://', |
487 | -# help="Read-only access of branches exported on the web using SSL." |
488 | - register_netloc=True) |
489 | -register_lazy_transport(b'https+pycurl://', 'breezy.transport.http._pycurl', |
490 | - 'PyCurlTransport') |
491 | # Default http transports (last declared wins (if it can be imported)) |
492 | register_transport_proto(b'http://', |
493 | help="Read-only access of branches exported on the web.") |
494 | register_transport_proto(b'https://', |
495 | help="Read-only access of branches exported on the web using SSL.") |
496 | # The default http implementation is urllib |
497 | -register_lazy_transport(b'http://', 'breezy.transport.http._pycurl', |
498 | - 'PyCurlTransport') |
499 | register_lazy_transport(b'http://', 'breezy.transport.http._urllib', |
500 | 'HttpTransport_urllib') |
501 | -register_lazy_transport(b'https://', 'breezy.transport.http._pycurl', |
502 | - 'PyCurlTransport') |
503 | register_lazy_transport(b'https://', 'breezy.transport.http._urllib', |
504 | 'HttpTransport_urllib') |
505 | |
506 | |
507 | === modified file 'breezy/transport/http/__init__.py' |
508 | --- breezy/transport/http/__init__.py 2017-06-05 23:15:32 +0000 |
509 | +++ breezy/transport/http/__init__.py 2017-06-08 20:56:03 +0000 |
510 | @@ -506,7 +506,7 @@ |
511 | # credentials (if they don't apply, the redirected to server |
512 | # will tell us, but if they do apply, we avoid prompting the |
513 | # user) |
514 | - redir_scheme = parsed_target.scheme + '+' + self._impl_name |
515 | + redir_scheme = parsed_target.scheme |
516 | new_url = self._unsplit_url(redir_scheme, |
517 | self._parsed_url.user, |
518 | self._parsed_url.password, |
519 | |
520 | === removed file 'breezy/transport/http/_pycurl.py' |
521 | --- breezy/transport/http/_pycurl.py 2017-05-22 00:56:52 +0000 |
522 | +++ breezy/transport/http/_pycurl.py 1970-01-01 00:00:00 +0000 |
523 | @@ -1,454 +0,0 @@ |
524 | -# Copyright (C) 2006-2011, 2017 Canonical Ltd |
525 | -# |
526 | -# This program is free software; you can redistribute it and/or modify |
527 | -# it under the terms of the GNU General Public License as published by |
528 | -# the Free Software Foundation; either version 2 of the License, or |
529 | -# (at your option) any later version. |
530 | -# |
531 | -# This program is distributed in the hope that it will be useful, |
532 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
533 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
534 | -# GNU General Public License for more details. |
535 | -# |
536 | -# You should have received a copy of the GNU General Public License |
537 | -# along with this program; if not, write to the Free Software |
538 | -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
539 | - |
540 | -"""http/https transport using pycurl""" |
541 | - |
542 | -from __future__ import absolute_import |
543 | - |
544 | -# TODO: test reporting of http errors |
545 | -# |
546 | -# TODO: Transport option to control caching of particular requests; broadly we |
547 | -# would want to offer "caching allowed" or "must revalidate", depending on |
548 | -# whether we expect a particular file will be modified after it's committed. |
549 | -# It's probably safer to just always revalidate. mbp 20060321 |
550 | - |
551 | -# TODO: Some refactoring could be done to avoid the strange idiom |
552 | -# used to capture data and headers while setting up the request |
553 | -# (and having to pass 'header' to _curl_perform to handle |
554 | -# redirections) . This could be achieved by creating a |
555 | -# specialized Curl object and returning code, headers and data |
556 | -# from _curl_perform. Not done because we may deprecate pycurl in the |
557 | -# future -- vila 20070212 |
558 | - |
559 | -import httplib |
560 | - |
561 | -import breezy |
562 | -from ... import ( |
563 | - debug, |
564 | - errors, |
565 | - trace, |
566 | - ) |
567 | -from ...sixish import ( |
568 | - BytesIO, |
569 | - ) |
570 | -from ...transport.http import ( |
571 | - ca_bundle, |
572 | - HttpTransportBase, |
573 | - response, |
574 | - unhtml_roughly, |
575 | - ) |
576 | - |
577 | -try: |
578 | - import pycurl |
579 | -except ImportError as e: |
580 | - trace.mutter("failed to import pycurl: %s", e) |
581 | - raise errors.DependencyNotPresent('pycurl', e) |
582 | - |
583 | -try: |
584 | - # see if we can actually initialize PyCurl - sometimes it will load but |
585 | - # fail to start up due to this bug: |
586 | - # |
587 | - # 32. (At least on Windows) If libcurl is built with c-ares and there's |
588 | - # no DNS server configured in the system, the ares_init() call fails and |
589 | - # thus curl_easy_init() fails as well. This causes weird effects for |
590 | - # people who use numerical IP addresses only. |
591 | - # |
592 | - # reported by Alexander Belchenko, 2006-04-26 |
593 | - pycurl.Curl() |
594 | -except pycurl.error as e: |
595 | - trace.mutter("failed to initialize pycurl: %s", e) |
596 | - raise errors.DependencyNotPresent('pycurl', e) |
597 | - |
598 | - |
599 | - |
600 | - |
601 | -def _get_pycurl_errcode(symbol, default): |
602 | - """ |
603 | - Returns the numerical error code for a symbol defined by pycurl. |
604 | - |
605 | - Different pycurl implementations define different symbols for error |
606 | - codes. Old versions never define some symbols (wether they can return the |
607 | - corresponding error code or not). The following addresses the problem by |
608 | - defining the symbols we care about. Note: this allows to define symbols |
609 | - for errors that older versions will never return, which is fine. |
610 | - """ |
611 | - return pycurl.__dict__.get(symbol, default) |
612 | - |
613 | -# Yes, weird but returned on weird http error (invalid status line) |
614 | -CURLE_FTP_WEIRD_SERVER_REPLY = _get_pycurl_errcode( |
615 | - 'E_FTP_WEIRD_SERVER_REPLY', 8) |
616 | -CURLE_COULDNT_CONNECT = _get_pycurl_errcode('E_COULDNT_CONNECT', 7) |
617 | -CURLE_COULDNT_RESOLVE_HOST = _get_pycurl_errcode('E_COULDNT_RESOLVE_HOST', 6) |
618 | -CURLE_COULDNT_RESOLVE_PROXY = _get_pycurl_errcode('E_COULDNT_RESOLVE_PROXY', 5) |
619 | -CURLE_GOT_NOTHING = _get_pycurl_errcode('E_GOT_NOTHING', 52) |
620 | -CURLE_PARTIAL_FILE = _get_pycurl_errcode('E_PARTIAL_FILE', 18) |
621 | -CURLE_SEND_ERROR = _get_pycurl_errcode('E_SEND_ERROR', 55) |
622 | -CURLE_RECV_ERROR = _get_pycurl_errcode('E_RECV_ERROR', 56) |
623 | -CURLE_SSL_CACERT = _get_pycurl_errcode('E_SSL_CACERT', 60) |
624 | -CURLE_SSL_CACERT_BADFILE = _get_pycurl_errcode('E_SSL_CACERT_BADFILE', 77) |
625 | - |
626 | - |
627 | -class PyCurlTransport(HttpTransportBase): |
628 | - """http client transport using pycurl |
629 | - |
630 | - PyCurl is a Python binding to the C "curl" multiprotocol client. |
631 | - |
632 | - This transport can be significantly faster than the builtin |
633 | - Python client. Advantages include: DNS caching. |
634 | - """ |
635 | - |
636 | - def __init__(self, base, _from_transport=None): |
637 | - super(PyCurlTransport, self).__init__(base, 'pycurl', |
638 | - _from_transport=_from_transport) |
639 | - if self._unqualified_scheme == 'https': |
640 | - # Check availability of https into pycurl supported |
641 | - # protocols |
642 | - supported = pycurl.version_info()[8] |
643 | - if 'https' not in supported: |
644 | - raise errors.DependencyNotPresent('pycurl', 'no https support') |
645 | - self.cabundle = ca_bundle.get_ca_path() |
646 | - |
647 | - def _get_curl(self): |
648 | - connection = self._get_connection() |
649 | - if connection is None: |
650 | - # First connection ever. There is no credentials for pycurl, either |
651 | - # the password was embedded in the URL or it's not needed. The |
652 | - # connection for pycurl is just the Curl object, it will not |
653 | - # connect to the http server until the first request (which had |
654 | - # just called us). |
655 | - connection = pycurl.Curl() |
656 | - # First request, initialize credentials. |
657 | - auth = self._create_auth() |
658 | - # Proxy handling is out of reach, so we punt |
659 | - self._set_connection(connection, auth) |
660 | - return connection |
661 | - |
662 | - def disconnect(self): |
663 | - connection = self._get_connection() |
664 | - if connection is not None: |
665 | - connection.close() |
666 | - |
667 | - def has(self, relpath): |
668 | - """See Transport.has()""" |
669 | - # We set NO BODY=0 in _get_full, so it should be safe |
670 | - # to re-use the non-range curl object |
671 | - curl = self._get_curl() |
672 | - abspath = self._remote_path(relpath) |
673 | - curl.setopt(pycurl.URL, abspath) |
674 | - self._set_curl_options(curl) |
675 | - curl.setopt(pycurl.HTTPGET, 1) |
676 | - # don't want the body - ie just do a HEAD request |
677 | - # This means "NO BODY" not 'nobody' |
678 | - curl.setopt(pycurl.NOBODY, 1) |
679 | - # But we need headers to handle redirections |
680 | - header = BytesIO() |
681 | - curl.setopt(pycurl.HEADERFUNCTION, header.write) |
682 | - # In some erroneous cases, pycurl will emit text on |
683 | - # stdout if we don't catch it (see InvalidStatus tests |
684 | - # for one such occurrence). |
685 | - blackhole = BytesIO() |
686 | - curl.setopt(pycurl.WRITEFUNCTION, blackhole.write) |
687 | - self._curl_perform(curl, header) |
688 | - code = curl.getinfo(pycurl.HTTP_CODE) |
689 | - if code == 404: # not found |
690 | - return False |
691 | - elif code == 200: # "ok" |
692 | - return True |
693 | - else: |
694 | - self._raise_curl_http_error(curl) |
695 | - |
696 | - def _get(self, relpath, offsets, tail_amount=0): |
697 | - # This just switches based on the type of request |
698 | - if offsets is not None or tail_amount not in (0, None): |
699 | - return self._get_ranged(relpath, offsets, tail_amount=tail_amount) |
700 | - else: |
701 | - return self._get_full(relpath) |
702 | - |
703 | - def _setup_get_request(self, curl, relpath): |
704 | - # Make sure we do a GET request. versions > 7.14.1 also set the |
705 | - # NO BODY flag, but we'll do it ourselves in case it is an older |
706 | - # pycurl version |
707 | - curl.setopt(pycurl.NOBODY, 0) |
708 | - curl.setopt(pycurl.HTTPGET, 1) |
709 | - return self._setup_request(curl, relpath) |
710 | - |
711 | - def _setup_request(self, curl, relpath): |
712 | - """Do the common setup stuff for making a request |
713 | - |
714 | - :param curl: The curl object to place the request on |
715 | - :param relpath: The relative path that we want to get |
716 | - :return: (abspath, data, header) |
717 | - abspath: full url |
718 | - data: file that will be filled with the body |
719 | - header: file that will be filled with the headers |
720 | - """ |
721 | - abspath = self._remote_path(relpath) |
722 | - curl.setopt(pycurl.URL, abspath) |
723 | - self._set_curl_options(curl) |
724 | - |
725 | - data = BytesIO() |
726 | - header = BytesIO() |
727 | - curl.setopt(pycurl.WRITEFUNCTION, data.write) |
728 | - curl.setopt(pycurl.HEADERFUNCTION, header.write) |
729 | - |
730 | - return abspath, data, header |
731 | - |
732 | - def _get_full(self, relpath): |
733 | - """Make a request for the entire file""" |
734 | - curl = self._get_curl() |
735 | - abspath, data, header = self._setup_get_request(curl, relpath) |
736 | - self._curl_perform(curl, header) |
737 | - |
738 | - code = curl.getinfo(pycurl.HTTP_CODE) |
739 | - data.seek(0) |
740 | - |
741 | - if code == 404: |
742 | - raise errors.NoSuchFile(abspath) |
743 | - if code != 200: |
744 | - self._raise_curl_http_error( |
745 | - curl, 'expected 200 or 404 for full response.') |
746 | - |
747 | - return code, data |
748 | - |
749 | - # The parent class use 0 to minimize the requests, but since we can't |
750 | - # exploit the results as soon as they are received (pycurl limitation) we'd |
751 | - # better issue more requests and provide a more responsive UI incurring |
752 | - # more latency costs. |
753 | - # If you modify this, think about modifying the comment in http/__init__.py |
754 | - # too. |
755 | - _get_max_size = 4 * 1024 * 1024 |
756 | - |
757 | - def _get_ranged(self, relpath, offsets, tail_amount): |
758 | - """Make a request for just part of the file.""" |
759 | - curl = self._get_curl() |
760 | - abspath, data, header = self._setup_get_request(curl, relpath) |
761 | - |
762 | - range_header = self._attempted_range_header(offsets, tail_amount) |
763 | - if range_header is None: |
764 | - # Forget ranges, the server can't handle them |
765 | - return self._get_full(relpath) |
766 | - |
767 | - self._curl_perform(curl, header, ['Range: bytes=%s' % range_header]) |
768 | - data.seek(0) |
769 | - |
770 | - code = curl.getinfo(pycurl.HTTP_CODE) |
771 | - |
772 | - if code == 404: # not found |
773 | - raise errors.NoSuchFile(abspath) |
774 | - elif code in (400, 416): |
775 | - # We don't know which, but one of the ranges we specified was |
776 | - # wrong. |
777 | - raise errors.InvalidHttpRange(abspath, range_header, |
778 | - 'Server return code %d' |
779 | - % curl.getinfo(pycurl.HTTP_CODE)) |
780 | - msg = self._parse_headers(header) |
781 | - return code, response.handle_response(abspath, code, msg, data) |
782 | - |
783 | - def _parse_headers(self, status_and_headers): |
784 | - """Transform the headers provided by curl into an HTTPMessage""" |
785 | - status_and_headers.seek(0) |
786 | - # Ignore status line |
787 | - status_and_headers.readline() |
788 | - msg = httplib.HTTPMessage(status_and_headers) |
789 | - return msg |
790 | - |
791 | - def _post(self, body_bytes): |
792 | - curl = self._get_curl() |
793 | - abspath, data, header = self._setup_request(curl, '.bzr/smart') |
794 | - curl.setopt(pycurl.POST, 1) |
795 | - fake_file = BytesIO(body_bytes) |
796 | - curl.setopt(pycurl.POSTFIELDSIZE, len(body_bytes)) |
797 | - curl.setopt(pycurl.READFUNCTION, fake_file.read) |
798 | - # We override the Expect: header so that pycurl will send the POST |
799 | - # body immediately. |
800 | - try: |
801 | - self._curl_perform(curl, header, |
802 | - ['Expect: ', |
803 | - 'Content-Type: application/octet-stream']) |
804 | - except pycurl.error as e: |
805 | - if e[0] == CURLE_SEND_ERROR: |
806 | - # When talking to an HTTP/1.0 server, getting a 400+ error code |
807 | - # triggers a bug in some combinations of curl/kernel in rare |
808 | - # occurrences. Basically, the server closes the connection |
809 | - # after sending the error but the client (having received and |
810 | - # parsed the response) still try to send the request body (see |
811 | - # bug #225020 and its upstream associated bug). Since the |
812 | - # error code and the headers are known to be available, we just |
813 | - # swallow the exception, leaving the upper levels handle the |
814 | - # 400+ error. |
815 | - trace.mutter('got pycurl error in POST: %s, %s, %s, url: %s ', |
816 | - e[0], e[1], e, abspath) |
817 | - else: |
818 | - # Re-raise otherwise |
819 | - raise |
820 | - data.seek(0) |
821 | - code = curl.getinfo(pycurl.HTTP_CODE) |
822 | - msg = self._parse_headers(header) |
823 | - return code, response.handle_response(abspath, code, msg, data) |
824 | - |
825 | - |
826 | - def _raise_curl_http_error(self, curl, info=None, body=None): |
827 | - """Common curl->breezy error translation. |
828 | - |
829 | - Some methods may choose to override this for particular cases. |
830 | - |
831 | - The URL and code are automatically included as appropriate. |
832 | - |
833 | - :param info: Extra information to include in the message. |
834 | - |
835 | - :param body: File-like object from which the body of the page can be |
836 | - read. |
837 | - """ |
838 | - code = curl.getinfo(pycurl.HTTP_CODE) |
839 | - url = curl.getinfo(pycurl.EFFECTIVE_URL) |
840 | - if body is not None: |
841 | - response_body = body.read() |
842 | - plaintext_body = unhtml_roughly(response_body) |
843 | - else: |
844 | - response_body = None |
845 | - plaintext_body = '' |
846 | - if code == 403: |
847 | - raise errors.TransportError( |
848 | - 'Server refuses to fulfill the request (403 Forbidden)' |
849 | - ' for %s: %s' % (url, plaintext_body)) |
850 | - else: |
851 | - if info is None: |
852 | - msg = '' |
853 | - else: |
854 | - msg = ': ' + info |
855 | - raise errors.InvalidHttpResponse( |
856 | - url, 'Unable to handle http code %d%s: %s' |
857 | - % (code, msg, plaintext_body)) |
858 | - |
859 | - def _debug_cb(self, kind, text): |
860 | - if kind in (pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN): |
861 | - self._report_activity(len(text), 'read') |
862 | - if (kind == pycurl.INFOTYPE_HEADER_IN |
863 | - and 'http' in debug.debug_flags): |
864 | - trace.mutter('< %s' % (text.rstrip(),)) |
865 | - elif kind in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT): |
866 | - self._report_activity(len(text), 'write') |
867 | - if (kind == pycurl.INFOTYPE_HEADER_OUT |
868 | - and 'http' in debug.debug_flags): |
869 | - lines = [] |
870 | - for line in text.rstrip().splitlines(): |
871 | - # People are often told to paste -Dhttp output to help |
872 | - # debug. Don't compromise credentials. |
873 | - try: |
874 | - header, details = line.split(':', 1) |
875 | - except ValueError: |
876 | - header = None |
877 | - if header in ('Authorization', 'Proxy-Authorization'): |
878 | - line = '%s: <masked>' % (header,) |
879 | - lines.append(line) |
880 | - trace.mutter('> ' + '\n> '.join(lines)) |
881 | - elif kind == pycurl.INFOTYPE_TEXT and 'http' in debug.debug_flags: |
882 | - trace.mutter('* %s' % text.rstrip()) |
883 | - elif (kind in (pycurl.INFOTYPE_TEXT, pycurl.INFOTYPE_SSL_DATA_IN, |
884 | - pycurl.INFOTYPE_SSL_DATA_OUT) |
885 | - and 'http' in debug.debug_flags): |
886 | - trace.mutter('* %s' % text) |
887 | - |
888 | - def _set_curl_options(self, curl): |
889 | - """Set options for all requests""" |
890 | - ua_str = 'bzr/%s (pycurl: %s)' % (breezy.__version__, pycurl.version) |
891 | - curl.setopt(pycurl.USERAGENT, ua_str) |
892 | - curl.setopt(pycurl.VERBOSE, 1) |
893 | - curl.setopt(pycurl.DEBUGFUNCTION, self._debug_cb) |
894 | - if self.cabundle: |
895 | - curl.setopt(pycurl.CAINFO, self.cabundle) |
896 | - # Set accepted auth methods |
897 | - curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_ANY) |
898 | - curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_ANY) |
899 | - auth = self._get_credentials() |
900 | - user = auth.get('user', None) |
901 | - password = auth.get('password', None) |
902 | - userpass = None |
903 | - if user is not None: |
904 | - userpass = user + ':' |
905 | - if password is not None: # '' is a valid password |
906 | - userpass += password |
907 | - curl.setopt(pycurl.USERPWD, userpass) |
908 | - |
909 | - def _curl_perform(self, curl, header, more_headers=[]): |
910 | - """Perform curl operation and translate exceptions.""" |
911 | - try: |
912 | - # There's no way in http/1.0 to say "must |
913 | - # revalidate"; we don't want to force it to always |
914 | - # retrieve. so just turn off the default Pragma |
915 | - # provided by Curl. |
916 | - headers = ['Cache-control: max-age=0', |
917 | - 'Pragma: no-cache', |
918 | - 'Connection: Keep-Alive'] |
919 | - curl.setopt(pycurl.HTTPHEADER, headers + more_headers) |
920 | - curl.perform() |
921 | - except pycurl.error as e: |
922 | - url = curl.getinfo(pycurl.EFFECTIVE_URL) |
923 | - trace.mutter('got pycurl error: %s, %s, %s, url: %s ', |
924 | - e[0], e[1], e, url) |
925 | - if e[0] in (CURLE_COULDNT_RESOLVE_HOST, |
926 | - CURLE_COULDNT_RESOLVE_PROXY, |
927 | - CURLE_COULDNT_CONNECT, |
928 | - CURLE_FTP_WEIRD_SERVER_REPLY, |
929 | - CURLE_GOT_NOTHING, |
930 | - CURLE_SSL_CACERT, |
931 | - CURLE_SSL_CACERT_BADFILE, |
932 | - ): |
933 | - raise errors.ConnectionError( |
934 | - 'curl connection error (%s)\non %s' % (e[1], url)) |
935 | - elif e[0] == CURLE_RECV_ERROR: |
936 | - raise errors.ConnectionReset( |
937 | - 'curl connection error (%s)\non %s' % (e[1], url)) |
938 | - elif e[0] == CURLE_PARTIAL_FILE: |
939 | - # Pycurl itself has detected a short read. We do not have all |
940 | - # the information for the ShortReadvError, but that should be |
941 | - # enough |
942 | - raise errors.ShortReadvError(url, |
943 | - offset='unknown', length='unknown', |
944 | - actual='unknown', |
945 | - extra='Server aborted the request') |
946 | - raise |
947 | - code = curl.getinfo(pycurl.HTTP_CODE) |
948 | - if code in (301, 302, 303, 307): |
949 | - url = curl.getinfo(pycurl.EFFECTIVE_URL) |
950 | - msg = self._parse_headers(header) |
951 | - redirected_to = msg.getheader('location') |
952 | - raise errors.RedirectRequested(url, |
953 | - redirected_to, |
954 | - is_permanent=(code == 301)) |
955 | - |
956 | - |
957 | -def get_test_permutations(): |
958 | - """Return the permutations to be used in testing.""" |
959 | - from ...tests import features |
960 | - from ...tests import http_server |
961 | - permutations = [(PyCurlTransport, http_server.HttpServer_PyCurl),] |
962 | - if features.HTTPSServerFeature.available(): |
963 | - from ...tests import ( |
964 | - https_server, |
965 | - ssl_certs, |
966 | - ) |
967 | - |
968 | - class HTTPS_pycurl_transport(PyCurlTransport): |
969 | - |
970 | - def __init__(self, base, _from_transport=None): |
971 | - super(HTTPS_pycurl_transport, self).__init__(base, |
972 | - _from_transport) |
973 | - self.cabundle = str(ssl_certs.build_path('ca.crt')) |
974 | - |
975 | - permutations.append((HTTPS_pycurl_transport, |
976 | - https_server.HTTPSServer_PyCurl)) |
977 | - return permutations |
978 | |
979 | === modified file 'doc/en/release-notes/brz-3.0.txt' |
980 | --- doc/en/release-notes/brz-3.0.txt 2017-06-07 01:26:42 +0000 |
981 | +++ doc/en/release-notes/brz-3.0.txt 2017-06-08 20:56:03 +0000 |
982 | @@ -34,6 +34,10 @@ |
983 | (https://help.launchpad.net/VcsImports). |
984 | (Colin Watson, #254567, #483689) |
985 | |
986 | + * Support for HTTP support using "pycurl" and the associated |
987 | + URL schemes "http+pycurl://" and "https+pycurl://" has been dropped. |
988 | + (Jelmer Vernooij, #82086, #377389, #122258, #516222, #545776, #1696602) |
989 | + |
990 | New Features |
991 | ************ |
992 |
Looks good. If we want ntlm support again seems there are different python packages we could optional depend on instead.