Merge lp:~jelmer/brz/kill-pycurl into lp:brz

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
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
Martin Packman (gz) wrote :

Looks good. If we want ntlm support again seems there are different python packages we could optional depend on instead.

review: Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :

A commit message must be set
http://10.242.247.184:8080/job/brz-dev/95/

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

Subscribers

People subscribed via source and target branches