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