~bzr-pqm/bzr/bzr.dev

1540.3.1 by Martin Pool
First-cut implementation of pycurl. Substantially faster than using urllib.
1
# Copyright (C) 2006 Canonical Ltd
1540.3.18 by Martin Pool
Style review fixes (thanks robertc)
2
#
1540.3.1 by Martin Pool
First-cut implementation of pycurl. Substantially faster than using urllib.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1540.3.18 by Martin Pool
Style review fixes (thanks robertc)
7
#
1540.3.1 by Martin Pool
First-cut implementation of pycurl. Substantially faster than using urllib.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1540.3.18 by Martin Pool
Style review fixes (thanks robertc)
12
#
1540.3.1 by Martin Pool
First-cut implementation of pycurl. Substantially faster than using urllib.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""http/https transport using pycurl"""
18
19
# TODO: test reporting of http errors
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
20
#
1616.1.9 by Martin Pool
Set Cache-control: max-age=0 and Pragma: no-cache
21
# TODO: Transport option to control caching of particular requests; broadly we
22
# would want to offer "caching allowed" or "must revalidate", depending on
23
# whether we expect a particular file will be modified after it's committed.
24
# It's probably safer to just always revalidate.  mbp 20060321
25
2164.2.16 by Vincent Ladeuil
Add tests.
26
# TODO: Some refactoring could be done to avoid the strange idiom
27
# used to capture data and headers while setting up the request
28
# (and having to pass 'header' to _curl_perform to handle
29
# redirections) . This could be achieved by creating a
30
# specialized Curl object and returning code, headers and data
31
# from _curl_perform.  Not done because we may deprecate pycurl in the
32
# future -- vila 20070212
33
1612.1.1 by Martin Pool
Raise errors correctly on pycurl connection failure
34
import os
1786.1.42 by John Arbash Meinel
Update _extract_headers, make it less generic, and non recursive.
35
from cStringIO import StringIO
2298.5.1 by Alexander Belchenko
Bugfix #82086: Searching location of CA bundle for PyCurl in env variable (CURL_CA_BUNDLE), and on win32 along the PATH
36
import sys
1540.3.5 by Martin Pool
Raise exception if unicode is passed to transport; formatting fixes
37
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
38
from bzrlib import (
39
    errors,
40
    __version__ as bzrlib_version,
41
    )
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
42
import bzrlib
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
43
from bzrlib.errors import (NoSuchFile,
44
                           ConnectionError,
1540.3.7 by Martin Pool
Prepare to select a transport depending on what dependencies can be satisfied.
45
                           DependencyNotPresent)
1540.3.18 by Martin Pool
Style review fixes (thanks robertc)
46
from bzrlib.trace import mutter
1636.1.2 by Robert Collins
More review fixen to the relpath at '/' fixes.
47
from bzrlib.transport import register_urlparse_netloc_protocol
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
48
from bzrlib.transport.http import (
2298.5.1 by Alexander Belchenko
Bugfix #82086: Searching location of CA bundle for PyCurl in env variable (CURL_CA_BUNDLE), and on win32 along the PATH
49
    ca_bundle,
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
50
    _extract_headers,
51
    HttpTransportBase,
52
    response,
53
    )
1540.3.1 by Martin Pool
First-cut implementation of pycurl. Substantially faster than using urllib.
54
1540.3.7 by Martin Pool
Prepare to select a transport depending on what dependencies can be satisfied.
55
try:
56
    import pycurl
57
except ImportError, e:
58
    mutter("failed to import pycurl: %s", e)
59
    raise DependencyNotPresent('pycurl', e)
60
1684.1.5 by Martin Pool
(patch) check that pycurl will actuall initialize as well as load (Alexander)
61
try:
62
    # see if we can actually initialize PyCurl - sometimes it will load but
63
    # fail to start up due to this bug:
64
    #  
65
    #   32. (At least on Windows) If libcurl is built with c-ares and there's
66
    #   no DNS server configured in the system, the ares_init() call fails and
67
    #   thus curl_easy_init() fails as well. This causes weird effects for
68
    #   people who use numerical IP addresses only.
69
    #
70
    # reported by Alexander Belchenko, 2006-04-26
71
    pycurl.Curl()
72
except pycurl.error, e:
73
    mutter("failed to initialize pycurl: %s", e)
74
    raise DependencyNotPresent('pycurl', e)
75
1540.3.7 by Martin Pool
Prepare to select a transport depending on what dependencies can be satisfied.
76
2872.6.1 by Vincent Ladeuil
Fix bug #147530 by enabling more robust error code definitions.
77
78
79
def _get_pycurl_errcode(symbol, default):
80
    """
81
    Returns the numerical error code for a symbol defined by pycurl.
82
83
    Different pycurl implementations define different symbols for error
84
    codes. Old versions never define some symbols (wether they can return the
85
    corresponding error code or not). The following addresses the problem by
86
    defining the symbols we care about.  Note: this allows to define symbols
87
    for errors that older versions will never return, which is fine.
88
    """
89
    return pycurl.__dict__.get(symbol, default)
90
91
CURLE_SSL_CACERT_BADFILE = _get_pycurl_errcode('E_SSL_CACERT_BADFILE', 77)
92
CURLE_COULDNT_CONNECT = _get_pycurl_errcode('E_COULDNT_CONNECT', 7)
93
CURLE_COULDNT_RESOLVE_HOST = _get_pycurl_errcode('E_COULDNT_RESOLVE_HOST', 6)
94
CURLE_COULDNT_RESOLVE_PROXY = _get_pycurl_errcode('E_COULDNT_RESOLVE_PROXY', 5)
95
CURLE_GOT_NOTHING = _get_pycurl_errcode('E_GOT_NOTHING', 52)
96
CURLE_PARTIAL_FILE = _get_pycurl_errcode('E_PARTIAL_FILE', 18)
97
98
1636.1.2 by Robert Collins
More review fixen to the relpath at '/' fixes.
99
register_urlparse_netloc_protocol('http+pycurl')
1636.1.1 by Robert Collins
Fix calling relpath() and abspath() on transports at their root.
100
101
1540.3.1 by Martin Pool
First-cut implementation of pycurl. Substantially faster than using urllib.
102
class PyCurlTransport(HttpTransportBase):
1540.3.3 by Martin Pool
Review updates of pycurl transport
103
    """http client transport using pycurl
104
105
    PyCurl is a Python binding to the C "curl" multiprotocol client.
106
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
107
    This transport can be significantly faster than the builtin
108
    Python client.  Advantages include: DNS caching.
1540.3.3 by Martin Pool
Review updates of pycurl transport
109
    """
110
2485.8.59 by Vincent Ladeuil
Update from review comments.
111
    def __init__(self, base, _from_transport=None):
112
        super(PyCurlTransport, self).__init__(base,
113
                                              _from_transport=_from_transport)
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
114
        if base.startswith('https'):
115
            # Check availability of https into pycurl supported
116
            # protocols
117
            supported = pycurl.version_info()[8]
118
            if 'https' not in supported:
119
                raise DependencyNotPresent('pycurl', 'no https support')
2298.5.1 by Alexander Belchenko
Bugfix #82086: Searching location of CA bundle for PyCurl in env variable (CURL_CA_BUNDLE), and on win32 along the PATH
120
        self.cabundle = ca_bundle.get_ca_path()
2485.8.41 by Vincent Ladeuil
Finish http refactoring. Test suite passing.
121
122
    def _get_curl(self):
123
        connection = self._get_connection()
124
        if connection is None:
125
            # First connection ever. There is no credentials for pycurl, either
126
            # the password was embedded in the URL or it's not needed. The
127
            # connection for pycurl is just the Curl object, it will not
2485.8.43 by Vincent Ladeuil
Cleaning.
128
            # connect to the http server until the first request (which had
129
            # just called us).
2485.8.41 by Vincent Ladeuil
Finish http refactoring. Test suite passing.
130
            connection = pycurl.Curl()
131
            self._set_connection(connection, None)
132
        return connection
1540.3.1 by Martin Pool
First-cut implementation of pycurl. Substantially faster than using urllib.
133
1540.3.3 by Martin Pool
Review updates of pycurl transport
134
    def has(self, relpath):
1786.1.32 by John Arbash Meinel
cleanup pass, allow pycurl connections to be shared between transports.
135
        """See Transport.has()"""
136
        # We set NO BODY=0 in _get_full, so it should be safe
137
        # to re-use the non-range curl object
2485.8.41 by Vincent Ladeuil
Finish http refactoring. Test suite passing.
138
        curl = self._get_curl()
2485.8.25 by Vincent Ladeuil
Separate abspath from _remote_path, the intents are different.
139
        abspath = self._remote_path(relpath)
1540.3.14 by Martin Pool
[pycurl] Make Curl instance a local variable not a long-lived object.
140
        curl.setopt(pycurl.URL, abspath)
141
        self._set_curl_options(curl)
2018.2.28 by Andrew Bennetts
Changes in response to review: re-use _base_curl, rather than keeping a seperate _post_curl object; add docstring to test_http.RecordingServer, set is_user_error on some new exceptions.
142
        curl.setopt(pycurl.HTTPGET, 1)
1540.3.3 by Martin Pool
Review updates of pycurl transport
143
        # don't want the body - ie just do a HEAD request
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
144
        # This means "NO BODY" not 'nobody'
1540.3.14 by Martin Pool
[pycurl] Make Curl instance a local variable not a long-lived object.
145
        curl.setopt(pycurl.NOBODY, 1)
2164.2.16 by Vincent Ladeuil
Add tests.
146
        # But we need headers to handle redirections
147
        header = StringIO()
148
        curl.setopt(pycurl.HEADERFUNCTION, header.write)
2004.1.16 by v.ladeuil+lp at free
Add tests against erroneous http status lines.
149
        # In some erroneous cases, pycurl will emit text on
150
        # stdout if we don't catch it (see InvalidStatus tests
151
        # for one such occurrence).
152
        blackhole = StringIO()
153
        curl.setopt(pycurl.WRITEFUNCTION, blackhole.write)
2164.2.16 by Vincent Ladeuil
Add tests.
154
        self._curl_perform(curl, header)
1540.3.14 by Martin Pool
[pycurl] Make Curl instance a local variable not a long-lived object.
155
        code = curl.getinfo(pycurl.HTTP_CODE)
156
        if code == 404: # not found
157
            return False
2164.2.16 by Vincent Ladeuil
Add tests.
158
        elif code == 200: # "ok"
1540.3.14 by Martin Pool
[pycurl] Make Curl instance a local variable not a long-lived object.
159
            return True
160
        else:
1612.1.1 by Martin Pool
Raise errors correctly on pycurl connection failure
161
            self._raise_curl_http_error(curl)
2000.3.1 by v.ladeuil+lp at free
Better connection sharing by using only one curl object.
162
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
163
    def _get(self, relpath, offsets, tail_amount=0):
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
164
        # This just switches based on the type of request
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
165
        if offsets is not None or tail_amount not in (0, None):
166
            return self._get_ranged(relpath, offsets, tail_amount=tail_amount)
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
167
        else:
2164.2.5 by v.ladeuil+lp at free
Simpler implementation using inspect. 'hints' is a kwargs.
168
            return self._get_full(relpath)
2000.3.1 by v.ladeuil+lp at free
Better connection sharing by using only one curl object.
169
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
170
    def _setup_get_request(self, curl, relpath):
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
171
        # Make sure we do a GET request. versions > 7.14.1 also set the
172
        # NO BODY flag, but we'll do it ourselves in case it is an older
173
        # pycurl version
174
        curl.setopt(pycurl.NOBODY, 0)
175
        curl.setopt(pycurl.HTTPGET, 1)
176
        return self._setup_request(curl, relpath)
177
178
    def _setup_request(self, curl, relpath):
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
179
        """Do the common setup stuff for making a request
180
181
        :param curl: The curl object to place the request on
182
        :param relpath: The relative path that we want to get
183
        :return: (abspath, data, header) 
184
                 abspath: full url
185
                 data: file that will be filled with the body
186
                 header: file that will be filled with the headers
187
        """
2485.8.25 by Vincent Ladeuil
Separate abspath from _remote_path, the intents are different.
188
        abspath = self._remote_path(relpath)
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
189
        curl.setopt(pycurl.URL, abspath)
190
        self._set_curl_options(curl)
191
192
        data = StringIO()
193
        header = StringIO()
194
        curl.setopt(pycurl.WRITEFUNCTION, data.write)
195
        curl.setopt(pycurl.HEADERFUNCTION, header.write)
196
197
        return abspath, data, header
198
2164.2.5 by v.ladeuil+lp at free
Simpler implementation using inspect. 'hints' is a kwargs.
199
    def _get_full(self, relpath):
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
200
        """Make a request for the entire file"""
2485.8.41 by Vincent Ladeuil
Finish http refactoring. Test suite passing.
201
        curl = self._get_curl()
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
202
        abspath, data, header = self._setup_get_request(curl, relpath)
2164.2.16 by Vincent Ladeuil
Add tests.
203
        self._curl_perform(curl, header)
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
204
205
        code = curl.getinfo(pycurl.HTTP_CODE)
206
        data.seek(0)
207
208
        if code == 404:
209
            raise NoSuchFile(abspath)
210
        if code != 200:
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
211
            self._raise_curl_http_error(
212
                curl, 'expected 200 or 404 for full response.')
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
213
214
        return code, data
215
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
216
    def _get_ranged(self, relpath, offsets, tail_amount):
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
217
        """Make a request for just part of the file."""
2485.8.41 by Vincent Ladeuil
Finish http refactoring. Test suite passing.
218
        curl = self._get_curl()
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
219
        abspath, data, header = self._setup_get_request(curl, relpath)
220
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
221
        range_header = self._attempted_range_header(offsets, tail_amount)
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
222
        if range_header is None:
223
            # Forget ranges, the server can't handle them
224
            return self._get_full(relpath)
225
2481.3.1 by Vincent Ladeuil
Fix bug #112719 by using the right range header.
226
        self._curl_perform(curl, header, ['Range: bytes=%s' % range_header])
1786.1.33 by John Arbash Meinel
Cleanup pass #2
227
        data.seek(0)
228
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
229
        code = curl.getinfo(pycurl.HTTP_CODE)
1979.1.1 by John Arbash Meinel
Fix bug #57723, parse boundary="" correctly, since Squid uses it
230
        # mutter('header:\n%r', header.getvalue())
1786.1.42 by John Arbash Meinel
Update _extract_headers, make it less generic, and non recursive.
231
        headers = _extract_headers(header.getvalue(), abspath)
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
232
        # handle_response will raise NoSuchFile, etc based on the response code
233
        return code, response.handle_response(abspath, code, headers, data)
1786.1.4 by John Arbash Meinel
Adding HEADERFUNCTION which lets us get any response codes we want.
234
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
235
    def _post(self, body_bytes):
236
        fake_file = StringIO(body_bytes)
2485.8.41 by Vincent Ladeuil
Finish http refactoring. Test suite passing.
237
        curl = self._get_curl()
238
        # Other places that use the Curl object (returned by _get_curl)
239
        # for GET requests explicitly set HTTPGET, so it should be safe to
240
        # re-use the same object for both GETs and POSTs.
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
241
        curl.setopt(pycurl.POST, 1)
242
        curl.setopt(pycurl.POSTFIELDSIZE, len(body_bytes))
243
        curl.setopt(pycurl.READFUNCTION, fake_file.read)
244
        abspath, data, header = self._setup_request(curl, '.bzr/smart')
2000.3.4 by v.ladeuil+lp at free
Merge bzr.dev
245
        # We override the Expect: header so that pycurl will send the POST
246
        # body immediately.
2164.2.16 by Vincent Ladeuil
Add tests.
247
        self._curl_perform(curl, header, ['Expect: '])
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
248
        data.seek(0)
249
        code = curl.getinfo(pycurl.HTTP_CODE)
250
        headers = _extract_headers(header.getvalue(), abspath)
251
        return code, response.handle_response(abspath, code, headers, data)
252
1786.1.40 by John Arbash Meinel
code cleanups from Martin Pool.
253
    def _raise_curl_http_error(self, curl, info=None):
1612.1.1 by Martin Pool
Raise errors correctly on pycurl connection failure
254
        code = curl.getinfo(pycurl.HTTP_CODE)
255
        url = curl.getinfo(pycurl.EFFECTIVE_URL)
2004.1.27 by v.ladeuil+lp at free
Fix bug #57644 by issuing an explicit error message.
256
        # Some error codes can be handled the same way for all
257
        # requests
258
        if code == 403:
2004.1.34 by v.ladeuil+lp at free
Cosmetic fix for bug #57644.
259
            raise errors.TransportError(
260
                'Server refuses to fullfil the request for: %s' % url)
1786.1.40 by John Arbash Meinel
code cleanups from Martin Pool.
261
        else:
2004.1.27 by v.ladeuil+lp at free
Fix bug #57644 by issuing an explicit error message.
262
            if info is None:
263
                msg = ''
264
            else:
265
                msg = ': ' + info
266
            raise errors.InvalidHttpResponse(
267
                url, 'Unable to handle http code %d%s' % (code,msg))
1540.3.1 by Martin Pool
First-cut implementation of pycurl. Substantially faster than using urllib.
268
1540.3.13 by Martin Pool
Curl should follow http redirects, the same as urllib
269
    def _set_curl_options(self, curl):
270
        """Set options for all requests"""
1540.3.14 by Martin Pool
[pycurl] Make Curl instance a local variable not a long-lived object.
271
        ## curl.setopt(pycurl.VERBOSE, 1)
1616.1.9 by Martin Pool
Set Cache-control: max-age=0 and Pragma: no-cache
272
        # TODO: maybe include a summary of the pycurl version
1786.1.33 by John Arbash Meinel
Cleanup pass #2
273
        ua_str = 'bzr/%s (pycurl)' % (bzrlib.__version__,)
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
274
        curl.setopt(pycurl.USERAGENT, ua_str)
2298.5.1 by Alexander Belchenko
Bugfix #82086: Searching location of CA bundle for PyCurl in env variable (CURL_CA_BUNDLE), and on win32 along the PATH
275
        if self.cabundle:
276
            curl.setopt(pycurl.CAINFO, self.cabundle)
1540.3.3 by Martin Pool
Review updates of pycurl transport
277
2164.2.16 by Vincent Ladeuil
Add tests.
278
    def _curl_perform(self, curl, header, more_headers=[]):
1540.3.3 by Martin Pool
Review updates of pycurl transport
279
        """Perform curl operation and translate exceptions."""
280
        try:
2000.3.1 by v.ladeuil+lp at free
Better connection sharing by using only one curl object.
281
            # There's no way in http/1.0 to say "must
282
            # revalidate"; we don't want to force it to always
283
            # retrieve.  so just turn off the default Pragma
284
            # provided by Curl.
285
            headers = ['Cache-control: max-age=0',
286
                       'Pragma: no-cache',
287
                       'Connection: Keep-Alive']
288
            curl.setopt(pycurl.HTTPHEADER, headers + more_headers)
1540.3.14 by Martin Pool
[pycurl] Make Curl instance a local variable not a long-lived object.
289
            curl.perform()
1540.3.3 by Martin Pool
Review updates of pycurl transport
290
        except pycurl.error, e:
1786.1.35 by John Arbash Meinel
For pycurl inverse of (NOBODY,1) is (HTTPGET,1) not (NOBODY,0)
291
            url = curl.getinfo(pycurl.EFFECTIVE_URL)
292
            mutter('got pycurl error: %s, %s, %s, url: %s ',
2872.6.1 by Vincent Ladeuil
Fix bug #147530 by enabling more robust error code definitions.
293
                    e[0], e[1], e, url)
294
            if e[0] in (CURLE_SSL_CACERT_BADFILE,
295
                        CURLE_COULDNT_RESOLVE_HOST,
296
                        CURLE_COULDNT_CONNECT,
297
                        CURLE_GOT_NOTHING,
298
                        CURLE_COULDNT_RESOLVE_PROXY,):
2051.2.1 by Matthieu Moy
correct handling of proxy error
299
                raise ConnectionError('curl connection error (%s)\non %s'
300
                              % (e[1], url))
2872.6.1 by Vincent Ladeuil
Fix bug #147530 by enabling more robust error code definitions.
301
            elif e[0] == CURLE_PARTIAL_FILE:
2180.1.2 by Aaron Bentley
Grammar fixes
302
                # Pycurl itself has detected a short read.  We do
303
                # not have all the information for the
2000.3.9 by v.ladeuil+lp at free
The tests that would have help avoid bug #73948 and all that mess :)
304
                # ShortReadvError, but that should be enough
305
                raise errors.ShortReadvError(url,
306
                                             offset='unknown', length='unknown',
307
                                             actual='unknown',
308
                                             extra='Server aborted the request')
2180.1.2 by Aaron Bentley
Grammar fixes
309
            # jam 20060713 The code didn't use to re-raise the exception here,
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
310
            # but that seemed bogus
311
            raise
2164.2.16 by Vincent Ladeuil
Add tests.
312
        code = curl.getinfo(pycurl.HTTP_CODE)
313
        if code in (301, 302, 303, 307):
314
            url = curl.getinfo(pycurl.EFFECTIVE_URL)
315
            headers = _extract_headers(header.getvalue(), url)
316
            redirected_to = headers['Location']
317
            raise errors.RedirectRequested(url,
318
                                           redirected_to,
319
                                           is_permament=(code == 301),
2485.8.24 by Vincent Ladeuil
Finish http refactoring. Test suite passing.
320
                                           qual_proto=self._scheme)
1540.3.1 by Martin Pool
First-cut implementation of pycurl. Substantially faster than using urllib.
321
1540.3.10 by Martin Pool
[broken] keep hooking pycurl into test framework
322
323
def get_test_permutations():
324
    """Return the permutations to be used in testing."""
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
325
    from bzrlib.tests.HttpServer import HttpServer_PyCurl
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
326
    return [(PyCurlTransport, HttpServer_PyCurl),
1540.3.10 by Martin Pool
[broken] keep hooking pycurl into test framework
327
            ]