/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3995.2.2 by Martin Pool
Cope with read_bundle_from_url deprecation in test_http
1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
2
#
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
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.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
7
#
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
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.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
12
#
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1185.16.68 by Martin Pool
- http url fixes suggested by Robey Pointer, and tests
16
3111.1.30 by Vincent Ladeuil
Update NEWS. Some cosmetic changes.
17
"""Tests for HTTP implementations.
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
18
3111.1.30 by Vincent Ladeuil
Update NEWS. Some cosmetic changes.
19
This module defines a load_tests() method that parametrize tests classes for
20
transport implementation, http protocol versions and authentication schemes.
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
21
"""
1540.3.3 by Martin Pool
Review updates of pycurl transport
22
1540.3.22 by Martin Pool
[patch] Add TestCase.assertIsInstance
23
# TODO: Should be renamed to bzrlib.transport.http.tests?
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
24
# TODO: What about renaming to bzrlib.tests.transport.http ?
1540.3.22 by Martin Pool
[patch] Add TestCase.assertIsInstance
25
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
26
from cStringIO import StringIO
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
27
import httplib
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
28
import os
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
29
import select
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
30
import SimpleHTTPServer
2000.2.2 by John Arbash Meinel
Update the urllib.has test.
31
import socket
2420.1.20 by Vincent Ladeuil
Fix test failure on pqm.
32
import sys
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
33
import threading
2000.2.2 by John Arbash Meinel
Update the urllib.has test.
34
1553.1.2 by James Henstridge
Add a test to make sure the user-agent header is being sent correctly.
35
import bzrlib
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
36
from bzrlib import (
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
37
    bzrdir,
2900.2.6 by Vincent Ladeuil
Make http aware of authentication config.
38
    config,
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
39
    errors,
40
    osutils,
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
41
    remote as _mod_remote,
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
42
    tests,
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
43
    transport,
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
44
    ui,
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
45
    urlutils,
46
    )
3995.2.2 by Martin Pool
Cope with read_bundle_from_url deprecation in test_http
47
from bzrlib.symbol_versioning import (
48
    deprecated_in,
49
    )
3102.1.1 by Vincent Ladeuil
Rename bzrlib/test/HTTPTestUtils.py to bzrlib/tests/http_utils.py and fix
50
from bzrlib.tests import (
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
51
    http_server,
3111.1.7 by Vincent Ladeuil
Further refactoring.
52
    http_utils,
3102.1.1 by Vincent Ladeuil
Rename bzrlib/test/HTTPTestUtils.py to bzrlib/tests/http_utils.py and fix
53
    )
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
54
from bzrlib.transport import (
55
    http,
56
    remote,
57
    )
2004.3.3 by vila
Better (but still incomplete) design for bogus servers.
58
from bzrlib.transport.http import (
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
59
    _urllib,
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
60
    _urllib2_wrappers,
2004.3.3 by vila
Better (but still incomplete) design for bogus servers.
61
    )
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
62
63
64
try:
65
    from bzrlib.transport.http._pycurl import PyCurlTransport
66
    pycurl_present = True
67
except errors.DependencyNotPresent:
68
    pycurl_present = False
69
70
71
def load_tests(standard_tests, module, loader):
72
    """Multiply tests for http clients and protocol versions."""
3945.1.7 by Vincent Ladeuil
Test against https.
73
    result = loader.suiteClass()
74
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
75
    # one for each transport implementation
3945.1.7 by Vincent Ladeuil
Test against https.
76
    t_tests, remaining_tests = tests.split_suite_by_condition(
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
77
        standard_tests, tests.condition_isinstance((
3945.1.7 by Vincent Ladeuil
Test against https.
78
                TestHttpTransportRegistration,
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
79
                TestHttpTransportUrls,
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
80
                Test_redirected_to,
3945.1.7 by Vincent Ladeuil
Test against https.
81
                )))
82
    transport_scenarios = [
83
        ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
84
                        _server=http_server.HttpServer_urllib,
85
                        _qualified_prefix='http+urllib',)),
86
        ]
87
    if pycurl_present:
88
        transport_scenarios.append(
89
            ('pycurl', dict(_transport=PyCurlTransport,
90
                            _server=http_server.HttpServer_PyCurl,
91
                            _qualified_prefix='http+pycurl',)))
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
92
    tests.multiply_tests(t_tests, transport_scenarios, result)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
93
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
94
    # each implementation tested with each HTTP version
3945.1.7 by Vincent Ladeuil
Test against https.
95
    tp_tests, remaining_tests = tests.split_suite_by_condition(
96
        remaining_tests, tests.condition_isinstance((
97
                SmartHTTPTunnellingTest,
98
                TestDoCatchRedirections,
99
                TestHTTPConnections,
100
                TestHTTPRedirections,
101
                TestHTTPSilentRedirections,
102
                TestLimitedRangeRequestServer,
103
                TestPost,
104
                TestProxyHttpServer,
105
                TestRanges,
106
                TestSpecificRequestHandler,
107
                )))
108
    protocol_scenarios = [
109
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
110
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
111
            ]
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
112
    tp_scenarios = tests.multiply_scenarios(transport_scenarios,
3945.1.7 by Vincent Ladeuil
Test against https.
113
                                            protocol_scenarios)
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
114
    tests.multiply_tests(tp_tests, tp_scenarios, result)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
115
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
116
    # proxy auth: each auth scheme on all http versions on all implementations.
117
    tppa_tests, remaining_tests = tests.split_suite_by_condition(
118
        remaining_tests, tests.condition_isinstance((
119
                TestProxyAuth,
120
                )))
121
    proxy_auth_scheme_scenarios = [
122
        ('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
123
        ('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
124
        ('basicdigest',
125
         dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
126
        ]
127
    tppa_scenarios = tests.multiply_scenarios(tp_scenarios,
128
                                              proxy_auth_scheme_scenarios)
129
    tests.multiply_tests(tppa_tests, tppa_scenarios, result)
130
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
131
    # auth: each auth scheme on all http versions on all implementations.
3945.1.7 by Vincent Ladeuil
Test against https.
132
    tpa_tests, remaining_tests = tests.split_suite_by_condition(
133
        remaining_tests, tests.condition_isinstance((
134
                TestAuth,
135
                )))
136
    auth_scheme_scenarios = [
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
137
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
138
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
139
        ('basicdigest',
140
         dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
3945.1.7 by Vincent Ladeuil
Test against https.
141
        ]
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
142
    tpa_scenarios = tests.multiply_scenarios(tp_scenarios,
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
143
                                             auth_scheme_scenarios)
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
144
    tests.multiply_tests(tpa_tests, tpa_scenarios, result)
3945.1.7 by Vincent Ladeuil
Test against https.
145
4382.1.1 by Vincent Ladeuil
Fix test failures for https/pycurl.
146
    # activity: on all http[s] versions on all implementations
3945.1.7 by Vincent Ladeuil
Test against https.
147
    tpact_tests, remaining_tests = tests.split_suite_by_condition(
148
        remaining_tests, tests.condition_isinstance((
149
                TestActivity,
150
                )))
151
    activity_scenarios = [
4382.1.3 by Vincent Ladeuil
Take more configurations into account.
152
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
153
                             _transport=_urllib.HttpTransport_urllib,)),
4382.1.1 by Vincent Ladeuil
Fix test failures for https/pycurl.
154
        ]
3945.1.7 by Vincent Ladeuil
Test against https.
155
    if tests.HTTPSServerFeature.available():
156
        activity_scenarios.append(
4382.1.3 by Vincent Ladeuil
Take more configurations into account.
157
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
158
                                  _transport=_urllib.HttpTransport_urllib,)),)
159
    if pycurl_present:
160
        activity_scenarios.append(
161
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
162
                                 _transport=PyCurlTransport,)),)
163
        if tests.HTTPSServerFeature.available():
4382.1.1 by Vincent Ladeuil
Fix test failures for https/pycurl.
164
            from bzrlib.tests import (
165
                ssl_certs,
166
                )
167
            # FIXME: Until we have a better way to handle self-signed
168
            # certificates (like allowing them in a test specific
169
            # authentication.conf for example), we need some specialized pycurl
170
            # transport for tests.
171
            class HTTPS_pycurl_transport(PyCurlTransport):
172
173
                def __init__(self, base, _from_transport=None):
174
                    super(HTTPS_pycurl_transport, self).__init__(
175
                        base, _from_transport)
176
                    self.cabundle = str(ssl_certs.build_path('ca.crt'))
177
4382.1.3 by Vincent Ladeuil
Take more configurations into account.
178
            activity_scenarios.append(
179
                ('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
180
                                      _transport=HTTPS_pycurl_transport,)),)
4382.1.1 by Vincent Ladeuil
Fix test failures for https/pycurl.
181
4382.1.3 by Vincent Ladeuil
Take more configurations into account.
182
    tpact_scenarios = tests.multiply_scenarios(activity_scenarios,
183
                                               protocol_scenarios)
4084.5.1 by Robert Collins
Bulk update all test adaptation into a single approach, using multiply_tests rather than test adapters.
184
    tests.multiply_tests(tpact_tests, tpact_scenarios, result)
3945.1.7 by Vincent Ladeuil
Test against https.
185
186
    # No parametrization for the remaining tests
187
    result.addTests(remaining_tests)
188
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
189
    return result
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
190
1786.1.8 by John Arbash Meinel
[merge] Johan Rydberg test updates
191
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
192
class FakeManager(object):
1786.1.8 by John Arbash Meinel
[merge] Johan Rydberg test updates
193
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
194
    def __init__(self):
195
        self.credentials = []
2004.3.1 by vila
Test ConnectionError exceptions.
196
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
197
    def add_password(self, realm, host, username, password):
198
        self.credentials.append([realm, host, username, password])
199
1553.1.2 by James Henstridge
Add a test to make sure the user-agent header is being sent correctly.
200
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
201
class RecordingServer(object):
202
    """A fake HTTP server.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
203
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
204
    It records the bytes sent to it, and replies with a 200.
205
    """
206
4691.2.1 by Robert Collins
Add stronger test isolation by interception BzrDir.open and checking the thing being opened is known to the test suite.
207
    def __init__(self, expect_body_tail=None, scheme=''):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
208
        """Constructor.
209
210
        :type expect_body_tail: str
211
        :param expect_body_tail: a reply won't be sent until this string is
212
            received.
213
        """
214
        self._expect_body_tail = expect_body_tail
215
        self.host = None
216
        self.port = None
217
        self.received_bytes = ''
4691.2.1 by Robert Collins
Add stronger test isolation by interception BzrDir.open and checking the thing being opened is known to the test suite.
218
        self.scheme = scheme
219
220
    def get_url(self):
221
        return '%s://%s:%s/' % (self.scheme, self.host, self.port)
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
222
223
    def setUp(self):
224
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
225
        self._sock.bind(('127.0.0.1', 0))
226
        self.host, self.port = self._sock.getsockname()
227
        self._ready = threading.Event()
228
        self._thread = threading.Thread(target=self._accept_read_and_reply)
229
        self._thread.setDaemon(True)
230
        self._thread.start()
4731.2.4 by Vincent Ladeuil
No more leaks in http tests.
231
        self._ready.wait()
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
232
233
    def _accept_read_and_reply(self):
234
        self._sock.listen(1)
4731.2.4 by Vincent Ladeuil
No more leaks in http tests.
235
        self._sock.settimeout(5)
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
236
        self._ready.set()
237
        try:
238
            conn, address = self._sock.accept()
239
            # On win32, the accepted connection will be non-blocking to start
240
            # with because we're using settimeout.
241
            conn.setblocking(True)
4731.2.4 by Vincent Ladeuil
No more leaks in http tests.
242
            if self._expect_body_tail is not None:
243
                while not self.received_bytes.endswith(self._expect_body_tail):
244
                    self.received_bytes += conn.recv(4096)
245
                conn.sendall('HTTP/1.1 200 OK\r\n')
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
246
        except socket.timeout:
247
            # Make sure the client isn't stuck waiting for us to e.g. accept.
4731.2.4 by Vincent Ladeuil
No more leaks in http tests.
248
            pass
249
        try:
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
250
            self._sock.close()
251
        except socket.error:
252
            # The client may have already closed the socket.
253
            pass
254
4731.2.6 by Vincent Ladeuil
Fix python-2.4/2.5 compatibility.
255
    def connect_socket(self):
256
        msg = "getaddrinfo returns an empty list"
257
        for res in socket.getaddrinfo(self.host, self.port):
258
            af, socktype, proto, canonname, sa = res
259
            sock = None
260
            try:
261
                sock = socket.socket(af, socktype, proto)
262
                sock.connect(sa)
263
                return sock
264
265
            except socket.error, msg:
266
                if sock is not None:
267
                    sock.close()
268
        raise socket.error, msg
269
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
270
    def tearDown(self):
271
        try:
4731.2.4 by Vincent Ladeuil
No more leaks in http tests.
272
            # Issue a fake connection to wake up the server and allow it to
273
            # finish quickly
4731.2.6 by Vincent Ladeuil
Fix python-2.4/2.5 compatibility.
274
            fake_conn = self.connect_socket()
4731.2.4 by Vincent Ladeuil
No more leaks in http tests.
275
            fake_conn.close()
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
276
        except socket.error:
277
            # We might have already closed it.  We don't care.
278
            pass
279
        self.host = None
280
        self.port = None
4731.2.4 by Vincent Ladeuil
No more leaks in http tests.
281
        self._thread.join()
282
        del self._thread
283
        self.thread = None
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
284
285
4050.2.2 by Vincent Ladeuil
Ensures all auth handlers correctly parse all auth headers.
286
class TestAuthHeader(tests.TestCase):
287
4284.1.1 by Vincent Ladeuil
Fix wrong realm extraction in http basic authentication (reported
288
    def parse_header(self, header, auth_handler_class=None):
289
        if auth_handler_class is None:
290
            auth_handler_class = _urllib2_wrappers.AbstractAuthHandler
291
        self.auth_handler =  auth_handler_class()
292
        return self.auth_handler._parse_auth_header(header)
4050.2.2 by Vincent Ladeuil
Ensures all auth handlers correctly parse all auth headers.
293
294
    def test_empty_header(self):
295
        scheme, remainder = self.parse_header('')
296
        self.assertEquals('', scheme)
297
        self.assertIs(None, remainder)
298
299
    def test_negotiate_header(self):
300
        scheme, remainder = self.parse_header('Negotiate')
301
        self.assertEquals('negotiate', scheme)
302
        self.assertIs(None, remainder)
303
304
    def test_basic_header(self):
305
        scheme, remainder = self.parse_header(
306
            'Basic realm="Thou should not pass"')
307
        self.assertEquals('basic', scheme)
308
        self.assertEquals('realm="Thou should not pass"', remainder)
309
4284.1.1 by Vincent Ladeuil
Fix wrong realm extraction in http basic authentication (reported
310
    def test_basic_extract_realm(self):
311
        scheme, remainder = self.parse_header(
312
            'Basic realm="Thou should not pass"',
313
            _urllib2_wrappers.BasicAuthHandler)
314
        match, realm = self.auth_handler.extract_realm(remainder)
315
        self.assertTrue(match is not None)
316
        self.assertEquals('Thou should not pass', realm)
317
4050.2.2 by Vincent Ladeuil
Ensures all auth handlers correctly parse all auth headers.
318
    def test_digest_header(self):
319
        scheme, remainder = self.parse_header(
320
            'Digest realm="Thou should not pass"')
321
        self.assertEquals('digest', scheme)
322
        self.assertEquals('realm="Thou should not pass"', remainder)
323
324
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
325
class TestHTTPServer(tests.TestCase):
326
    """Test the HTTP servers implementations."""
327
328
    def test_invalid_protocol(self):
329
        class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
330
331
            protocol_version = 'HTTP/0.1'
332
333
        server = http_server.HttpServer(BogusRequestHandler)
334
        try:
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
335
            self.assertRaises(httplib.UnknownProtocol, server.setUp)
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
336
        except:
337
            server.tearDown()
338
            self.fail('HTTP Server creation did not raise UnknownProtocol')
339
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
340
    def test_force_invalid_protocol(self):
341
        server = http_server.HttpServer(protocol_version='HTTP/0.1')
342
        try:
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
343
            self.assertRaises(httplib.UnknownProtocol, server.setUp)
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
344
        except:
345
            server.tearDown()
346
            self.fail('HTTP Server creation did not raise UnknownProtocol')
347
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
348
    def test_server_start_and_stop(self):
349
        server = http_server.HttpServer()
350
        server.setUp()
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
351
        try:
4731.2.2 by Vincent Ladeuil
Cleanup and refactor the server shutdown.
352
            self.assertTrue(server._httpd is not None)
353
            self.assertTrue(server._httpd.serving)
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
354
        finally:
355
            server.tearDown()
4731.2.2 by Vincent Ladeuil
Cleanup and refactor the server shutdown.
356
        self.assertFalse(server._httpd.serving)
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
357
358
    def test_create_http_server_one_zero(self):
359
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
360
361
            protocol_version = 'HTTP/1.0'
362
363
        server = http_server.HttpServer(RequestHandlerOneZero)
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
364
        self.start_server(server)
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
365
        self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
366
367
    def test_create_http_server_one_one(self):
368
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
369
370
            protocol_version = 'HTTP/1.1'
371
372
        server = http_server.HttpServer(RequestHandlerOneOne)
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
373
        self.start_server(server)
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
374
        self.assertIsInstance(server._httpd,
375
                              http_server.TestingThreadingHTTPServer)
376
377
    def test_create_http_server_force_one_one(self):
378
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
379
380
            protocol_version = 'HTTP/1.0'
381
382
        server = http_server.HttpServer(RequestHandlerOneZero,
383
                                        protocol_version='HTTP/1.1')
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
384
        self.start_server(server)
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
385
        self.assertIsInstance(server._httpd,
386
                              http_server.TestingThreadingHTTPServer)
387
388
    def test_create_http_server_force_one_zero(self):
389
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
390
391
            protocol_version = 'HTTP/1.1'
392
393
        server = http_server.HttpServer(RequestHandlerOneOne,
394
                                        protocol_version='HTTP/1.0')
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
395
        self.start_server(server)
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
396
        self.assertIsInstance(server._httpd,
397
                              http_server.TestingHTTPServer)
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
398
399
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
400
class TestWithTransport_pycurl(object):
401
    """Test case to inherit from if pycurl is present"""
402
403
    def _get_pycurl_maybe(self):
404
        try:
405
            from bzrlib.transport.http._pycurl import PyCurlTransport
406
            return PyCurlTransport
407
        except errors.DependencyNotPresent:
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
408
            raise tests.TestSkipped('pycurl not present')
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
409
410
    _transport = property(_get_pycurl_maybe)
411
412
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
413
class TestHttpUrls(tests.TestCase):
1786.1.8 by John Arbash Meinel
[merge] Johan Rydberg test updates
414
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
415
    # TODO: This should be moved to authorization tests once they
416
    # are written.
2004.1.40 by v.ladeuil+lp at free
Fix the race condition again and correct some small typos to be in
417
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
418
    def test_url_parsing(self):
419
        f = FakeManager()
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
420
        url = http.extract_auth('http://example.com', f)
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
421
        self.assertEquals('http://example.com', url)
422
        self.assertEquals(0, len(f.credentials))
3111.1.30 by Vincent Ladeuil
Update NEWS. Some cosmetic changes.
423
        url = http.extract_auth(
424
            'http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
1185.50.94 by John Arbash Meinel
Updated web page url to http://bazaar-vcs.org
425
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
426
        self.assertEquals(1, len(f.credentials))
2004.3.1 by vila
Test ConnectionError exceptions.
427
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
428
                          f.credentials[0])
429
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
430
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
431
class TestHttpTransportUrls(tests.TestCase):
432
    """Test the http urls."""
433
434
    def test_abs_url(self):
435
        """Construction of absolute http URLs"""
436
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
437
        eq = self.assertEqualDiff
438
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
439
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
440
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
441
        eq(t.abspath('.bzr/1//2/./3'),
442
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
443
444
    def test_invalid_http_urls(self):
445
        """Trap invalid construction of urls"""
446
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
447
        self.assertRaises(errors.InvalidURL,
448
                          self._transport,
449
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
450
451
    def test_http_root_urls(self):
452
        """Construction of URLs from server root"""
453
        t = self._transport('http://bzr.ozlabs.org/')
454
        eq = self.assertEqualDiff
455
        eq(t.abspath('.bzr/tree-version'),
456
           'http://bzr.ozlabs.org/.bzr/tree-version')
457
458
    def test_http_impl_urls(self):
459
        """There are servers which ask for particular clients to connect"""
460
        server = self._server()
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
461
        server.setUp()
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
462
        try:
463
            url = server.get_url()
464
            self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
465
        finally:
466
            server.tearDown()
467
468
3111.1.9 by Vincent Ladeuil
Most refactoring regarding parameterization for urllib/pycurl and custom
469
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
470
471
    # TODO: This should really be moved into another pycurl
472
    # specific test. When https tests will be implemented, take
473
    # this one into account.
474
    def test_pycurl_without_https_support(self):
475
        """Test that pycurl without SSL do not fail with a traceback.
476
477
        For the purpose of the test, we force pycurl to ignore
478
        https by supplying a fake version_info that do not
479
        support it.
480
        """
481
        try:
482
            import pycurl
483
        except ImportError:
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
484
            raise tests.TestSkipped('pycurl not present')
3111.1.14 by Vincent Ladeuil
Fix test leakage.
485
486
        version_info_orig = pycurl.version_info
487
        try:
488
            # Now that we have pycurl imported, we can fake its version_info
489
            # This was taken from a windows pycurl without SSL
490
            # (thanks to bialix)
491
            pycurl.version_info = lambda : (2,
492
                                            '7.13.2',
493
                                            462082,
494
                                            'i386-pc-win32',
495
                                            2576,
496
                                            None,
497
                                            0,
498
                                            None,
499
                                            ('ftp', 'gopher', 'telnet',
500
                                             'dict', 'ldap', 'http', 'file'),
501
                                            None,
502
                                            0,
503
                                            None)
504
            self.assertRaises(errors.DependencyNotPresent, self._transport,
505
                              'https://launchpad.net')
506
        finally:
507
            # Restore the right function
508
            pycurl.version_info = version_info_orig
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
509
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
510
511
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
512
    """Test the http connections."""
513
514
    def setUp(self):
515
        http_utils.TestCaseWithWebserver.setUp(self)
516
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
517
                        transport=self.get_transport())
518
519
    def test_http_has(self):
520
        server = self.get_readonly_server()
521
        t = self._transport(server.get_url())
522
        self.assertEqual(t.has('foo/bar'), True)
523
        self.assertEqual(len(server.logs), 1)
524
        self.assertContainsRe(server.logs[0],
525
            r'"HEAD /foo/bar HTTP/1.." (200|302) - "-" "bzr/')
526
527
    def test_http_has_not_found(self):
528
        server = self.get_readonly_server()
529
        t = self._transport(server.get_url())
530
        self.assertEqual(t.has('not-found'), False)
531
        self.assertContainsRe(server.logs[1],
532
            r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
533
534
    def test_http_get(self):
535
        server = self.get_readonly_server()
536
        t = self._transport(server.get_url())
537
        fp = t.get('foo/bar')
538
        self.assertEqualDiff(
539
            fp.read(),
540
            'contents of foo/bar\n')
541
        self.assertEqual(len(server.logs), 1)
542
        self.assertTrue(server.logs[0].find(
543
            '"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s'
544
            % bzrlib.__version__) > -1)
545
546
    def test_has_on_bogus_host(self):
547
        # Get a free address and don't 'accept' on it, so that we
548
        # can be sure there is no http handler there, but set a
549
        # reasonable timeout to not slow down tests too much.
550
        default_timeout = socket.getdefaulttimeout()
551
        try:
552
            socket.setdefaulttimeout(2)
553
            s = socket.socket()
554
            s.bind(('localhost', 0))
555
            t = self._transport('http://%s:%s/' % s.getsockname())
556
            self.assertRaises(errors.ConnectionError, t.has, 'foo/bar')
557
        finally:
558
            socket.setdefaulttimeout(default_timeout)
559
560
561
class TestHttpTransportRegistration(tests.TestCase):
562
    """Test registrations of various http implementations"""
563
564
    def test_http_registered(self):
565
        t = transport.get_transport('%s://foo.com/' % self._qualified_prefix)
566
        self.assertIsInstance(t, transport.Transport)
567
        self.assertIsInstance(t, self._transport)
568
569
570
class TestPost(tests.TestCase):
571
572
    def test_post_body_is_received(self):
4691.2.1 by Robert Collins
Add stronger test isolation by interception BzrDir.open and checking the thing being opened is known to the test suite.
573
        server = RecordingServer(expect_body_tail='end-of-body',
574
            scheme=self._qualified_prefix)
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
575
        self.start_server(server)
4691.2.1 by Robert Collins
Add stronger test isolation by interception BzrDir.open and checking the thing being opened is known to the test suite.
576
        url = server.get_url()
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
577
        http_transport = self._transport(url)
578
        code, response = http_transport._post('abc def end-of-body')
579
        self.assertTrue(
580
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
581
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
582
        # The transport should not be assuming that the server can accept
583
        # chunked encoding the first time it connects, because HTTP/1.1, so we
584
        # check for the literal string.
585
        self.assertTrue(
586
            server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
587
588
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
589
class TestRangeHeader(tests.TestCase):
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
590
    """Test range_header method"""
591
592
    def check_header(self, value, ranges=[], tail=0):
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
593
        offsets = [ (start, end - start + 1) for start, end in ranges]
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
594
        coalesce = transport.Transport._coalesce_offsets
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
595
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
596
        range_header = http.HttpTransportBase._range_header
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
597
        self.assertEqual(value, range_header(coalesced, tail))
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
598
599
    def test_range_header_single(self):
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
600
        self.check_header('0-9', ranges=[(0,9)])
601
        self.check_header('100-109', ranges=[(100,109)])
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
602
603
    def test_range_header_tail(self):
1786.1.36 by John Arbash Meinel
pycurl expects us to just set the range of bytes, not including bytes=
604
        self.check_header('-10', tail=10)
605
        self.check_header('-50', tail=50)
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
606
607
    def test_range_header_multi(self):
1786.1.36 by John Arbash Meinel
pycurl expects us to just set the range of bytes, not including bytes=
608
        self.check_header('0-9,100-200,300-5000',
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
609
                          ranges=[(0,9), (100, 200), (300,5000)])
610
611
    def test_range_header_mixed(self):
1786.1.36 by John Arbash Meinel
pycurl expects us to just set the range of bytes, not including bytes=
612
        self.check_header('0-9,300-5000,-50',
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
613
                          ranges=[(0,9), (300,5000)],
614
                          tail=50)
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
615
2004.1.15 by v.ladeuil+lp at free
Better design for bogus servers. Both urllib and pycurl pass tests.
616
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
617
class TestSpecificRequestHandler(http_utils.TestCaseWithWebserver):
618
    """Tests a specific request handler.
619
3111.1.31 by Vincent Ladeuil
Review feeback.
620
    Daughter classes are expected to override _req_handler_class
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
621
    """
622
623
    # Provide a useful default
624
    _req_handler_class = http_server.TestingHTTPRequestHandler
625
626
    def create_transport_readonly_server(self):
627
        return http_server.HttpServer(self._req_handler_class,
628
                                      protocol_version=self._protocol_version)
629
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
630
    def _testing_pycurl(self):
631
        return pycurl_present and self._transport == PyCurlTransport
632
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
633
634
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
635
    """Whatever request comes in, close the connection"""
636
4731.2.3 by Vincent Ladeuil
Reduce the leaking http tests from ~200 to ~5.
637
    def _handle_one_request(self):
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
638
        """Handle a single HTTP request, by abruptly closing the connection"""
639
        self.close_connection = 1
640
641
642
class TestWallServer(TestSpecificRequestHandler):
643
    """Tests exceptions during the connection phase"""
644
645
    _req_handler_class = WallRequestHandler
646
647
    def test_http_has(self):
648
        server = self.get_readonly_server()
649
        t = self._transport(server.get_url())
650
        # Unfortunately httplib (see HTTPResponse._read_status
651
        # for details) make no distinction between a closed
652
        # socket and badly formatted status line, so we can't
653
        # just test for ConnectionError, we have to test
4628.1.2 by Vincent Ladeuil
More complete fix.
654
        # InvalidHttpResponse too. And pycurl may raise ConnectionReset
655
        # instead of ConnectionError too.
656
        self.assertRaises(( errors.ConnectionError, errors.ConnectionReset,
657
                            errors.InvalidHttpResponse),
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
658
                          t.has, 'foo/bar')
659
660
    def test_http_get(self):
661
        server = self.get_readonly_server()
662
        t = self._transport(server.get_url())
4628.1.2 by Vincent Ladeuil
More complete fix.
663
        self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
664
                           errors.InvalidHttpResponse),
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
665
                          t.get, 'foo/bar')
666
667
668
class BadStatusRequestHandler(http_server.TestingHTTPRequestHandler):
669
    """Whatever request comes in, returns a bad status"""
670
671
    def parse_request(self):
672
        """Fakes handling a single HTTP request, returns a bad status"""
673
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
674
        self.send_response(0, "Bad status")
675
        self.close_connection = 1
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
676
        return False
677
678
679
class TestBadStatusServer(TestSpecificRequestHandler):
680
    """Tests bad status from server."""
681
682
    _req_handler_class = BadStatusRequestHandler
683
684
    def test_http_has(self):
685
        server = self.get_readonly_server()
686
        t = self._transport(server.get_url())
687
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
688
689
    def test_http_get(self):
690
        server = self.get_readonly_server()
691
        t = self._transport(server.get_url())
692
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
693
694
695
class InvalidStatusRequestHandler(http_server.TestingHTTPRequestHandler):
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
696
    """Whatever request comes in, returns an invalid status"""
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
697
698
    def parse_request(self):
699
        """Fakes handling a single HTTP request, returns a bad status"""
700
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
701
        self.wfile.write("Invalid status line\r\n")
702
        return False
703
704
705
class TestInvalidStatusServer(TestBadStatusServer):
706
    """Tests invalid status from server.
707
708
    Both implementations raises the same error as for a bad status.
709
    """
710
711
    _req_handler_class = InvalidStatusRequestHandler
712
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
713
    def test_http_has(self):
714
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
715
            raise tests.KnownFailure(
716
                'pycurl hangs if the server send back garbage')
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
717
        super(TestInvalidStatusServer, self).test_http_has()
718
719
    def test_http_get(self):
720
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
721
            raise tests.KnownFailure(
722
                'pycurl hangs if the server send back garbage')
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
723
        super(TestInvalidStatusServer, self).test_http_get()
724
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
725
726
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
727
    """Whatever request comes in, returns a bad protocol version"""
728
729
    def parse_request(self):
730
        """Fakes handling a single HTTP request, returns a bad status"""
731
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
732
        # Returns an invalid protocol version, but curl just
733
        # ignores it and those cannot be tested.
734
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
735
                                           404,
736
                                           'Look at my protocol version'))
737
        return False
738
739
740
class TestBadProtocolServer(TestSpecificRequestHandler):
741
    """Tests bad protocol from server."""
742
743
    _req_handler_class = BadProtocolRequestHandler
744
745
    def setUp(self):
746
        if pycurl_present and self._transport == PyCurlTransport:
747
            raise tests.TestNotApplicable(
748
                "pycurl doesn't check the protocol version")
749
        super(TestBadProtocolServer, self).setUp()
750
751
    def test_http_has(self):
752
        server = self.get_readonly_server()
753
        t = self._transport(server.get_url())
754
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
755
756
    def test_http_get(self):
757
        server = self.get_readonly_server()
758
        t = self._transport(server.get_url())
759
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
760
761
762
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
763
    """Whatever request comes in, returns a 403 code"""
764
765
    def parse_request(self):
766
        """Handle a single HTTP request, by replying we cannot handle it"""
767
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
768
        self.send_error(403)
769
        return False
770
771
772
class TestForbiddenServer(TestSpecificRequestHandler):
773
    """Tests forbidden server"""
774
775
    _req_handler_class = ForbiddenRequestHandler
776
777
    def test_http_has(self):
778
        server = self.get_readonly_server()
779
        t = self._transport(server.get_url())
780
        self.assertRaises(errors.TransportError, t.has, 'foo/bar')
781
782
    def test_http_get(self):
783
        server = self.get_readonly_server()
784
        t = self._transport(server.get_url())
785
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
786
787
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
788
class TestRecordingServer(tests.TestCase):
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
789
790
    def test_create(self):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
791
        server = RecordingServer(expect_body_tail=None)
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
792
        self.assertEqual('', server.received_bytes)
793
        self.assertEqual(None, server.host)
794
        self.assertEqual(None, server.port)
795
796
    def test_setUp_and_tearDown(self):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
797
        server = RecordingServer(expect_body_tail=None)
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
798
        server.setUp()
799
        try:
800
            self.assertNotEqual(None, server.host)
801
            self.assertNotEqual(None, server.port)
802
        finally:
803
            server.tearDown()
804
        self.assertEqual(None, server.host)
805
        self.assertEqual(None, server.port)
806
807
    def test_send_receive_bytes(self):
4691.2.1 by Robert Collins
Add stronger test isolation by interception BzrDir.open and checking the thing being opened is known to the test suite.
808
        server = RecordingServer(expect_body_tail='c', scheme='http')
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
809
        self.start_server(server)
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
810
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
811
        sock.connect((server.host, server.port))
812
        sock.sendall('abc')
813
        self.assertEqual('HTTP/1.1 200 OK\r\n',
2091.1.1 by Martin Pool
Avoid MSG_WAITALL as it doesn't work on Windows
814
                         osutils.recv_all(sock, 4096))
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
815
        self.assertEqual('abc', server.received_bytes)
2004.1.29 by v.ladeuil+lp at free
New tests for http range requests handling.
816
817
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
818
class TestRangeRequestServer(TestSpecificRequestHandler):
819
    """Tests readv requests against server.
820
821
    We test against default "normal" server.
822
    """
823
824
    def setUp(self):
825
        super(TestRangeRequestServer, self).setUp()
826
        self.build_tree_contents([('a', '0123456789')],)
827
828
    def test_readv(self):
829
        server = self.get_readonly_server()
830
        t = self._transport(server.get_url())
831
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
832
        self.assertEqual(l[0], (0, '0'))
833
        self.assertEqual(l[1], (1, '1'))
834
        self.assertEqual(l[2], (3, '34'))
835
        self.assertEqual(l[3], (9, '9'))
836
837
    def test_readv_out_of_order(self):
838
        server = self.get_readonly_server()
839
        t = self._transport(server.get_url())
840
        l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
841
        self.assertEqual(l[0], (1, '1'))
842
        self.assertEqual(l[1], (9, '9'))
843
        self.assertEqual(l[2], (0, '0'))
844
        self.assertEqual(l[3], (3, '34'))
845
846
    def test_readv_invalid_ranges(self):
847
        server = self.get_readonly_server()
848
        t = self._transport(server.get_url())
849
850
        # This is intentionally reading off the end of the file
851
        # since we are sure that it cannot get there
852
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
853
                              t.readv, 'a', [(1,1), (8,10)])
854
855
        # This is trying to seek past the end of the file, it should
856
        # also raise a special error
857
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
858
                              t.readv, 'a', [(12,2)])
859
860
    def test_readv_multiple_get_requests(self):
861
        server = self.get_readonly_server()
862
        t = self._transport(server.get_url())
863
        # force transport to issue multiple requests
864
        t._max_readv_combine = 1
865
        t._max_get_ranges = 1
866
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
867
        self.assertEqual(l[0], (0, '0'))
868
        self.assertEqual(l[1], (1, '1'))
869
        self.assertEqual(l[2], (3, '34'))
870
        self.assertEqual(l[3], (9, '9'))
871
        # The server should have issued 4 requests
872
        self.assertEqual(4, server.GET_request_nb)
873
874
    def test_readv_get_max_size(self):
875
        server = self.get_readonly_server()
876
        t = self._transport(server.get_url())
877
        # force transport to issue multiple requests by limiting the number of
878
        # bytes by request. Note that this apply to coalesced offsets only, a
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
879
        # single range will keep its size even if bigger than the limit.
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
880
        t._get_max_size = 2
881
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
882
        self.assertEqual(l[0], (0, '0'))
883
        self.assertEqual(l[1], (1, '1'))
884
        self.assertEqual(l[2], (2, '2345'))
885
        self.assertEqual(l[3], (6, '6789'))
886
        # The server should have issued 3 requests
887
        self.assertEqual(3, server.GET_request_nb)
888
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
889
    def test_complete_readv_leave_pipe_clean(self):
890
        server = self.get_readonly_server()
891
        t = self._transport(server.get_url())
892
        # force transport to issue multiple requests
893
        t._get_max_size = 2
894
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
895
        # The server should have issued 3 requests
896
        self.assertEqual(3, server.GET_request_nb)
897
        self.assertEqual('0123456789', t.get_bytes('a'))
898
        self.assertEqual(4, server.GET_request_nb)
899
900
    def test_incomplete_readv_leave_pipe_clean(self):
901
        server = self.get_readonly_server()
902
        t = self._transport(server.get_url())
903
        # force transport to issue multiple requests
904
        t._get_max_size = 2
905
        # Don't collapse readv results into a list so that we leave unread
906
        # bytes on the socket
907
        ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
908
        self.assertEqual((0, '0'), ireadv.next())
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
909
        # The server should have issued one request so far
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
910
        self.assertEqual(1, server.GET_request_nb)
911
        self.assertEqual('0123456789', t.get_bytes('a'))
912
        # get_bytes issued an additional request, the readv pending ones are
913
        # lost
914
        self.assertEqual(2, server.GET_request_nb)
915
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
916
917
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
918
    """Always reply to range request as if they were single.
919
920
    Don't be explicit about it, just to annoy the clients.
921
    """
922
923
    def get_multiple_ranges(self, file, file_size, ranges):
924
        """Answer as if it was a single range request and ignores the rest"""
925
        (start, end) = ranges[0]
926
        return self.get_single_range(file, file_size, start, end)
927
928
929
class TestSingleRangeRequestServer(TestRangeRequestServer):
930
    """Test readv against a server which accept only single range requests"""
931
932
    _req_handler_class = SingleRangeRequestHandler
933
934
935
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
936
    """Only reply to simple range requests, errors out on multiple"""
937
938
    def get_multiple_ranges(self, file, file_size, ranges):
939
        """Refuses the multiple ranges request"""
940
        if len(ranges) > 1:
941
            file.close()
942
            self.send_error(416, "Requested range not satisfiable")
943
            return
944
        (start, end) = ranges[0]
945
        return self.get_single_range(file, file_size, start, end)
946
947
948
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
949
    """Test readv against a server which only accept single range requests"""
950
951
    _req_handler_class = SingleOnlyRangeRequestHandler
952
953
954
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
955
    """Ignore range requests without notice"""
956
957
    def do_GET(self):
958
        # Update the statistics
959
        self.server.test_case_server.GET_request_nb += 1
960
        # Just bypass the range handling done by TestingHTTPRequestHandler
961
        return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
962
963
964
class TestNoRangeRequestServer(TestRangeRequestServer):
965
    """Test readv against a server which do not accept range requests"""
966
967
    _req_handler_class = NoRangeRequestHandler
968
969
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
970
class MultipleRangeWithoutContentLengthRequestHandler(
971
    http_server.TestingHTTPRequestHandler):
972
    """Reply to multiple range requests without content length header."""
973
974
    def get_multiple_ranges(self, file, file_size, ranges):
975
        self.send_response(206)
976
        self.send_header('Accept-Ranges', 'bytes')
977
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
978
        self.send_header("Content-Type",
979
                         "multipart/byteranges; boundary=%s" % boundary)
980
        self.end_headers()
981
        for (start, end) in ranges:
982
            self.wfile.write("--%s\r\n" % boundary)
983
            self.send_header("Content-type", 'application/octet-stream')
984
            self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
985
                                                                  end,
986
                                                                  file_size))
987
            self.end_headers()
988
            self.send_range_content(file, start, end - start + 1)
989
        # Final boundary
990
        self.wfile.write("--%s\r\n" % boundary)
991
992
993
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
994
995
    _req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
996
3146.3.2 by Vincent Ladeuil
Fix #179368 by keeping the current range hint on ShortReadvErrors.
997
998
class TruncatedMultipleRangeRequestHandler(
999
    http_server.TestingHTTPRequestHandler):
1000
    """Reply to multiple range requests truncating the last ones.
1001
1002
    This server generates responses whose Content-Length describes all the
1003
    ranges, but fail to include the last ones leading to client short reads.
1004
    This has been observed randomly with lighttpd (bug #179368).
1005
    """
1006
1007
    _truncated_ranges = 2
1008
1009
    def get_multiple_ranges(self, file, file_size, ranges):
1010
        self.send_response(206)
1011
        self.send_header('Accept-Ranges', 'bytes')
1012
        boundary = 'tagada'
1013
        self.send_header('Content-Type',
1014
                         'multipart/byteranges; boundary=%s' % boundary)
1015
        boundary_line = '--%s\r\n' % boundary
1016
        # Calculate the Content-Length
1017
        content_length = 0
1018
        for (start, end) in ranges:
1019
            content_length += len(boundary_line)
1020
            content_length += self._header_line_length(
1021
                'Content-type', 'application/octet-stream')
1022
            content_length += self._header_line_length(
1023
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
1024
            content_length += len('\r\n') # end headers
1025
            content_length += end - start # + 1
1026
        content_length += len(boundary_line)
1027
        self.send_header('Content-length', content_length)
1028
        self.end_headers()
1029
1030
        # Send the multipart body
1031
        cur = 0
1032
        for (start, end) in ranges:
1033
            self.wfile.write(boundary_line)
1034
            self.send_header('Content-type', 'application/octet-stream')
1035
            self.send_header('Content-Range', 'bytes %d-%d/%d'
1036
                             % (start, end, file_size))
1037
            self.end_headers()
1038
            if cur + self._truncated_ranges >= len(ranges):
1039
                # Abruptly ends the response and close the connection
1040
                self.close_connection = 1
1041
                return
1042
            self.send_range_content(file, start, end - start + 1)
1043
            cur += 1
1044
        # No final boundary
1045
        self.wfile.write(boundary_line)
1046
1047
1048
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
1049
1050
    _req_handler_class = TruncatedMultipleRangeRequestHandler
1051
1052
    def setUp(self):
1053
        super(TestTruncatedMultipleRangeServer, self).setUp()
1054
        self.build_tree_contents([('a', '0123456789')],)
1055
1056
    def test_readv_with_short_reads(self):
1057
        server = self.get_readonly_server()
1058
        t = self._transport(server.get_url())
1059
        # Force separate ranges for each offset
1060
        t._bytes_to_read_before_seek = 0
1061
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1062
        self.assertEqual((0, '0'), ireadv.next())
1063
        self.assertEqual((2, '2'), ireadv.next())
1064
        if not self._testing_pycurl():
1065
            # Only one request have been issued so far (except for pycurl that
1066
            # try to read the whole response at once)
1067
            self.assertEqual(1, server.GET_request_nb)
1068
        self.assertEqual((4, '45'), ireadv.next())
1069
        self.assertEqual((9, '9'), ireadv.next())
1070
        # Both implementations issue 3 requests but:
1071
        # - urllib does two multiple (4 ranges, then 2 ranges) then a single
1072
        #   range,
1073
        # - pycurl does two multiple (4 ranges, 4 ranges) then a single range
1074
        self.assertEqual(3, server.GET_request_nb)
1075
        # Finally the client have tried a single range request and stays in
1076
        # that mode
1077
        self.assertEqual('single', t._range_hint)
1078
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1079
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1080
    """Errors out when range specifiers exceed the limit"""
1081
1082
    def get_multiple_ranges(self, file, file_size, ranges):
1083
        """Refuses the multiple ranges request"""
1084
        tcs = self.server.test_case_server
1085
        if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
1086
            file.close()
1087
            # Emulate apache behavior
1088
            self.send_error(400, "Bad Request")
1089
            return
1090
        return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1091
            self, file, file_size, ranges)
1092
1093
1094
class LimitedRangeHTTPServer(http_server.HttpServer):
1095
    """An HttpServer erroring out on requests with too much range specifiers"""
1096
1097
    def __init__(self, request_handler=LimitedRangeRequestHandler,
1098
                 protocol_version=None,
1099
                 range_limit=None):
1100
        http_server.HttpServer.__init__(self, request_handler,
1101
                                        protocol_version=protocol_version)
1102
        self.range_limit = range_limit
1103
1104
1105
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1106
    """Tests readv requests against a server erroring out on too much ranges."""
1107
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
1108
    # Requests with more range specifiers will error out
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1109
    range_limit = 3
1110
1111
    def create_transport_readonly_server(self):
1112
        return LimitedRangeHTTPServer(range_limit=self.range_limit,
1113
                                      protocol_version=self._protocol_version)
1114
1115
    def get_transport(self):
1116
        return self._transport(self.get_readonly_server().get_url())
1117
1118
    def setUp(self):
1119
        http_utils.TestCaseWithWebserver.setUp(self)
1120
        # We need to manipulate ranges that correspond to real chunks in the
1121
        # response, so we build a content appropriately.
1122
        filler = ''.join(['abcdefghij' for x in range(102)])
1123
        content = ''.join(['%04d' % v + filler for v in range(16)])
1124
        self.build_tree_contents([('a', content)],)
1125
1126
    def test_few_ranges(self):
1127
        t = self.get_transport()
1128
        l = list(t.readv('a', ((0, 4), (1024, 4), )))
1129
        self.assertEqual(l[0], (0, '0000'))
1130
        self.assertEqual(l[1], (1024, '0001'))
1131
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1132
1133
    def test_more_ranges(self):
1134
        t = self.get_transport()
1135
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1136
        self.assertEqual(l[0], (0, '0000'))
1137
        self.assertEqual(l[1], (1024, '0001'))
1138
        self.assertEqual(l[2], (4096, '0004'))
1139
        self.assertEqual(l[3], (8192, '0008'))
1140
        # The server will refuse to serve the first request (too much ranges),
3199.1.2 by Vincent Ladeuil
Fix two more leaked log files.
1141
        # a second request will succeed.
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1142
        self.assertEqual(2, self.get_readonly_server().GET_request_nb)
1143
1144
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
1145
class TestHttpProxyWhiteBox(tests.TestCase):
2298.7.1 by Vincent Ladeuil
Fix bug #87765: proxy env variables without scheme should cause
1146
    """Whitebox test proxy http authorization.
1147
2420.1.3 by Vincent Ladeuil
Implement http proxy basic authentication.
1148
    Only the urllib implementation is tested here.
2298.7.1 by Vincent Ladeuil
Fix bug #87765: proxy env variables without scheme should cause
1149
    """
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1150
1151
    def setUp(self):
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
1152
        tests.TestCase.setUp(self)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1153
        self._old_env = {}
1154
1155
    def tearDown(self):
1156
        self._restore_env()
3199.1.2 by Vincent Ladeuil
Fix two more leaked log files.
1157
        tests.TestCase.tearDown(self)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1158
1159
    def _install_env(self, env):
1160
        for name, value in env.iteritems():
2420.1.2 by Vincent Ladeuil
Define tests for http proxy basic authentication. They fail.
1161
            self._old_env[name] = osutils.set_or_unset_env(name, value)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1162
1163
    def _restore_env(self):
1164
        for name, value in self._old_env.iteritems():
1165
            osutils.set_or_unset_env(name, value)
1166
1167
    def _proxied_request(self):
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
1168
        handler = _urllib2_wrappers.ProxyHandler()
1169
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1170
        handler.set_proxy(request, 'http')
1171
        return request
1172
1173
    def test_empty_user(self):
1174
        self._install_env({'http_proxy': 'http://bar.com'})
1175
        request = self._proxied_request()
1176
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
1177
2298.7.1 by Vincent Ladeuil
Fix bug #87765: proxy env variables without scheme should cause
1178
    def test_invalid_proxy(self):
1179
        """A proxy env variable without scheme"""
1180
        self._install_env({'http_proxy': 'host:1234'})
1181
        self.assertRaises(errors.InvalidURL, self._proxied_request)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1182
1183
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1184
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
1185
    """Tests proxy server.
1186
1187
    Be aware that we do not setup a real proxy here. Instead, we
1188
    check that the *connection* goes through the proxy by serving
1189
    different content (the faked proxy server append '-proxied'
1190
    to the file names).
1191
    """
1192
1193
    # FIXME: We don't have an https server available, so we don't
1194
    # test https connections.
1195
1196
    def setUp(self):
1197
        super(TestProxyHttpServer, self).setUp()
1198
        self.build_tree_contents([('foo', 'contents of foo\n'),
1199
                                  ('foo-proxied', 'proxied contents of foo\n')])
1200
        # Let's setup some attributes for tests
1201
        self.server = self.get_readonly_server()
1202
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
1203
        if self._testing_pycurl():
1204
            # Oh my ! pycurl does not check for the port as part of
1205
            # no_proxy :-( So we just test the host part
1206
            self.no_proxy_host = 'localhost'
1207
        else:
1208
            self.no_proxy_host = self.proxy_address
1209
        # The secondary server is the proxy
1210
        self.proxy = self.get_secondary_server()
1211
        self.proxy_url = self.proxy.get_url()
1212
        self._old_env = {}
1213
1214
    def _testing_pycurl(self):
1215
        return pycurl_present and self._transport == PyCurlTransport
1216
1217
    def create_transport_secondary_server(self):
1218
        """Creates an http server that will serve files with
1219
        '-proxied' appended to their names.
1220
        """
1221
        return http_utils.ProxyServer(protocol_version=self._protocol_version)
1222
1223
    def _install_env(self, env):
1224
        for name, value in env.iteritems():
1225
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1226
1227
    def _restore_env(self):
1228
        for name, value in self._old_env.iteritems():
1229
            osutils.set_or_unset_env(name, value)
1230
1231
    def proxied_in_env(self, env):
1232
        self._install_env(env)
1233
        url = self.server.get_url()
1234
        t = self._transport(url)
1235
        try:
3734.2.8 by Vincent Ladeuil
Catch spurious exceptions (python-2.6) when SocketServer is shut down.
1236
            self.assertEqual('proxied contents of foo\n', t.get('foo').read())
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1237
        finally:
1238
            self._restore_env()
1239
1240
    def not_proxied_in_env(self, env):
1241
        self._install_env(env)
1242
        url = self.server.get_url()
1243
        t = self._transport(url)
1244
        try:
3734.2.8 by Vincent Ladeuil
Catch spurious exceptions (python-2.6) when SocketServer is shut down.
1245
            self.assertEqual('contents of foo\n', t.get('foo').read())
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1246
        finally:
1247
            self._restore_env()
1248
1249
    def test_http_proxy(self):
1250
        self.proxied_in_env({'http_proxy': self.proxy_url})
1251
1252
    def test_HTTP_PROXY(self):
1253
        if self._testing_pycurl():
1254
            # pycurl does not check HTTP_PROXY for security reasons
1255
            # (for use in a CGI context that we do not care
1256
            # about. Should we ?)
1257
            raise tests.TestNotApplicable(
1258
                'pycurl does not check HTTP_PROXY for security reasons')
1259
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
1260
1261
    def test_all_proxy(self):
1262
        self.proxied_in_env({'all_proxy': self.proxy_url})
1263
1264
    def test_ALL_PROXY(self):
1265
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
1266
1267
    def test_http_proxy_with_no_proxy(self):
1268
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
1269
                                 'no_proxy': self.no_proxy_host})
1270
1271
    def test_HTTP_PROXY_with_NO_PROXY(self):
1272
        if self._testing_pycurl():
1273
            raise tests.TestNotApplicable(
1274
                'pycurl does not check HTTP_PROXY for security reasons')
1275
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
1276
                                 'NO_PROXY': self.no_proxy_host})
1277
1278
    def test_all_proxy_with_no_proxy(self):
1279
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
1280
                                 'no_proxy': self.no_proxy_host})
1281
1282
    def test_ALL_PROXY_with_NO_PROXY(self):
1283
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
1284
                                 'NO_PROXY': self.no_proxy_host})
1285
1286
    def test_http_proxy_without_scheme(self):
1287
        if self._testing_pycurl():
1288
            # pycurl *ignores* invalid proxy env variables. If that ever change
1289
            # in the future, this test will fail indicating that pycurl do not
1290
            # ignore anymore such variables.
1291
            self.not_proxied_in_env({'http_proxy': self.proxy_address})
1292
        else:
1293
            self.assertRaises(errors.InvalidURL,
1294
                              self.proxied_in_env,
1295
                              {'http_proxy': self.proxy_address})
1296
1297
1298
class TestRanges(http_utils.TestCaseWithWebserver):
1299
    """Test the Range header in GET methods."""
1300
1301
    def setUp(self):
1302
        http_utils.TestCaseWithWebserver.setUp(self)
1303
        self.build_tree_contents([('a', '0123456789')],)
1304
        server = self.get_readonly_server()
1305
        self.transport = self._transport(server.get_url())
1306
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
1307
    def create_transport_readonly_server(self):
1308
        return http_server.HttpServer(protocol_version=self._protocol_version)
1309
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1310
    def _file_contents(self, relpath, ranges):
1311
        offsets = [ (start, end - start + 1) for start, end in ranges]
1312
        coalesce = self.transport._coalesce_offsets
1313
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1314
        code, data = self.transport._get(relpath, coalesced)
1315
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1316
        for start, end in ranges:
1317
            data.seek(start)
1318
            yield data.read(end - start + 1)
1319
1320
    def _file_tail(self, relpath, tail_amount):
1321
        code, data = self.transport._get(relpath, [], tail_amount)
1322
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1323
        data.seek(-tail_amount, 2)
1324
        return data.read(tail_amount)
1325
1326
    def test_range_header(self):
1327
        # Valid ranges
1328
        map(self.assertEqual,['0', '234'],
1329
            list(self._file_contents('a', [(0,0), (2,4)])),)
1330
1331
    def test_range_header_tail(self):
1332
        self.assertEqual('789', self._file_tail('a', 3))
1333
1334
    def test_syntactically_invalid_range_header(self):
1335
        self.assertListRaises(errors.InvalidHttpRange,
1336
                          self._file_contents, 'a', [(4, 3)])
1337
1338
    def test_semantically_invalid_range_header(self):
1339
        self.assertListRaises(errors.InvalidHttpRange,
1340
                          self._file_contents, 'a', [(42, 128)])
1341
1342
1343
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1344
    """Test redirection between http servers."""
1345
1346
    def create_transport_secondary_server(self):
1347
        """Create the secondary server redirecting to the primary server"""
1348
        new = self.get_readonly_server()
1349
1350
        redirecting = http_utils.HTTPServerRedirecting(
1351
            protocol_version=self._protocol_version)
1352
        redirecting.redirect_to(new.host, new.port)
1353
        return redirecting
1354
1355
    def setUp(self):
1356
        super(TestHTTPRedirections, self).setUp()
1357
        self.build_tree_contents([('a', '0123456789'),
1358
                                  ('bundle',
1359
                                  '# Bazaar revision bundle v0.9\n#\n')
1360
                                  ],)
3878.4.1 by Vincent Ladeuil
Fix bug #245964 by preserving decorators during redirections (when
1361
        # The requests to the old server will be redirected to the new server
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1362
        self.old_transport = self._transport(self.old_server.get_url())
1363
1364
    def test_redirected(self):
1365
        self.assertRaises(errors.RedirectRequested, self.old_transport.get, 'a')
1366
        t = self._transport(self.new_server.get_url())
1367
        self.assertEqual('0123456789', t.get('a').read())
1368
1369
    def test_read_redirected_bundle_from_url(self):
1370
        from bzrlib.bundle import read_bundle_from_url
1371
        url = self.old_transport.abspath('bundle')
3995.2.2 by Martin Pool
Cope with read_bundle_from_url deprecation in test_http
1372
        bundle = self.applyDeprecated(deprecated_in((1, 12, 0)),
1373
                read_bundle_from_url, url)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1374
        # If read_bundle_from_url was successful we get an empty bundle
1375
        self.assertEqual([], bundle.revisions)
1376
1377
1378
class RedirectedRequest(_urllib2_wrappers.Request):
1379
    """Request following redirections. """
1380
1381
    init_orig = _urllib2_wrappers.Request.__init__
1382
1383
    def __init__(self, method, url, *args, **kwargs):
1384
        """Constructor.
1385
1386
        """
1387
        # Since the tests using this class will replace
1388
        # _urllib2_wrappers.Request, we can't just call the base class __init__
1389
        # or we'll loop.
4208.3.2 by Andrew Bennetts
Fix one test failure in test_http under Python 2.7a0.
1390
        RedirectedRequest.init_orig(self, method, url, *args, **kwargs)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1391
        self.follow_redirections = True
1392
1393
1394
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
1395
    """Test redirections.
1396
1397
    http implementations do not redirect silently anymore (they
1398
    do not redirect at all in fact). The mechanism is still in
1399
    place at the _urllib2_wrappers.Request level and these tests
1400
    exercise it.
1401
1402
    For the pycurl implementation
1403
    the redirection have been deleted as we may deprecate pycurl
1404
    and I have no place to keep a working implementation.
1405
    -- vila 20070212
1406
    """
1407
1408
    def setUp(self):
1409
        if pycurl_present and self._transport == PyCurlTransport:
1410
            raise tests.TestNotApplicable(
1411
                "pycurl doesn't redirect silently annymore")
1412
        super(TestHTTPSilentRedirections, self).setUp()
1413
        self.setup_redirected_request()
1414
        self.addCleanup(self.cleanup_redirected_request)
1415
        self.build_tree_contents([('a','a'),
1416
                                  ('1/',),
1417
                                  ('1/a', 'redirected once'),
1418
                                  ('2/',),
1419
                                  ('2/a', 'redirected twice'),
1420
                                  ('3/',),
1421
                                  ('3/a', 'redirected thrice'),
1422
                                  ('4/',),
1423
                                  ('4/a', 'redirected 4 times'),
1424
                                  ('5/',),
1425
                                  ('5/a', 'redirected 5 times'),
1426
                                  ],)
1427
1428
        self.old_transport = self._transport(self.old_server.get_url())
1429
1430
    def setup_redirected_request(self):
1431
        self.original_class = _urllib2_wrappers.Request
1432
        _urllib2_wrappers.Request = RedirectedRequest
1433
1434
    def cleanup_redirected_request(self):
1435
        _urllib2_wrappers.Request = self.original_class
1436
1437
    def create_transport_secondary_server(self):
1438
        """Create the secondary server, redirections are defined in the tests"""
1439
        return http_utils.HTTPServerRedirecting(
1440
            protocol_version=self._protocol_version)
1441
1442
    def test_one_redirection(self):
1443
        t = self.old_transport
1444
1445
        req = RedirectedRequest('GET', t.abspath('a'))
1446
        req.follow_redirections = True
1447
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1448
                                       self.new_server.port)
1449
        self.old_server.redirections = \
1450
            [('(.*)', r'%s/1\1' % (new_prefix), 301),]
1451
        self.assertEquals('redirected once',t._perform(req).read())
1452
1453
    def test_five_redirections(self):
1454
        t = self.old_transport
1455
1456
        req = RedirectedRequest('GET', t.abspath('a'))
1457
        req.follow_redirections = True
1458
        old_prefix = 'http://%s:%s' % (self.old_server.host,
1459
                                       self.old_server.port)
1460
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1461
                                       self.new_server.port)
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
1462
        self.old_server.redirections = [
1463
            ('/1(.*)', r'%s/2\1' % (old_prefix), 302),
1464
            ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
1465
            ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
1466
            ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1467
            ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1468
            ]
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1469
        self.assertEquals('redirected 5 times',t._perform(req).read())
1470
1471
1472
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1473
    """Test transport.do_catching_redirections."""
1474
1475
    def setUp(self):
1476
        super(TestDoCatchRedirections, self).setUp()
1477
        self.build_tree_contents([('a', '0123456789'),],)
1478
1479
        self.old_transport = self._transport(self.old_server.get_url())
1480
1481
    def get_a(self, transport):
1482
        return transport.get('a')
1483
1484
    def test_no_redirection(self):
1485
        t = self._transport(self.new_server.get_url())
1486
1487
        # We use None for redirected so that we fail if redirected
1488
        self.assertEquals('0123456789',
1489
                          transport.do_catching_redirections(
1490
                self.get_a, t, None).read())
1491
1492
    def test_one_redirection(self):
1493
        self.redirections = 0
1494
1495
        def redirected(transport, exception, redirection_notice):
1496
            self.redirections += 1
1497
            dir, file = urlutils.split(exception.target)
1498
            return self._transport(dir)
1499
1500
        self.assertEquals('0123456789',
1501
                          transport.do_catching_redirections(
1502
                self.get_a, self.old_transport, redirected).read())
1503
        self.assertEquals(1, self.redirections)
1504
1505
    def test_redirection_loop(self):
1506
1507
        def redirected(transport, exception, redirection_notice):
1508
            # By using the redirected url as a base dir for the
1509
            # *old* transport, we create a loop: a => a/a =>
1510
            # a/a/a
1511
            return self.old_transport.clone(exception.target)
1512
1513
        self.assertRaises(errors.TooManyRedirections,
1514
                          transport.do_catching_redirections,
1515
                          self.get_a, self.old_transport, redirected)
1516
1517
1518
class TestAuth(http_utils.TestCaseWithWebserver):
1519
    """Test authentication scheme"""
1520
1521
    _auth_header = 'Authorization'
1522
    _password_prompt_prefix = ''
4222.3.12 by Jelmer Vernooij
Check that the HTTP transport prompts for usernames.
1523
    _username_prompt_prefix = ''
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
1524
    # Set by load_tests
1525
    _auth_server = None
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1526
1527
    def setUp(self):
1528
        super(TestAuth, self).setUp()
1529
        self.server = self.get_readonly_server()
1530
        self.build_tree_contents([('a', 'contents of a\n'),
1531
                                  ('b', 'contents of b\n'),])
1532
1533
    def create_transport_readonly_server(self):
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
1534
        return self._auth_server(protocol_version=self._protocol_version)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1535
1536
    def _testing_pycurl(self):
1537
        return pycurl_present and self._transport == PyCurlTransport
1538
3910.2.4 by Vincent Ladeuil
Fixed as per John's review.
1539
    def get_user_url(self, user, password):
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1540
        """Build an url embedding user and password"""
1541
        url = '%s://' % self.server._url_protocol
1542
        if user is not None:
1543
            url += user
1544
            if password is not None:
1545
                url += ':' + password
1546
            url += '@'
1547
        url += '%s:%s/' % (self.server.host, self.server.port)
1548
        return url
1549
3910.2.4 by Vincent Ladeuil
Fixed as per John's review.
1550
    def get_user_transport(self, user, password):
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1551
        return self._transport(self.get_user_url(user, password))
1552
1553
    def test_no_user(self):
1554
        self.server.add_user('joe', 'foo')
3910.2.4 by Vincent Ladeuil
Fixed as per John's review.
1555
        t = self.get_user_transport(None, None)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1556
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1557
        # Only one 'Authentication Required' error should occur
1558
        self.assertEqual(1, self.server.auth_required_errors)
1559
1560
    def test_empty_pass(self):
1561
        self.server.add_user('joe', '')
1562
        t = self.get_user_transport('joe', '')
1563
        self.assertEqual('contents of a\n', t.get('a').read())
1564
        # Only one 'Authentication Required' error should occur
1565
        self.assertEqual(1, self.server.auth_required_errors)
1566
1567
    def test_user_pass(self):
1568
        self.server.add_user('joe', 'foo')
1569
        t = self.get_user_transport('joe', 'foo')
1570
        self.assertEqual('contents of a\n', t.get('a').read())
1571
        # Only one 'Authentication Required' error should occur
1572
        self.assertEqual(1, self.server.auth_required_errors)
1573
1574
    def test_unknown_user(self):
1575
        self.server.add_user('joe', 'foo')
1576
        t = self.get_user_transport('bill', 'foo')
1577
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1578
        # Two 'Authentication Required' errors should occur (the
1579
        # initial 'who are you' and 'I don't know you, who are
1580
        # you').
1581
        self.assertEqual(2, self.server.auth_required_errors)
1582
1583
    def test_wrong_pass(self):
1584
        self.server.add_user('joe', 'foo')
1585
        t = self.get_user_transport('joe', 'bar')
1586
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1587
        # Two 'Authentication Required' errors should occur (the
1588
        # initial 'who are you' and 'this is not you, who are you')
1589
        self.assertEqual(2, self.server.auth_required_errors)
1590
4222.3.12 by Jelmer Vernooij
Check that the HTTP transport prompts for usernames.
1591
    def test_prompt_for_username(self):
1592
        if self._testing_pycurl():
1593
            raise tests.TestNotApplicable(
1594
                'pycurl cannot prompt, it handles auth by embedding'
1595
                ' user:pass in urls only')
1596
1597
        self.server.add_user('joe', 'foo')
1598
        t = self.get_user_transport(None, None)
1599
        stdout = tests.StringIOWrapper()
4368.3.1 by Vincent Ladeuil
Use stderr for UI prompt to address bug #376582.
1600
        stderr = tests.StringIOWrapper()
1601
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
1602
                                            stdout=stdout, stderr=stderr)
4222.3.12 by Jelmer Vernooij
Check that the HTTP transport prompts for usernames.
1603
        self.assertEqual('contents of a\n',t.get('a').read())
1604
        # stdin should be empty
1605
        self.assertEqual('', ui.ui_factory.stdin.readline())
4368.3.1 by Vincent Ladeuil
Use stderr for UI prompt to address bug #376582.
1606
        stderr.seek(0)
4222.3.12 by Jelmer Vernooij
Check that the HTTP transport prompts for usernames.
1607
        expected_prompt = self._expected_username_prompt(t._unqualified_scheme)
4368.3.1 by Vincent Ladeuil
Use stderr for UI prompt to address bug #376582.
1608
        self.assertEquals(expected_prompt, stderr.read(len(expected_prompt)))
1609
        self.assertEquals('', stdout.getvalue())
4222.3.12 by Jelmer Vernooij
Check that the HTTP transport prompts for usernames.
1610
        self._check_password_prompt(t._unqualified_scheme, 'joe',
4368.3.1 by Vincent Ladeuil
Use stderr for UI prompt to address bug #376582.
1611
                                    stderr.readline())
4284.1.2 by Vincent Ladeuil
Delete spurious space.
1612
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1613
    def test_prompt_for_password(self):
1614
        if self._testing_pycurl():
1615
            raise tests.TestNotApplicable(
1616
                'pycurl cannot prompt, it handles auth by embedding'
1617
                ' user:pass in urls only')
1618
1619
        self.server.add_user('joe', 'foo')
1620
        t = self.get_user_transport('joe', None)
1621
        stdout = tests.StringIOWrapper()
4368.3.1 by Vincent Ladeuil
Use stderr for UI prompt to address bug #376582.
1622
        stderr = tests.StringIOWrapper()
1623
        ui.ui_factory = tests.TestUIFactory(stdin='foo\n',
1624
                                            stdout=stdout, stderr=stderr)
1625
        self.assertEqual('contents of a\n', t.get('a').read())
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1626
        # stdin should be empty
1627
        self.assertEqual('', ui.ui_factory.stdin.readline())
1628
        self._check_password_prompt(t._unqualified_scheme, 'joe',
4368.3.1 by Vincent Ladeuil
Use stderr for UI prompt to address bug #376582.
1629
                                    stderr.getvalue())
1630
        self.assertEquals('', stdout.getvalue())
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1631
        # And we shouldn't prompt again for a different request
1632
        # against the same transport.
1633
        self.assertEqual('contents of b\n',t.get('b').read())
1634
        t2 = t.clone()
1635
        # And neither against a clone
1636
        self.assertEqual('contents of b\n',t2.get('b').read())
1637
        # Only one 'Authentication Required' error should occur
1638
        self.assertEqual(1, self.server.auth_required_errors)
1639
1640
    def _check_password_prompt(self, scheme, user, actual_prompt):
1641
        expected_prompt = (self._password_prompt_prefix
1642
                           + ("%s %s@%s:%d, Realm: '%s' password: "
1643
                              % (scheme.upper(),
1644
                                 user, self.server.host, self.server.port,
1645
                                 self.server.auth_realm)))
1646
        self.assertEquals(expected_prompt, actual_prompt)
1647
4222.3.12 by Jelmer Vernooij
Check that the HTTP transport prompts for usernames.
1648
    def _expected_username_prompt(self, scheme):
1649
        return (self._username_prompt_prefix
1650
                + "%s %s:%d, Realm: '%s' username: " % (scheme.upper(),
1651
                                 self.server.host, self.server.port,
1652
                                 self.server.auth_realm))
1653
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1654
    def test_no_prompt_for_password_when_using_auth_config(self):
1655
        if self._testing_pycurl():
1656
            raise tests.TestNotApplicable(
1657
                'pycurl does not support authentication.conf'
1658
                ' since it cannot prompt')
1659
1660
        user =' joe'
1661
        password = 'foo'
1662
        stdin_content = 'bar\n'  # Not the right password
1663
        self.server.add_user(user, password)
1664
        t = self.get_user_transport(user, None)
1665
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1666
                                            stdout=tests.StringIOWrapper())
1667
        # Create a minimal config file with the right password
1668
        conf = config.AuthenticationConfig()
1669
        conf._get_config().update(
1670
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1671
                          'user': user, 'password': password}})
1672
        conf._save()
1673
        # Issue a request to the server to connect
1674
        self.assertEqual('contents of a\n',t.get('a').read())
1675
        # stdin should have  been left untouched
1676
        self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
1677
        # Only one 'Authentication Required' error should occur
1678
        self.assertEqual(1, self.server.auth_required_errors)
1679
3910.2.2 by Vincent Ladeuil
Fix bug #300347 by allowing querying authentication.conf if no
1680
    def test_user_from_auth_conf(self):
1681
        if self._testing_pycurl():
1682
            raise tests.TestNotApplicable(
1683
                'pycurl does not support authentication.conf')
3910.2.3 by Ben Jansen
Made tweaks requested by John Arbash Meinel.
1684
        user = 'joe'
3910.2.2 by Vincent Ladeuil
Fix bug #300347 by allowing querying authentication.conf if no
1685
        password = 'foo'
1686
        self.server.add_user(user, password)
1687
        # Create a minimal config file with the right password
1688
        conf = config.AuthenticationConfig()
1689
        conf._get_config().update(
1690
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1691
                          'user': user, 'password': password}})
1692
        conf._save()
3910.2.4 by Vincent Ladeuil
Fixed as per John's review.
1693
        t = self.get_user_transport(None, None)
3910.2.2 by Vincent Ladeuil
Fix bug #300347 by allowing querying authentication.conf if no
1694
        # Issue a request to the server to connect
3910.2.3 by Ben Jansen
Made tweaks requested by John Arbash Meinel.
1695
        self.assertEqual('contents of a\n', t.get('a').read())
3910.2.2 by Vincent Ladeuil
Fix bug #300347 by allowing querying authentication.conf if no
1696
        # Only one 'Authentication Required' error should occur
1697
        self.assertEqual(1, self.server.auth_required_errors)
1698
3111.1.26 by Vincent Ladeuil
Re-add a test lost in refactoring.
1699
    def test_changing_nonce(self):
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
1700
        if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1701
                                     http_utils.ProxyDigestAuthServer):
1702
            raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
3111.1.26 by Vincent Ladeuil
Re-add a test lost in refactoring.
1703
        if self._testing_pycurl():
1704
            raise tests.KnownFailure(
1705
                'pycurl does not handle a nonce change')
1706
        self.server.add_user('joe', 'foo')
1707
        t = self.get_user_transport('joe', 'foo')
1708
        self.assertEqual('contents of a\n', t.get('a').read())
1709
        self.assertEqual('contents of b\n', t.get('b').read())
1710
        # Only one 'Authentication Required' error should have
1711
        # occured so far
1712
        self.assertEqual(1, self.server.auth_required_errors)
1713
        # The server invalidates the current nonce
1714
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1715
        self.assertEqual('contents of a\n', t.get('a').read())
1716
        # Two 'Authentication Required' errors should occur (the
1717
        # initial 'who are you' and a second 'who are you' with the new nonce)
1718
        self.assertEqual(2, self.server.auth_required_errors)
1719
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1720
1721
1722
class TestProxyAuth(TestAuth):
1723
    """Test proxy authentication schemes."""
1724
1725
    _auth_header = 'Proxy-authorization'
4222.3.12 by Jelmer Vernooij
Check that the HTTP transport prompts for usernames.
1726
    _password_prompt_prefix = 'Proxy '
1727
    _username_prompt_prefix = 'Proxy '
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1728
1729
    def setUp(self):
1730
        super(TestProxyAuth, self).setUp()
1731
        self._old_env = {}
1732
        self.addCleanup(self._restore_env)
1733
        # Override the contents to avoid false positives
1734
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1735
                                  ('b', 'not proxied contents of b\n'),
1736
                                  ('a-proxied', 'contents of a\n'),
1737
                                  ('b-proxied', 'contents of b\n'),
1738
                                  ])
1739
3910.2.4 by Vincent Ladeuil
Fixed as per John's review.
1740
    def get_user_transport(self, user, password):
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1741
        self._install_env({'all_proxy': self.get_user_url(user, password)})
1742
        return self._transport(self.server.get_url())
1743
1744
    def _install_env(self, env):
1745
        for name, value in env.iteritems():
1746
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1747
1748
    def _restore_env(self):
1749
        for name, value in self._old_env.iteritems():
1750
            osutils.set_or_unset_env(name, value)
1751
1752
    def test_empty_pass(self):
1753
        if self._testing_pycurl():
1754
            import pycurl
1755
            if pycurl.version_info()[1] < '7.16.0':
1756
                raise tests.KnownFailure(
1757
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
1758
        super(TestProxyAuth, self).test_empty_pass()
1759
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
1760
1761
class SampleSocket(object):
1762
    """A socket-like object for use in testing the HTTP request handler."""
1763
1764
    def __init__(self, socket_read_content):
1765
        """Constructs a sample socket.
1766
1767
        :param socket_read_content: a byte sequence
1768
        """
1769
        # Use plain python StringIO so we can monkey-patch the close method to
1770
        # not discard the contents.
1771
        from StringIO import StringIO
1772
        self.readfile = StringIO(socket_read_content)
1773
        self.writefile = StringIO()
1774
        self.writefile.close = lambda: None
1775
1776
    def makefile(self, mode='r', bufsize=None):
1777
        if 'r' in mode:
1778
            return self.readfile
1779
        else:
1780
            return self.writefile
1781
1782
1783
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1784
1785
    def setUp(self):
1786
        super(SmartHTTPTunnellingTest, self).setUp()
1787
        # We use the VFS layer as part of HTTP tunnelling tests.
1788
        self._captureVar('BZR_NO_SMART_VFS', None)
1789
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1790
1791
    def create_transport_readonly_server(self):
1792
        return http_utils.HTTPServerWithSmarts(
1793
            protocol_version=self._protocol_version)
1794
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
1795
    def test_open_bzrdir(self):
1796
        branch = self.make_branch('relpath')
1797
        http_server = self.get_readonly_server()
1798
        url = http_server.get_url() + 'relpath'
1799
        bd = bzrdir.BzrDir.open(url)
1800
        self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1801
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
1802
    def test_bulk_data(self):
1803
        # We should be able to send and receive bulk data in a single message.
1804
        # The 'readv' command in the smart protocol both sends and receives
1805
        # bulk data, so we use that.
1806
        self.build_tree(['data-file'])
1807
        http_server = self.get_readonly_server()
1808
        http_transport = self._transport(http_server.get_url())
1809
        medium = http_transport.get_smart_medium()
1810
        # Since we provide the medium, the url below will be mostly ignored
1811
        # during the test, as long as the path is '/'.
1812
        remote_transport = remote.RemoteTransport('bzr://fake_host/',
1813
                                                  medium=medium)
1814
        self.assertEqual(
1815
            [(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1816
1817
    def test_http_send_smart_request(self):
1818
1819
        post_body = 'hello\n'
3245.4.59 by Andrew Bennetts
Various tweaks in response to Martin's review.
1820
        expected_reply_body = 'ok\x012\n'
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
1821
1822
        http_server = self.get_readonly_server()
1823
        http_transport = self._transport(http_server.get_url())
1824
        medium = http_transport.get_smart_medium()
1825
        response = medium.send_http_smart_request(post_body)
1826
        reply_body = response.read()
1827
        self.assertEqual(expected_reply_body, reply_body)
1828
1829
    def test_smart_http_server_post_request_handler(self):
1830
        httpd = self.get_readonly_server()._get_httpd()
1831
1832
        socket = SampleSocket(
1833
            'POST /.bzr/smart %s \r\n' % self._protocol_version
1834
            # HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1835
            # for 1.0)
1836
            + 'Content-Length: 6\r\n'
1837
            '\r\n'
1838
            'hello\n')
1839
        # Beware: the ('localhost', 80) below is the
1840
        # client_address parameter, but we don't have one because
1841
        # we have defined a socket which is not bound to an
1842
        # address. The test framework never uses this client
1843
        # address, so far...
1844
        request_handler = http_utils.SmartRequestHandler(socket,
1845
                                                         ('localhost', 80),
1846
                                                         httpd)
1847
        response = socket.writefile.getvalue()
1848
        self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1849
        # This includes the end of the HTTP headers, and all the body.
3245.4.59 by Andrew Bennetts
Various tweaks in response to Martin's review.
1850
        expected_end_of_response = '\r\n\r\nok\x012\n'
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
1851
        self.assertEndsWith(response, expected_end_of_response)
1852
1853
3430.3.4 by Vincent Ladeuil
Of course we can write tests !
1854
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1855
    """No smart server here request handler."""
1856
1857
    def do_POST(self):
1858
        self.send_error(403, "Forbidden")
1859
1860
1861
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1862
    """Test smart client behaviour against an http server without smarts."""
1863
1864
    _req_handler_class = ForbiddenRequestHandler
1865
1866
    def test_probe_smart_server(self):
1867
        """Test error handling against server refusing smart requests."""
1868
        server = self.get_readonly_server()
1869
        t = self._transport(server.get_url())
1870
        # No need to build a valid smart request here, the server will not even
1871
        # try to interpret it.
1872
        self.assertRaises(errors.SmartProtocolError,
3734.3.1 by Vincent Ladeuil
Fix SmartHTTPMedium refactoring related test.
1873
                          t.get_smart_medium().send_http_smart_request,
1874
                          'whatever')
3430.3.4 by Vincent Ladeuil
Of course we can write tests !
1875
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1876
class Test_redirected_to(tests.TestCase):
1877
1878
    def test_redirected_to_subdir(self):
1879
        t = self._transport('http://www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1880
        r = t._redirected_to('http://www.example.com/foo',
1881
                             'http://www.example.com/foo/subdir')
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1882
        self.assertIsInstance(r, type(t))
1883
        # Both transports share the some connection
1884
        self.assertEquals(t._get_connection(), r._get_connection())
1885
3878.4.3 by Vincent Ladeuil
Fix bug #303959 by returning a transport based on the same url
1886
    def test_redirected_to_self_with_slash(self):
1887
        t = self._transport('http://www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1888
        r = t._redirected_to('http://www.example.com/foo',
1889
                             'http://www.example.com/foo/')
3878.4.3 by Vincent Ladeuil
Fix bug #303959 by returning a transport based on the same url
1890
        self.assertIsInstance(r, type(t))
1891
        # Both transports share the some connection (one can argue that we
1892
        # should return the exact same transport here, but that seems
1893
        # overkill).
1894
        self.assertEquals(t._get_connection(), r._get_connection())
1895
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1896
    def test_redirected_to_host(self):
1897
        t = self._transport('http://www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1898
        r = t._redirected_to('http://www.example.com/foo',
1899
                             'http://foo.example.com/foo/subdir')
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1900
        self.assertIsInstance(r, type(t))
1901
1902
    def test_redirected_to_same_host_sibling_protocol(self):
1903
        t = self._transport('http://www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1904
        r = t._redirected_to('http://www.example.com/foo',
1905
                             'https://www.example.com/foo')
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1906
        self.assertIsInstance(r, type(t))
1907
1908
    def test_redirected_to_same_host_different_protocol(self):
1909
        t = self._transport('http://www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1910
        r = t._redirected_to('http://www.example.com/foo',
1911
                             'ftp://www.example.com/foo')
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1912
        self.assertNotEquals(type(r), type(t))
1913
1914
    def test_redirected_to_different_host_same_user(self):
1915
        t = self._transport('http://joe@www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1916
        r = t._redirected_to('http://www.example.com/foo',
1917
                             'https://foo.example.com/foo')
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1918
        self.assertIsInstance(r, type(t))
1919
        self.assertEquals(t._user, r._user)
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1920
1921
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1922
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1923
    """Request handler for a unique and pre-defined request.
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1924
1925
    The only thing we care about here is how many bytes travel on the wire. But
1926
    since we want to measure it for a real http client, we have to send it
1927
    correct responses.
1928
1929
    We expect to receive a *single* request nothing more (and we won't even
1930
    check what request it is, we just measure the bytes read until an empty
1931
    line.
1932
    """
1933
4731.2.3 by Vincent Ladeuil
Reduce the leaking http tests from ~200 to ~5.
1934
    def _handle_one_request(self):
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1935
        tcs = self.server.test_case_server
1936
        requestline = self.rfile.readline()
1937
        headers = self.MessageClass(self.rfile, 0)
1938
        # We just read: the request, the headers, an empty line indicating the
1939
        # end of the headers.
1940
        bytes_read = len(requestline)
1941
        for line in headers.headers:
1942
            bytes_read += len(line)
1943
        bytes_read += len('\r\n')
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1944
        if requestline.startswith('POST'):
1945
            # The body should be a single line (or we don't know where it ends
1946
            # and we don't want to issue a blocking read)
1947
            body = self.rfile.readline()
1948
            bytes_read += len(body)
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1949
        tcs.bytes_read = bytes_read
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1950
1951
        # We set the bytes written *before* issuing the write, the client is
1952
        # supposed to consume every produced byte *before* checking that value.
3945.1.7 by Vincent Ladeuil
Test against https.
1953
1954
        # Doing the oppposite may lead to test failure: we may be interrupted
1955
        # after the write but before updating the value. The client can then
1956
        # continue and read the value *before* we can update it. And yes,
1957
        # this has been observed -- vila 20090129
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1958
        tcs.bytes_written = len(tcs.canned_response)
1959
        self.wfile.write(tcs.canned_response)
1960
1961
1962
class ActivityServerMixin(object):
1963
1964
    def __init__(self, protocol_version):
1965
        super(ActivityServerMixin, self).__init__(
1966
            request_handler=PredefinedRequestHandler,
1967
            protocol_version=protocol_version)
1968
        # Bytes read and written by the server
1969
        self.bytes_read = 0
1970
        self.bytes_written = 0
1971
        self.canned_response = None
1972
1973
1974
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
1975
    pass
1976
1977
1978
if tests.HTTPSServerFeature.available():
1979
    from bzrlib.tests import https_server
1980
    class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1981
        pass
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1982
1983
1984
class TestActivity(tests.TestCase):
1985
    """Test socket activity reporting.
1986
1987
    We use a special purpose server to control the bytes sent and received and
1988
    be able to predict the activity on the client socket.
1989
    """
1990
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1991
    def setUp(self):
1992
        tests.TestCase.setUp(self)
1993
        self.server = self._activity_server(self._protocol_version)
1994
        self.server.setUp()
1995
        self.activities = {}
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1996
        def report_activity(t, bytes, direction):
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1997
            count = self.activities.get(direction, 0)
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1998
            count += bytes
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1999
            self.activities[direction] = count
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
2000
2001
        # We override at class level because constructors may propagate the
2002
        # bound method and render instance overriding ineffective (an
4031.3.1 by Frank Aspell
Fixing various typos
2003
        # alternative would be to define a specific ui factory instead...)
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
2004
        self.orig_report_activity = self._transport._report_activity
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
2005
        self._transport._report_activity = report_activity
2006
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
2007
    def tearDown(self):
2008
        self._transport._report_activity = self.orig_report_activity
2009
        self.server.tearDown()
2010
        tests.TestCase.tearDown(self)
2011
2012
    def get_transport(self):
2013
        return self._transport(self.server.get_url())
2014
2015
    def assertActivitiesMatch(self):
2016
        self.assertEqual(self.server.bytes_read,
2017
                         self.activities.get('write', 0), 'written bytes')
2018
        self.assertEqual(self.server.bytes_written,
2019
                         self.activities.get('read', 0), 'read bytes')
2020
2021
    def test_get(self):
2022
        self.server.canned_response = '''HTTP/1.1 200 OK\r
2023
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2024
Server: Apache/2.0.54 (Fedora)\r
2025
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2026
ETag: "56691-23-38e9ae00"\r
2027
Accept-Ranges: bytes\r
2028
Content-Length: 35\r
2029
Connection: close\r
2030
Content-Type: text/plain; charset=UTF-8\r
2031
\r
2032
Bazaar-NG meta directory, format 1
2033
'''
2034
        t = self.get_transport()
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
2035
        self.assertEqual('Bazaar-NG meta directory, format 1\n',
2036
                         t.get('foo/bar').read())
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
2037
        self.assertActivitiesMatch()
2038
2039
    def test_has(self):
2040
        self.server.canned_response = '''HTTP/1.1 200 OK\r
2041
Server: SimpleHTTP/0.6 Python/2.5.2\r
2042
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2043
Content-type: application/octet-stream\r
2044
Content-Length: 20\r
2045
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2046
\r
2047
'''
2048
        t = self.get_transport()
2049
        self.assertTrue(t.has('foo/bar'))
2050
        self.assertActivitiesMatch()
2051
2052
    def test_readv(self):
2053
        self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
2054
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2055
Server: Apache/2.0.54 (Fedora)\r
2056
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2057
ETag: "238a3c-16ec2-805c5540"\r
2058
Accept-Ranges: bytes\r
2059
Content-Length: 1534\r
2060
Connection: close\r
2061
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2062
\r
2063
\r
2064
--418470f848b63279b\r
2065
Content-type: text/plain; charset=UTF-8\r
2066
Content-range: bytes 0-254/93890\r
2067
\r
2068
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2069
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2070
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2071
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2072
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2073
\r
2074
--418470f848b63279b\r
2075
Content-type: text/plain; charset=UTF-8\r
2076
Content-range: bytes 1000-2049/93890\r
2077
\r
2078
40-fd4ec249b6b139ab
2079
mbp@sourcefrog.net-20050311063625-07858525021f270b
2080
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2081
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2082
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2083
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2084
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2085
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2086
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2087
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2088
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2089
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2090
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2091
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2092
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2093
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2094
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2095
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2096
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2097
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2098
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2099
mbp@source\r
2100
--418470f848b63279b--\r
2101
'''
2102
        t = self.get_transport()
2103
        # Remember that the request is ignored and that the ranges below
2104
        # doesn't have to match the canned response.
2105
        l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2106
        self.assertEqual(2, len(l))
2107
        self.assertActivitiesMatch()
2108
2109
    def test_post(self):
2110
        self.server.canned_response = '''HTTP/1.1 200 OK\r
2111
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2112
Server: Apache/2.0.54 (Fedora)\r
2113
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2114
ETag: "56691-23-38e9ae00"\r
2115
Accept-Ranges: bytes\r
2116
Content-Length: 35\r
2117
Connection: close\r
2118
Content-Type: text/plain; charset=UTF-8\r
2119
\r
2120
lalala whatever as long as itsssss
2121
'''
2122
        t = self.get_transport()
2123
        # We must send a single line of body bytes, see
4731.2.3 by Vincent Ladeuil
Reduce the leaking http tests from ~200 to ~5.
2124
        # PredefinedRequestHandler._handle_one_request
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
2125
        code, f = t._post('abc def end-of-body\n')
2126
        self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2127
        self.assertActivitiesMatch()