~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

(jelmer) Use the absolute_import feature everywhere in bzrlib,
 and add a source test to make sure it's used everywhere. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
23
23
# TODO: Should be renamed to bzrlib.transport.http.tests?
24
24
# TODO: What about renaming to bzrlib.tests.transport.http ?
25
25
 
26
 
from cStringIO import StringIO
27
26
import httplib
28
 
import os
29
 
import select
30
27
import SimpleHTTPServer
31
28
import socket
32
29
import sys
35
32
import bzrlib
36
33
from bzrlib import (
37
34
    bzrdir,
 
35
    cethread,
38
36
    config,
 
37
    debug,
39
38
    errors,
40
39
    osutils,
41
40
    remote as _mod_remote,
42
41
    tests,
 
42
    trace,
43
43
    transport,
44
44
    ui,
45
 
    urlutils,
46
 
    )
47
 
from bzrlib.symbol_versioning import (
48
 
    deprecated_in,
49
45
    )
50
46
from bzrlib.tests import (
 
47
    features,
51
48
    http_server,
52
49
    http_utils,
 
50
    test_server,
 
51
    )
 
52
from bzrlib.tests.scenarios import (
 
53
    load_tests_apply_scenarios,
 
54
    multiply_scenarios,
53
55
    )
54
56
from bzrlib.transport import (
55
57
    http,
61
63
    )
62
64
 
63
65
 
64
 
try:
 
66
if features.pycurl.available():
65
67
    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."""
73
 
    result = loader.suiteClass()
74
 
 
75
 
    # one for each transport implementation
76
 
    t_tests, remaining_tests = tests.split_suite_by_condition(
77
 
        standard_tests, tests.condition_isinstance((
78
 
                TestHttpTransportRegistration,
79
 
                TestHttpTransportUrls,
80
 
                Test_redirected_to,
81
 
                )))
 
68
 
 
69
 
 
70
load_tests = load_tests_apply_scenarios
 
71
 
 
72
 
 
73
def vary_by_http_client_implementation():
 
74
    """Test the two libraries we can use, pycurl and urllib."""
82
75
    transport_scenarios = [
83
76
        ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
84
77
                        _server=http_server.HttpServer_urllib,
85
 
                        _qualified_prefix='http+urllib',)),
 
78
                        _url_protocol='http+urllib',)),
86
79
        ]
87
 
    if pycurl_present:
 
80
    if features.pycurl.available():
88
81
        transport_scenarios.append(
89
82
            ('pycurl', dict(_transport=PyCurlTransport,
90
83
                            _server=http_server.HttpServer_PyCurl,
91
 
                            _qualified_prefix='http+pycurl',)))
92
 
    tests.multiply_tests(t_tests, transport_scenarios, result)
93
 
 
94
 
    # each implementation tested with each HTTP version
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
 
            ]
112
 
    tp_scenarios = tests.multiply_scenarios(transport_scenarios,
113
 
                                            protocol_scenarios)
114
 
    tests.multiply_tests(tp_tests, tp_scenarios, result)
115
 
 
116
 
    # proxy auth: each auth scheme on all http versions on all implementations.
117
 
    tppa_tests, remaining_tests = tests.split_suite_by_condition(
118
 
        remaining_tests, tests.condition_isinstance((
119
 
                TestProxyAuth,
120
 
                )))
121
 
    proxy_auth_scheme_scenarios = [
122
 
        ('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
123
 
        ('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
124
 
        ('basicdigest',
125
 
         dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
84
                            _url_protocol='http+pycurl',)))
 
85
    return transport_scenarios
 
86
 
 
87
 
 
88
def vary_by_http_protocol_version():
 
89
    """Test on http/1.0 and 1.1"""
 
90
    return [
 
91
        ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
 
92
        ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
126
93
        ]
127
 
    tppa_scenarios = tests.multiply_scenarios(tp_scenarios,
128
 
                                              proxy_auth_scheme_scenarios)
129
 
    tests.multiply_tests(tppa_tests, tppa_scenarios, result)
130
 
 
131
 
    # auth: each auth scheme on all http versions on all implementations.
132
 
    tpa_tests, remaining_tests = tests.split_suite_by_condition(
133
 
        remaining_tests, tests.condition_isinstance((
134
 
                TestAuth,
135
 
                )))
136
 
    auth_scheme_scenarios = [
 
94
 
 
95
 
 
96
def vary_by_http_auth_scheme():
 
97
    scenarios = [
137
98
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
138
99
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
139
100
        ('basicdigest',
140
 
         dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
141
 
        ]
142
 
    tpa_scenarios = tests.multiply_scenarios(tp_scenarios,
143
 
                                             auth_scheme_scenarios)
144
 
    tests.multiply_tests(tpa_tests, tpa_scenarios, result)
145
 
 
146
 
    # activity: on all http[s] versions on all implementations
147
 
    tpact_tests, remaining_tests = tests.split_suite_by_condition(
148
 
        remaining_tests, tests.condition_isinstance((
149
 
                TestActivity,
150
 
                )))
 
101
            dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
 
102
        ]
 
103
    # Add some attributes common to all scenarios
 
104
    for scenario_id, scenario_dict in scenarios:
 
105
        scenario_dict.update(_auth_header='Authorization',
 
106
                             _username_prompt_prefix='',
 
107
                             _password_prompt_prefix='')
 
108
    return scenarios
 
109
 
 
110
 
 
111
def vary_by_http_proxy_auth_scheme():
 
112
    scenarios = [
 
113
        ('proxy-basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
 
114
        ('proxy-digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
 
115
        ('proxy-basicdigest',
 
116
            dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
117
        ]
 
118
    # Add some attributes common to all scenarios
 
119
    for scenario_id, scenario_dict in scenarios:
 
120
        scenario_dict.update(_auth_header='Proxy-Authorization',
 
121
                             _username_prompt_prefix='Proxy ',
 
122
                             _password_prompt_prefix='Proxy ')
 
123
    return scenarios
 
124
 
 
125
 
 
126
def vary_by_http_activity():
151
127
    activity_scenarios = [
152
128
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
153
 
                             _transport=_urllib.HttpTransport_urllib,)),
 
129
                            _transport=_urllib.HttpTransport_urllib,)),
154
130
        ]
155
 
    if tests.HTTPSServerFeature.available():
 
131
    if features.HTTPSServerFeature.available():
156
132
        activity_scenarios.append(
157
133
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
158
 
                                  _transport=_urllib.HttpTransport_urllib,)),)
159
 
    if pycurl_present:
 
134
                                _transport=_urllib.HttpTransport_urllib,)),)
 
135
    if features.pycurl.available():
160
136
        activity_scenarios.append(
161
137
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
162
 
                                 _transport=PyCurlTransport,)),)
163
 
        if tests.HTTPSServerFeature.available():
 
138
                                _transport=PyCurlTransport,)),)
 
139
        if features.HTTPSServerFeature.available():
164
140
            from bzrlib.tests import (
165
141
                ssl_certs,
166
142
                )
177
153
 
178
154
            activity_scenarios.append(
179
155
                ('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
180
 
                                      _transport=HTTPS_pycurl_transport,)),)
181
 
 
182
 
    tpact_scenarios = tests.multiply_scenarios(activity_scenarios,
183
 
                                               protocol_scenarios)
184
 
    tests.multiply_tests(tpact_tests, tpact_scenarios, result)
185
 
 
186
 
    # No parametrization for the remaining tests
187
 
    result.addTests(remaining_tests)
188
 
 
189
 
    return result
 
156
                                    _transport=HTTPS_pycurl_transport,)),)
 
157
    return activity_scenarios
190
158
 
191
159
 
192
160
class FakeManager(object):
220
188
    def get_url(self):
221
189
        return '%s://%s:%s/' % (self.scheme, self.host, self.port)
222
190
 
223
 
    def setUp(self):
 
191
    def start_server(self):
224
192
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
225
193
        self._sock.bind(('127.0.0.1', 0))
226
194
        self.host, self.port = self._sock.getsockname()
227
195
        self._ready = threading.Event()
228
 
        self._thread = threading.Thread(target=self._accept_read_and_reply)
229
 
        self._thread.setDaemon(True)
 
196
        self._thread = test_server.TestThread(
 
197
            sync_event=self._ready, target=self._accept_read_and_reply)
230
198
        self._thread.start()
231
 
        self._ready.wait(5)
 
199
        if 'threads' in tests.selftest_debug_flags:
 
200
            sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
 
201
        self._ready.wait()
232
202
 
233
203
    def _accept_read_and_reply(self):
234
204
        self._sock.listen(1)
235
205
        self._ready.set()
236
 
        self._sock.settimeout(5)
237
 
        try:
238
 
            conn, address = self._sock.accept()
239
 
            # On win32, the accepted connection will be non-blocking to start
240
 
            # with because we're using settimeout.
241
 
            conn.setblocking(True)
 
206
        conn, address = self._sock.accept()
 
207
        if self._expect_body_tail is not None:
242
208
            while not self.received_bytes.endswith(self._expect_body_tail):
243
209
                self.received_bytes += conn.recv(4096)
244
210
            conn.sendall('HTTP/1.1 200 OK\r\n')
245
 
        except socket.timeout:
246
 
            # Make sure the client isn't stuck waiting for us to e.g. accept.
 
211
        try:
247
212
            self._sock.close()
248
213
        except socket.error:
249
214
            # The client may have already closed the socket.
250
215
            pass
251
216
 
252
 
    def tearDown(self):
 
217
    def stop_server(self):
253
218
        try:
254
 
            self._sock.close()
 
219
            # Issue a fake connection to wake up the server and allow it to
 
220
            # finish quickly
 
221
            fake_conn = osutils.connect_socket((self.host, self.port))
 
222
            fake_conn.close()
255
223
        except socket.error:
256
224
            # We might have already closed it.  We don't care.
257
225
            pass
258
226
        self.host = None
259
227
        self.port = None
 
228
        self._thread.join()
 
229
        if 'threads' in tests.selftest_debug_flags:
 
230
            sys.stderr.write('Thread  joined: %s\n' % (self._thread.ident,))
260
231
 
261
232
 
262
233
class TestAuthHeader(tests.TestCase):
269
240
 
270
241
    def test_empty_header(self):
271
242
        scheme, remainder = self.parse_header('')
272
 
        self.assertEquals('', scheme)
 
243
        self.assertEqual('', scheme)
273
244
        self.assertIs(None, remainder)
274
245
 
275
246
    def test_negotiate_header(self):
276
247
        scheme, remainder = self.parse_header('Negotiate')
277
 
        self.assertEquals('negotiate', scheme)
 
248
        self.assertEqual('negotiate', scheme)
278
249
        self.assertIs(None, remainder)
279
250
 
280
251
    def test_basic_header(self):
281
252
        scheme, remainder = self.parse_header(
282
253
            'Basic realm="Thou should not pass"')
283
 
        self.assertEquals('basic', scheme)
284
 
        self.assertEquals('realm="Thou should not pass"', remainder)
 
254
        self.assertEqual('basic', scheme)
 
255
        self.assertEqual('realm="Thou should not pass"', remainder)
285
256
 
286
257
    def test_basic_extract_realm(self):
287
258
        scheme, remainder = self.parse_header(
289
260
            _urllib2_wrappers.BasicAuthHandler)
290
261
        match, realm = self.auth_handler.extract_realm(remainder)
291
262
        self.assertTrue(match is not None)
292
 
        self.assertEquals('Thou should not pass', realm)
 
263
        self.assertEqual('Thou should not pass', realm)
293
264
 
294
265
    def test_digest_header(self):
295
266
        scheme, remainder = self.parse_header(
296
267
            'Digest realm="Thou should not pass"')
297
 
        self.assertEquals('digest', scheme)
298
 
        self.assertEquals('realm="Thou should not pass"', remainder)
 
268
        self.assertEqual('digest', scheme)
 
269
        self.assertEqual('realm="Thou should not pass"', remainder)
 
270
 
 
271
 
 
272
class TestHTTPRangeParsing(tests.TestCase):
 
273
 
 
274
    def setUp(self):
 
275
        super(TestHTTPRangeParsing, self).setUp()
 
276
        # We focus on range  parsing here and ignore everything else
 
277
        class RequestHandler(http_server.TestingHTTPRequestHandler):
 
278
            def setup(self): pass
 
279
            def handle(self): pass
 
280
            def finish(self): pass
 
281
 
 
282
        self.req_handler = RequestHandler(None, None, None)
 
283
 
 
284
    def assertRanges(self, ranges, header, file_size):
 
285
        self.assertEquals(ranges,
 
286
                          self.req_handler._parse_ranges(header, file_size))
 
287
 
 
288
    def test_simple_range(self):
 
289
        self.assertRanges([(0,2)], 'bytes=0-2', 12)
 
290
 
 
291
    def test_tail(self):
 
292
        self.assertRanges([(8, 11)], 'bytes=-4', 12)
 
293
 
 
294
    def test_tail_bigger_than_file(self):
 
295
        self.assertRanges([(0, 11)], 'bytes=-99', 12)
 
296
 
 
297
    def test_range_without_end(self):
 
298
        self.assertRanges([(4, 11)], 'bytes=4-', 12)
 
299
 
 
300
    def test_invalid_ranges(self):
 
301
        self.assertRanges(None, 'bytes=12-22', 12)
 
302
        self.assertRanges(None, 'bytes=1-3,12-22', 12)
 
303
        self.assertRanges(None, 'bytes=-', 12)
299
304
 
300
305
 
301
306
class TestHTTPServer(tests.TestCase):
306
311
 
307
312
            protocol_version = 'HTTP/0.1'
308
313
 
309
 
        server = http_server.HttpServer(BogusRequestHandler)
310
 
        try:
311
 
            self.assertRaises(httplib.UnknownProtocol, server.setUp)
312
 
        except:
313
 
            server.tearDown()
314
 
            self.fail('HTTP Server creation did not raise UnknownProtocol')
 
314
        self.assertRaises(httplib.UnknownProtocol,
 
315
                          http_server.HttpServer, BogusRequestHandler)
315
316
 
316
317
    def test_force_invalid_protocol(self):
317
 
        server = http_server.HttpServer(protocol_version='HTTP/0.1')
318
 
        try:
319
 
            self.assertRaises(httplib.UnknownProtocol, server.setUp)
320
 
        except:
321
 
            server.tearDown()
322
 
            self.fail('HTTP Server creation did not raise UnknownProtocol')
 
318
        self.assertRaises(httplib.UnknownProtocol,
 
319
                          http_server.HttpServer, protocol_version='HTTP/0.1')
323
320
 
324
321
    def test_server_start_and_stop(self):
325
322
        server = http_server.HttpServer()
326
 
        server.setUp()
327
 
        try:
328
 
            self.assertTrue(server._http_running)
329
 
        finally:
330
 
            server.tearDown()
331
 
        self.assertFalse(server._http_running)
 
323
        self.addCleanup(server.stop_server)
 
324
        server.start_server()
 
325
        self.assertTrue(server.server is not None)
 
326
        self.assertTrue(server.server.serving is not None)
 
327
        self.assertTrue(server.server.serving)
332
328
 
333
329
    def test_create_http_server_one_zero(self):
334
330
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
337
333
 
338
334
        server = http_server.HttpServer(RequestHandlerOneZero)
339
335
        self.start_server(server)
340
 
        self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
 
336
        self.assertIsInstance(server.server, http_server.TestingHTTPServer)
341
337
 
342
338
    def test_create_http_server_one_one(self):
343
339
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
346
342
 
347
343
        server = http_server.HttpServer(RequestHandlerOneOne)
348
344
        self.start_server(server)
349
 
        self.assertIsInstance(server._httpd,
 
345
        self.assertIsInstance(server.server,
350
346
                              http_server.TestingThreadingHTTPServer)
351
347
 
352
348
    def test_create_http_server_force_one_one(self):
357
353
        server = http_server.HttpServer(RequestHandlerOneZero,
358
354
                                        protocol_version='HTTP/1.1')
359
355
        self.start_server(server)
360
 
        self.assertIsInstance(server._httpd,
 
356
        self.assertIsInstance(server.server,
361
357
                              http_server.TestingThreadingHTTPServer)
362
358
 
363
359
    def test_create_http_server_force_one_zero(self):
368
364
        server = http_server.HttpServer(RequestHandlerOneOne,
369
365
                                        protocol_version='HTTP/1.0')
370
366
        self.start_server(server)
371
 
        self.assertIsInstance(server._httpd,
 
367
        self.assertIsInstance(server.server,
372
368
                              http_server.TestingHTTPServer)
373
369
 
374
370
 
376
372
    """Test case to inherit from if pycurl is present"""
377
373
 
378
374
    def _get_pycurl_maybe(self):
379
 
        try:
380
 
            from bzrlib.transport.http._pycurl import PyCurlTransport
381
 
            return PyCurlTransport
382
 
        except errors.DependencyNotPresent:
383
 
            raise tests.TestSkipped('pycurl not present')
 
375
        self.requireFeature(features.pycurl)
 
376
        return PyCurlTransport
384
377
 
385
378
    _transport = property(_get_pycurl_maybe)
386
379
 
393
386
    def test_url_parsing(self):
394
387
        f = FakeManager()
395
388
        url = http.extract_auth('http://example.com', f)
396
 
        self.assertEquals('http://example.com', url)
397
 
        self.assertEquals(0, len(f.credentials))
 
389
        self.assertEqual('http://example.com', url)
 
390
        self.assertEqual(0, len(f.credentials))
398
391
        url = http.extract_auth(
399
 
            'http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
400
 
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
401
 
        self.assertEquals(1, len(f.credentials))
402
 
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
403
 
                          f.credentials[0])
 
392
            'http://user:pass@example.com/bzr/bzr.dev', f)
 
393
        self.assertEqual('http://example.com/bzr/bzr.dev', url)
 
394
        self.assertEqual(1, len(f.credentials))
 
395
        self.assertEqual([None, 'example.com', 'user', 'pass'],
 
396
                         f.credentials[0])
404
397
 
405
398
 
406
399
class TestHttpTransportUrls(tests.TestCase):
407
400
    """Test the http urls."""
408
401
 
 
402
    scenarios = vary_by_http_client_implementation()
 
403
 
409
404
    def test_abs_url(self):
410
405
        """Construction of absolute http URLs"""
411
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
406
        t = self._transport('http://example.com/bzr/bzr.dev/')
412
407
        eq = self.assertEqualDiff
413
 
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
414
 
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
415
 
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
 
408
        eq(t.abspath('.'), 'http://example.com/bzr/bzr.dev')
 
409
        eq(t.abspath('foo/bar'), 'http://example.com/bzr/bzr.dev/foo/bar')
 
410
        eq(t.abspath('.bzr'), 'http://example.com/bzr/bzr.dev/.bzr')
416
411
        eq(t.abspath('.bzr/1//2/./3'),
417
 
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
 
412
           'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
418
413
 
419
414
    def test_invalid_http_urls(self):
420
415
        """Trap invalid construction of urls"""
421
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
416
        self._transport('http://example.com/bzr/bzr.dev/')
422
417
        self.assertRaises(errors.InvalidURL,
423
418
                          self._transport,
424
 
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
 
419
                          'http://http://example.com/bzr/bzr.dev/')
425
420
 
426
421
    def test_http_root_urls(self):
427
422
        """Construction of URLs from server root"""
428
 
        t = self._transport('http://bzr.ozlabs.org/')
 
423
        t = self._transport('http://example.com/')
429
424
        eq = self.assertEqualDiff
430
425
        eq(t.abspath('.bzr/tree-version'),
431
 
           'http://bzr.ozlabs.org/.bzr/tree-version')
 
426
           'http://example.com/.bzr/tree-version')
432
427
 
433
428
    def test_http_impl_urls(self):
434
429
        """There are servers which ask for particular clients to connect"""
435
430
        server = self._server()
436
 
        server.setUp()
 
431
        server.start_server()
437
432
        try:
438
433
            url = server.get_url()
439
 
            self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
 
434
            self.assertTrue(url.startswith('%s://' % self._url_protocol))
440
435
        finally:
441
 
            server.tearDown()
 
436
            server.stop_server()
442
437
 
443
438
 
444
439
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
453
448
        https by supplying a fake version_info that do not
454
449
        support it.
455
450
        """
456
 
        try:
457
 
            import pycurl
458
 
        except ImportError:
459
 
            raise tests.TestSkipped('pycurl not present')
 
451
        self.requireFeature(features.pycurl)
 
452
        # Import the module locally now that we now it's available.
 
453
        pycurl = features.pycurl.module
460
454
 
461
 
        version_info_orig = pycurl.version_info
462
 
        try:
463
 
            # Now that we have pycurl imported, we can fake its version_info
464
 
            # This was taken from a windows pycurl without SSL
465
 
            # (thanks to bialix)
466
 
            pycurl.version_info = lambda : (2,
467
 
                                            '7.13.2',
468
 
                                            462082,
469
 
                                            'i386-pc-win32',
470
 
                                            2576,
471
 
                                            None,
472
 
                                            0,
473
 
                                            None,
474
 
                                            ('ftp', 'gopher', 'telnet',
475
 
                                             'dict', 'ldap', 'http', 'file'),
476
 
                                            None,
477
 
                                            0,
478
 
                                            None)
479
 
            self.assertRaises(errors.DependencyNotPresent, self._transport,
480
 
                              'https://launchpad.net')
481
 
        finally:
482
 
            # Restore the right function
483
 
            pycurl.version_info = version_info_orig
 
455
        self.overrideAttr(pycurl, 'version_info',
 
456
                          # Fake the pycurl version_info This was taken from
 
457
                          # a windows pycurl without SSL (thanks to bialix)
 
458
                          lambda : (2,
 
459
                                    '7.13.2',
 
460
                                    462082,
 
461
                                    'i386-pc-win32',
 
462
                                    2576,
 
463
                                    None,
 
464
                                    0,
 
465
                                    None,
 
466
                                    ('ftp', 'gopher', 'telnet',
 
467
                                     'dict', 'ldap', 'http', 'file'),
 
468
                                    None,
 
469
                                    0,
 
470
                                    None))
 
471
        self.assertRaises(errors.DependencyNotPresent, self._transport,
 
472
                          'https://launchpad.net')
484
473
 
485
474
 
486
475
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
487
476
    """Test the http connections."""
488
477
 
 
478
    scenarios = multiply_scenarios(
 
479
        vary_by_http_client_implementation(),
 
480
        vary_by_http_protocol_version(),
 
481
        )
 
482
 
489
483
    def setUp(self):
490
484
        http_utils.TestCaseWithWebserver.setUp(self)
491
485
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
493
487
 
494
488
    def test_http_has(self):
495
489
        server = self.get_readonly_server()
496
 
        t = self._transport(server.get_url())
 
490
        t = self.get_readonly_transport()
497
491
        self.assertEqual(t.has('foo/bar'), True)
498
492
        self.assertEqual(len(server.logs), 1)
499
493
        self.assertContainsRe(server.logs[0],
501
495
 
502
496
    def test_http_has_not_found(self):
503
497
        server = self.get_readonly_server()
504
 
        t = self._transport(server.get_url())
 
498
        t = self.get_readonly_transport()
505
499
        self.assertEqual(t.has('not-found'), False)
506
500
        self.assertContainsRe(server.logs[1],
507
501
            r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
508
502
 
509
503
    def test_http_get(self):
510
504
        server = self.get_readonly_server()
511
 
        t = self._transport(server.get_url())
 
505
        t = self.get_readonly_transport()
512
506
        fp = t.get('foo/bar')
513
507
        self.assertEqualDiff(
514
508
            fp.read(),
536
530
class TestHttpTransportRegistration(tests.TestCase):
537
531
    """Test registrations of various http implementations"""
538
532
 
 
533
    scenarios = vary_by_http_client_implementation()
 
534
 
539
535
    def test_http_registered(self):
540
 
        t = transport.get_transport('%s://foo.com/' % self._qualified_prefix)
 
536
        t = transport.get_transport_from_url(
 
537
            '%s://foo.com/' % self._url_protocol)
541
538
        self.assertIsInstance(t, transport.Transport)
542
539
        self.assertIsInstance(t, self._transport)
543
540
 
544
541
 
545
542
class TestPost(tests.TestCase):
546
543
 
 
544
    scenarios = multiply_scenarios(
 
545
        vary_by_http_client_implementation(),
 
546
        vary_by_http_protocol_version(),
 
547
        )
 
548
 
547
549
    def test_post_body_is_received(self):
548
550
        server = RecordingServer(expect_body_tail='end-of-body',
549
 
            scheme=self._qualified_prefix)
 
551
                                 scheme=self._url_protocol)
550
552
        self.start_server(server)
551
553
        url = server.get_url()
552
 
        http_transport = self._transport(url)
 
554
        # FIXME: needs a cleanup -- vila 20100611
 
555
        http_transport = transport.get_transport_from_url(url)
553
556
        code, response = http_transport._post('abc def end-of-body')
554
557
        self.assertTrue(
555
558
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
556
559
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
 
560
        self.assertTrue('content-type: application/octet-stream\r'
 
561
                        in server.received_bytes.lower())
557
562
        # The transport should not be assuming that the server can accept
558
563
        # chunked encoding the first time it connects, because HTTP/1.1, so we
559
564
        # check for the literal string.
595
600
    Daughter classes are expected to override _req_handler_class
596
601
    """
597
602
 
 
603
    scenarios = multiply_scenarios(
 
604
        vary_by_http_client_implementation(),
 
605
        vary_by_http_protocol_version(),
 
606
        )
 
607
 
598
608
    # Provide a useful default
599
609
    _req_handler_class = http_server.TestingHTTPRequestHandler
600
610
 
601
611
    def create_transport_readonly_server(self):
602
 
        return http_server.HttpServer(self._req_handler_class,
603
 
                                      protocol_version=self._protocol_version)
 
612
        server = http_server.HttpServer(self._req_handler_class,
 
613
                                        protocol_version=self._protocol_version)
 
614
        server._url_protocol = self._url_protocol
 
615
        return server
604
616
 
605
617
    def _testing_pycurl(self):
606
 
        return pycurl_present and self._transport == PyCurlTransport
 
618
        # TODO: This is duplicated for lots of the classes in this file
 
619
        return (features.pycurl.available()
 
620
                and self._transport == PyCurlTransport)
607
621
 
608
622
 
609
623
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
610
624
    """Whatever request comes in, close the connection"""
611
625
 
612
 
    def handle_one_request(self):
 
626
    def _handle_one_request(self):
613
627
        """Handle a single HTTP request, by abruptly closing the connection"""
614
628
        self.close_connection = 1
615
629
 
620
634
    _req_handler_class = WallRequestHandler
621
635
 
622
636
    def test_http_has(self):
623
 
        server = self.get_readonly_server()
624
 
        t = self._transport(server.get_url())
 
637
        t = self.get_readonly_transport()
625
638
        # Unfortunately httplib (see HTTPResponse._read_status
626
639
        # for details) make no distinction between a closed
627
640
        # socket and badly formatted status line, so we can't
633
646
                          t.has, 'foo/bar')
634
647
 
635
648
    def test_http_get(self):
636
 
        server = self.get_readonly_server()
637
 
        t = self._transport(server.get_url())
 
649
        t = self.get_readonly_transport()
638
650
        self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
639
651
                           errors.InvalidHttpResponse),
640
652
                          t.get, 'foo/bar')
657
669
    _req_handler_class = BadStatusRequestHandler
658
670
 
659
671
    def test_http_has(self):
660
 
        server = self.get_readonly_server()
661
 
        t = self._transport(server.get_url())
 
672
        t = self.get_readonly_transport()
662
673
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
663
674
 
664
675
    def test_http_get(self):
665
 
        server = self.get_readonly_server()
666
 
        t = self._transport(server.get_url())
 
676
        t = self.get_readonly_transport()
667
677
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
668
678
 
669
679
 
674
684
        """Fakes handling a single HTTP request, returns a bad status"""
675
685
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
676
686
        self.wfile.write("Invalid status line\r\n")
 
687
        # If we don't close the connection pycurl will hang. Since this is a
 
688
        # stress test we don't *have* to respect the protocol, but we don't
 
689
        # have to sabotage it too much either.
 
690
        self.close_connection = True
677
691
        return False
678
692
 
679
693
 
685
699
 
686
700
    _req_handler_class = InvalidStatusRequestHandler
687
701
 
688
 
    def test_http_has(self):
689
 
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
690
 
            raise tests.KnownFailure(
691
 
                'pycurl hangs if the server send back garbage')
692
 
        super(TestInvalidStatusServer, self).test_http_has()
693
 
 
694
 
    def test_http_get(self):
695
 
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
696
 
            raise tests.KnownFailure(
697
 
                'pycurl hangs if the server send back garbage')
698
 
        super(TestInvalidStatusServer, self).test_http_get()
699
 
 
700
702
 
701
703
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
702
704
    """Whatever request comes in, returns a bad protocol version"""
718
720
    _req_handler_class = BadProtocolRequestHandler
719
721
 
720
722
    def setUp(self):
721
 
        if pycurl_present and self._transport == PyCurlTransport:
 
723
        if self._testing_pycurl():
722
724
            raise tests.TestNotApplicable(
723
725
                "pycurl doesn't check the protocol version")
724
726
        super(TestBadProtocolServer, self).setUp()
725
727
 
726
728
    def test_http_has(self):
727
 
        server = self.get_readonly_server()
728
 
        t = self._transport(server.get_url())
 
729
        t = self.get_readonly_transport()
729
730
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
730
731
 
731
732
    def test_http_get(self):
732
 
        server = self.get_readonly_server()
733
 
        t = self._transport(server.get_url())
 
733
        t = self.get_readonly_transport()
734
734
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
735
735
 
736
736
 
750
750
    _req_handler_class = ForbiddenRequestHandler
751
751
 
752
752
    def test_http_has(self):
753
 
        server = self.get_readonly_server()
754
 
        t = self._transport(server.get_url())
 
753
        t = self.get_readonly_transport()
755
754
        self.assertRaises(errors.TransportError, t.has, 'foo/bar')
756
755
 
757
756
    def test_http_get(self):
758
 
        server = self.get_readonly_server()
759
 
        t = self._transport(server.get_url())
 
757
        t = self.get_readonly_transport()
760
758
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
761
759
 
762
760
 
768
766
        self.assertEqual(None, server.host)
769
767
        self.assertEqual(None, server.port)
770
768
 
771
 
    def test_setUp_and_tearDown(self):
 
769
    def test_setUp_and_stop(self):
772
770
        server = RecordingServer(expect_body_tail=None)
773
 
        server.setUp()
 
771
        server.start_server()
774
772
        try:
775
773
            self.assertNotEqual(None, server.host)
776
774
            self.assertNotEqual(None, server.port)
777
775
        finally:
778
 
            server.tearDown()
 
776
            server.stop_server()
779
777
        self.assertEqual(None, server.host)
780
778
        self.assertEqual(None, server.port)
781
779
 
801
799
        self.build_tree_contents([('a', '0123456789')],)
802
800
 
803
801
    def test_readv(self):
804
 
        server = self.get_readonly_server()
805
 
        t = self._transport(server.get_url())
 
802
        t = self.get_readonly_transport()
806
803
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
807
804
        self.assertEqual(l[0], (0, '0'))
808
805
        self.assertEqual(l[1], (1, '1'))
810
807
        self.assertEqual(l[3], (9, '9'))
811
808
 
812
809
    def test_readv_out_of_order(self):
813
 
        server = self.get_readonly_server()
814
 
        t = self._transport(server.get_url())
 
810
        t = self.get_readonly_transport()
815
811
        l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
816
812
        self.assertEqual(l[0], (1, '1'))
817
813
        self.assertEqual(l[1], (9, '9'))
819
815
        self.assertEqual(l[3], (3, '34'))
820
816
 
821
817
    def test_readv_invalid_ranges(self):
822
 
        server = self.get_readonly_server()
823
 
        t = self._transport(server.get_url())
 
818
        t = self.get_readonly_transport()
824
819
 
825
820
        # This is intentionally reading off the end of the file
826
821
        # since we are sure that it cannot get there
834
829
 
835
830
    def test_readv_multiple_get_requests(self):
836
831
        server = self.get_readonly_server()
837
 
        t = self._transport(server.get_url())
 
832
        t = self.get_readonly_transport()
838
833
        # force transport to issue multiple requests
839
834
        t._max_readv_combine = 1
840
835
        t._max_get_ranges = 1
848
843
 
849
844
    def test_readv_get_max_size(self):
850
845
        server = self.get_readonly_server()
851
 
        t = self._transport(server.get_url())
 
846
        t = self.get_readonly_transport()
852
847
        # force transport to issue multiple requests by limiting the number of
853
848
        # bytes by request. Note that this apply to coalesced offsets only, a
854
849
        # single range will keep its size even if bigger than the limit.
863
858
 
864
859
    def test_complete_readv_leave_pipe_clean(self):
865
860
        server = self.get_readonly_server()
866
 
        t = self._transport(server.get_url())
 
861
        t = self.get_readonly_transport()
867
862
        # force transport to issue multiple requests
868
863
        t._get_max_size = 2
869
 
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
864
        list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
870
865
        # The server should have issued 3 requests
871
866
        self.assertEqual(3, server.GET_request_nb)
872
867
        self.assertEqual('0123456789', t.get_bytes('a'))
874
869
 
875
870
    def test_incomplete_readv_leave_pipe_clean(self):
876
871
        server = self.get_readonly_server()
877
 
        t = self._transport(server.get_url())
 
872
        t = self.get_readonly_transport()
878
873
        # force transport to issue multiple requests
879
874
        t._get_max_size = 2
880
875
        # Don't collapse readv results into a list so that we leave unread
949
944
    def get_multiple_ranges(self, file, file_size, ranges):
950
945
        self.send_response(206)
951
946
        self.send_header('Accept-Ranges', 'bytes')
 
947
        # XXX: this is strange; the 'random' name below seems undefined and
 
948
        # yet the tests pass -- mbp 2010-10-11 bug 658773
952
949
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
953
950
        self.send_header("Content-Type",
954
951
                         "multipart/byteranges; boundary=%s" % boundary)
1016
1013
                return
1017
1014
            self.send_range_content(file, start, end - start + 1)
1018
1015
            cur += 1
1019
 
        # No final boundary
 
1016
        # Final boundary
1020
1017
        self.wfile.write(boundary_line)
1021
1018
 
1022
1019
 
1030
1027
 
1031
1028
    def test_readv_with_short_reads(self):
1032
1029
        server = self.get_readonly_server()
1033
 
        t = self._transport(server.get_url())
 
1030
        t = self.get_readonly_transport()
1034
1031
        # Force separate ranges for each offset
1035
1032
        t._bytes_to_read_before_seek = 0
1036
1033
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1051
1048
        # that mode
1052
1049
        self.assertEqual('single', t._range_hint)
1053
1050
 
 
1051
 
 
1052
class TruncatedBeforeBoundaryRequestHandler(
 
1053
    http_server.TestingHTTPRequestHandler):
 
1054
    """Truncation before a boundary, like in bug 198646"""
 
1055
 
 
1056
    _truncated_ranges = 1
 
1057
 
 
1058
    def get_multiple_ranges(self, file, file_size, ranges):
 
1059
        self.send_response(206)
 
1060
        self.send_header('Accept-Ranges', 'bytes')
 
1061
        boundary = 'tagada'
 
1062
        self.send_header('Content-Type',
 
1063
                         'multipart/byteranges; boundary=%s' % boundary)
 
1064
        boundary_line = '--%s\r\n' % boundary
 
1065
        # Calculate the Content-Length
 
1066
        content_length = 0
 
1067
        for (start, end) in ranges:
 
1068
            content_length += len(boundary_line)
 
1069
            content_length += self._header_line_length(
 
1070
                'Content-type', 'application/octet-stream')
 
1071
            content_length += self._header_line_length(
 
1072
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
 
1073
            content_length += len('\r\n') # end headers
 
1074
            content_length += end - start # + 1
 
1075
        content_length += len(boundary_line)
 
1076
        self.send_header('Content-length', content_length)
 
1077
        self.end_headers()
 
1078
 
 
1079
        # Send the multipart body
 
1080
        cur = 0
 
1081
        for (start, end) in ranges:
 
1082
            if cur + self._truncated_ranges >= len(ranges):
 
1083
                # Abruptly ends the response and close the connection
 
1084
                self.close_connection = 1
 
1085
                return
 
1086
            self.wfile.write(boundary_line)
 
1087
            self.send_header('Content-type', 'application/octet-stream')
 
1088
            self.send_header('Content-Range', 'bytes %d-%d/%d'
 
1089
                             % (start, end, file_size))
 
1090
            self.end_headers()
 
1091
            self.send_range_content(file, start, end - start + 1)
 
1092
            cur += 1
 
1093
        # Final boundary
 
1094
        self.wfile.write(boundary_line)
 
1095
 
 
1096
 
 
1097
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
 
1098
    """Tests the case of bug 198646, disconnecting before a boundary."""
 
1099
 
 
1100
    _req_handler_class = TruncatedBeforeBoundaryRequestHandler
 
1101
 
 
1102
    def setUp(self):
 
1103
        super(TestTruncatedBeforeBoundary, self).setUp()
 
1104
        self.build_tree_contents([('a', '0123456789')],)
 
1105
 
 
1106
    def test_readv_with_short_reads(self):
 
1107
        server = self.get_readonly_server()
 
1108
        t = self.get_readonly_transport()
 
1109
        # Force separate ranges for each offset
 
1110
        t._bytes_to_read_before_seek = 0
 
1111
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
 
1112
        self.assertEqual((0, '0'), ireadv.next())
 
1113
        self.assertEqual((2, '2'), ireadv.next())
 
1114
        self.assertEqual((4, '45'), ireadv.next())
 
1115
        self.assertEqual((9, '9'), ireadv.next())
 
1116
 
 
1117
 
1054
1118
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1055
1119
    """Errors out when range specifiers exceed the limit"""
1056
1120
 
1080
1144
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1081
1145
    """Tests readv requests against a server erroring out on too much ranges."""
1082
1146
 
 
1147
    scenarios = multiply_scenarios(
 
1148
        vary_by_http_client_implementation(),
 
1149
        vary_by_http_protocol_version(),
 
1150
        )
 
1151
 
1083
1152
    # Requests with more range specifiers will error out
1084
1153
    range_limit = 3
1085
1154
 
1087
1156
        return LimitedRangeHTTPServer(range_limit=self.range_limit,
1088
1157
                                      protocol_version=self._protocol_version)
1089
1158
 
1090
 
    def get_transport(self):
1091
 
        return self._transport(self.get_readonly_server().get_url())
1092
 
 
1093
1159
    def setUp(self):
1094
1160
        http_utils.TestCaseWithWebserver.setUp(self)
1095
1161
        # We need to manipulate ranges that correspond to real chunks in the
1099
1165
        self.build_tree_contents([('a', content)],)
1100
1166
 
1101
1167
    def test_few_ranges(self):
1102
 
        t = self.get_transport()
 
1168
        t = self.get_readonly_transport()
1103
1169
        l = list(t.readv('a', ((0, 4), (1024, 4), )))
1104
1170
        self.assertEqual(l[0], (0, '0000'))
1105
1171
        self.assertEqual(l[1], (1024, '0001'))
1106
1172
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1107
1173
 
1108
1174
    def test_more_ranges(self):
1109
 
        t = self.get_transport()
 
1175
        t = self.get_readonly_transport()
1110
1176
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1111
1177
        self.assertEqual(l[0], (0, '0000'))
1112
1178
        self.assertEqual(l[1], (1024, '0001'))
1123
1189
    Only the urllib implementation is tested here.
1124
1190
    """
1125
1191
 
1126
 
    def setUp(self):
1127
 
        tests.TestCase.setUp(self)
1128
 
        self._old_env = {}
1129
 
 
1130
 
    def tearDown(self):
1131
 
        self._restore_env()
1132
 
        tests.TestCase.tearDown(self)
1133
 
 
1134
 
    def _install_env(self, env):
1135
 
        for name, value in env.iteritems():
1136
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1137
 
 
1138
 
    def _restore_env(self):
1139
 
        for name, value in self._old_env.iteritems():
1140
 
            osutils.set_or_unset_env(name, value)
1141
 
 
1142
1192
    def _proxied_request(self):
1143
1193
        handler = _urllib2_wrappers.ProxyHandler()
1144
 
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
 
1194
        request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
1145
1195
        handler.set_proxy(request, 'http')
1146
1196
        return request
1147
1197
 
 
1198
    def assertEvaluateProxyBypass(self, expected, host, no_proxy):
 
1199
        handler = _urllib2_wrappers.ProxyHandler()
 
1200
        self.assertEquals(expected,
 
1201
                          handler.evaluate_proxy_bypass(host, no_proxy))
 
1202
 
1148
1203
    def test_empty_user(self):
1149
 
        self._install_env({'http_proxy': 'http://bar.com'})
 
1204
        self.overrideEnv('http_proxy', 'http://bar.com')
 
1205
        request = self._proxied_request()
 
1206
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
 
1207
 
 
1208
    def test_user_with_at(self):
 
1209
        self.overrideEnv('http_proxy',
 
1210
                         'http://username@domain:password@proxy_host:1234')
1150
1211
        request = self._proxied_request()
1151
1212
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
1152
1213
 
1153
1214
    def test_invalid_proxy(self):
1154
1215
        """A proxy env variable without scheme"""
1155
 
        self._install_env({'http_proxy': 'host:1234'})
 
1216
        self.overrideEnv('http_proxy', 'host:1234')
1156
1217
        self.assertRaises(errors.InvalidURL, self._proxied_request)
1157
1218
 
 
1219
    def test_evaluate_proxy_bypass_true(self):
 
1220
        """The host is not proxied"""
 
1221
        self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
 
1222
        self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
 
1223
 
 
1224
    def test_evaluate_proxy_bypass_false(self):
 
1225
        """The host is proxied"""
 
1226
        self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
 
1227
 
 
1228
    def test_evaluate_proxy_bypass_unknown(self):
 
1229
        """The host is not explicitly proxied"""
 
1230
        self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
 
1231
        self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
 
1232
 
 
1233
    def test_evaluate_proxy_bypass_empty_entries(self):
 
1234
        """Ignore empty entries"""
 
1235
        self.assertEvaluateProxyBypass(None, 'example.com', '')
 
1236
        self.assertEvaluateProxyBypass(None, 'example.com', ',')
 
1237
        self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
 
1238
 
1158
1239
 
1159
1240
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
1160
1241
    """Tests proxy server.
1165
1246
    to the file names).
1166
1247
    """
1167
1248
 
 
1249
    scenarios = multiply_scenarios(
 
1250
        vary_by_http_client_implementation(),
 
1251
        vary_by_http_protocol_version(),
 
1252
        )
 
1253
 
1168
1254
    # FIXME: We don't have an https server available, so we don't
1169
 
    # test https connections.
 
1255
    # test https connections. --vila toolongago
1170
1256
 
1171
1257
    def setUp(self):
1172
1258
        super(TestProxyHttpServer, self).setUp()
 
1259
        self.transport_secondary_server = http_utils.ProxyServer
1173
1260
        self.build_tree_contents([('foo', 'contents of foo\n'),
1174
1261
                                  ('foo-proxied', 'proxied contents of foo\n')])
1175
1262
        # Let's setup some attributes for tests
1176
 
        self.server = self.get_readonly_server()
1177
 
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
 
1263
        server = self.get_readonly_server()
 
1264
        self.server_host_port = '%s:%d' % (server.host, server.port)
1178
1265
        if self._testing_pycurl():
1179
1266
            # Oh my ! pycurl does not check for the port as part of
1180
1267
            # no_proxy :-( So we just test the host part
1181
 
            self.no_proxy_host = self.server.host
 
1268
            self.no_proxy_host = server.host
1182
1269
        else:
1183
 
            self.no_proxy_host = self.proxy_address
 
1270
            self.no_proxy_host = self.server_host_port
1184
1271
        # The secondary server is the proxy
1185
 
        self.proxy = self.get_secondary_server()
1186
 
        self.proxy_url = self.proxy.get_url()
1187
 
        self._old_env = {}
 
1272
        self.proxy_url = self.get_secondary_url()
1188
1273
 
1189
1274
    def _testing_pycurl(self):
1190
 
        return pycurl_present and self._transport == PyCurlTransport
1191
 
 
1192
 
    def create_transport_secondary_server(self):
1193
 
        """Creates an http server that will serve files with
1194
 
        '-proxied' appended to their names.
1195
 
        """
1196
 
        return http_utils.ProxyServer(protocol_version=self._protocol_version)
1197
 
 
1198
 
    def _install_env(self, env):
1199
 
        for name, value in env.iteritems():
1200
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1201
 
 
1202
 
    def _restore_env(self):
1203
 
        for name, value in self._old_env.iteritems():
1204
 
            osutils.set_or_unset_env(name, value)
1205
 
 
1206
 
    def proxied_in_env(self, env):
1207
 
        self._install_env(env)
1208
 
        url = self.server.get_url()
1209
 
        t = self._transport(url)
1210
 
        try:
1211
 
            self.assertEqual('proxied contents of foo\n', t.get('foo').read())
1212
 
        finally:
1213
 
            self._restore_env()
1214
 
 
1215
 
    def not_proxied_in_env(self, env):
1216
 
        self._install_env(env)
1217
 
        url = self.server.get_url()
1218
 
        t = self._transport(url)
1219
 
        try:
1220
 
            self.assertEqual('contents of foo\n', t.get('foo').read())
1221
 
        finally:
1222
 
            self._restore_env()
 
1275
        # TODO: This is duplicated for lots of the classes in this file
 
1276
        return (features.pycurl.available()
 
1277
                and self._transport == PyCurlTransport)
 
1278
 
 
1279
    def assertProxied(self):
 
1280
        t = self.get_readonly_transport()
 
1281
        self.assertEqual('proxied contents of foo\n', t.get('foo').read())
 
1282
 
 
1283
    def assertNotProxied(self):
 
1284
        t = self.get_readonly_transport()
 
1285
        self.assertEqual('contents of foo\n', t.get('foo').read())
1223
1286
 
1224
1287
    def test_http_proxy(self):
1225
 
        self.proxied_in_env({'http_proxy': self.proxy_url})
 
1288
        self.overrideEnv('http_proxy', self.proxy_url)
 
1289
        self.assertProxied()
1226
1290
 
1227
1291
    def test_HTTP_PROXY(self):
1228
1292
        if self._testing_pycurl():
1231
1295
            # about. Should we ?)
1232
1296
            raise tests.TestNotApplicable(
1233
1297
                'pycurl does not check HTTP_PROXY for security reasons')
1234
 
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
 
1298
        self.overrideEnv('HTTP_PROXY', self.proxy_url)
 
1299
        self.assertProxied()
1235
1300
 
1236
1301
    def test_all_proxy(self):
1237
 
        self.proxied_in_env({'all_proxy': self.proxy_url})
 
1302
        self.overrideEnv('all_proxy', self.proxy_url)
 
1303
        self.assertProxied()
1238
1304
 
1239
1305
    def test_ALL_PROXY(self):
1240
 
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
 
1306
        self.overrideEnv('ALL_PROXY', self.proxy_url)
 
1307
        self.assertProxied()
1241
1308
 
1242
1309
    def test_http_proxy_with_no_proxy(self):
1243
 
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
1244
 
                                 'no_proxy': self.no_proxy_host})
 
1310
        self.overrideEnv('no_proxy', self.no_proxy_host)
 
1311
        self.overrideEnv('http_proxy', self.proxy_url)
 
1312
        self.assertNotProxied()
1245
1313
 
1246
1314
    def test_HTTP_PROXY_with_NO_PROXY(self):
1247
1315
        if self._testing_pycurl():
1248
1316
            raise tests.TestNotApplicable(
1249
1317
                'pycurl does not check HTTP_PROXY for security reasons')
1250
 
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
1251
 
                                 'NO_PROXY': self.no_proxy_host})
 
1318
        self.overrideEnv('NO_PROXY', self.no_proxy_host)
 
1319
        self.overrideEnv('HTTP_PROXY', self.proxy_url)
 
1320
        self.assertNotProxied()
1252
1321
 
1253
1322
    def test_all_proxy_with_no_proxy(self):
1254
 
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
1255
 
                                 'no_proxy': self.no_proxy_host})
 
1323
        self.overrideEnv('no_proxy', self.no_proxy_host)
 
1324
        self.overrideEnv('all_proxy', self.proxy_url)
 
1325
        self.assertNotProxied()
1256
1326
 
1257
1327
    def test_ALL_PROXY_with_NO_PROXY(self):
1258
 
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
1259
 
                                 'NO_PROXY': self.no_proxy_host})
 
1328
        self.overrideEnv('NO_PROXY', self.no_proxy_host)
 
1329
        self.overrideEnv('ALL_PROXY', self.proxy_url)
 
1330
        self.assertNotProxied()
1260
1331
 
1261
1332
    def test_http_proxy_without_scheme(self):
 
1333
        self.overrideEnv('http_proxy', self.server_host_port)
1262
1334
        if self._testing_pycurl():
1263
1335
            # pycurl *ignores* invalid proxy env variables. If that ever change
1264
1336
            # in the future, this test will fail indicating that pycurl do not
1265
1337
            # ignore anymore such variables.
1266
 
            self.not_proxied_in_env({'http_proxy': self.proxy_address})
 
1338
            self.assertNotProxied()
1267
1339
        else:
1268
 
            self.assertRaises(errors.InvalidURL,
1269
 
                              self.proxied_in_env,
1270
 
                              {'http_proxy': self.proxy_address})
 
1340
            self.assertRaises(errors.InvalidURL, self.assertProxied)
1271
1341
 
1272
1342
 
1273
1343
class TestRanges(http_utils.TestCaseWithWebserver):
1274
1344
    """Test the Range header in GET methods."""
1275
1345
 
 
1346
    scenarios = multiply_scenarios(
 
1347
        vary_by_http_client_implementation(),
 
1348
        vary_by_http_protocol_version(),
 
1349
        )
 
1350
 
1276
1351
    def setUp(self):
1277
1352
        http_utils.TestCaseWithWebserver.setUp(self)
1278
1353
        self.build_tree_contents([('a', '0123456789')],)
1279
 
        server = self.get_readonly_server()
1280
 
        self.transport = self._transport(server.get_url())
1281
1354
 
1282
1355
    def create_transport_readonly_server(self):
1283
1356
        return http_server.HttpServer(protocol_version=self._protocol_version)
1284
1357
 
1285
1358
    def _file_contents(self, relpath, ranges):
 
1359
        t = self.get_readonly_transport()
1286
1360
        offsets = [ (start, end - start + 1) for start, end in ranges]
1287
 
        coalesce = self.transport._coalesce_offsets
 
1361
        coalesce = t._coalesce_offsets
1288
1362
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1289
 
        code, data = self.transport._get(relpath, coalesced)
 
1363
        code, data = t._get(relpath, coalesced)
1290
1364
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1291
1365
        for start, end in ranges:
1292
1366
            data.seek(start)
1293
1367
            yield data.read(end - start + 1)
1294
1368
 
1295
1369
    def _file_tail(self, relpath, tail_amount):
1296
 
        code, data = self.transport._get(relpath, [], tail_amount)
 
1370
        t = self.get_readonly_transport()
 
1371
        code, data = t._get(relpath, [], tail_amount)
1297
1372
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1298
1373
        data.seek(-tail_amount, 2)
1299
1374
        return data.read(tail_amount)
1318
1393
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1319
1394
    """Test redirection between http servers."""
1320
1395
 
1321
 
    def create_transport_secondary_server(self):
1322
 
        """Create the secondary server redirecting to the primary server"""
1323
 
        new = self.get_readonly_server()
1324
 
 
1325
 
        redirecting = http_utils.HTTPServerRedirecting(
1326
 
            protocol_version=self._protocol_version)
1327
 
        redirecting.redirect_to(new.host, new.port)
1328
 
        return redirecting
 
1396
    scenarios = multiply_scenarios(
 
1397
        vary_by_http_client_implementation(),
 
1398
        vary_by_http_protocol_version(),
 
1399
        )
1329
1400
 
1330
1401
    def setUp(self):
1331
1402
        super(TestHTTPRedirections, self).setUp()
1333
1404
                                  ('bundle',
1334
1405
                                  '# Bazaar revision bundle v0.9\n#\n')
1335
1406
                                  ],)
1336
 
        # The requests to the old server will be redirected to the new server
1337
 
        self.old_transport = self._transport(self.old_server.get_url())
1338
1407
 
1339
1408
    def test_redirected(self):
1340
 
        self.assertRaises(errors.RedirectRequested, self.old_transport.get, 'a')
1341
 
        t = self._transport(self.new_server.get_url())
1342
 
        self.assertEqual('0123456789', t.get('a').read())
1343
 
 
1344
 
    def test_read_redirected_bundle_from_url(self):
1345
 
        from bzrlib.bundle import read_bundle_from_url
1346
 
        url = self.old_transport.abspath('bundle')
1347
 
        bundle = self.applyDeprecated(deprecated_in((1, 12, 0)),
1348
 
                read_bundle_from_url, url)
1349
 
        # If read_bundle_from_url was successful we get an empty bundle
1350
 
        self.assertEqual([], bundle.revisions)
 
1409
        self.assertRaises(errors.RedirectRequested,
 
1410
                          self.get_old_transport().get, 'a')
 
1411
        self.assertEqual('0123456789', self.get_new_transport().get('a').read())
1351
1412
 
1352
1413
 
1353
1414
class RedirectedRequest(_urllib2_wrappers.Request):
1366
1427
        self.follow_redirections = True
1367
1428
 
1368
1429
 
 
1430
def install_redirected_request(test):
 
1431
    test.overrideAttr(_urllib2_wrappers, 'Request', RedirectedRequest)
 
1432
 
 
1433
 
 
1434
def cleanup_http_redirection_connections(test):
 
1435
    # Some sockets are opened but never seen by _urllib, so we trap them at
 
1436
    # the _urllib2_wrappers level to be able to clean them up.
 
1437
    def socket_disconnect(sock):
 
1438
        try:
 
1439
            sock.shutdown(socket.SHUT_RDWR)
 
1440
            sock.close()
 
1441
        except socket.error:
 
1442
            pass
 
1443
    def connect(connection):
 
1444
        test.http_connect_orig(connection)
 
1445
        test.addCleanup(socket_disconnect, connection.sock)
 
1446
    test.http_connect_orig = test.overrideAttr(
 
1447
        _urllib2_wrappers.HTTPConnection, 'connect', connect)
 
1448
    def connect(connection):
 
1449
        test.https_connect_orig(connection)
 
1450
        test.addCleanup(socket_disconnect, connection.sock)
 
1451
    test.https_connect_orig = test.overrideAttr(
 
1452
        _urllib2_wrappers.HTTPSConnection, 'connect', connect)
 
1453
 
 
1454
 
1369
1455
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
1370
1456
    """Test redirections.
1371
1457
 
1380
1466
    -- vila 20070212
1381
1467
    """
1382
1468
 
 
1469
    scenarios = multiply_scenarios(
 
1470
        vary_by_http_client_implementation(),
 
1471
        vary_by_http_protocol_version(),
 
1472
        )
 
1473
 
1383
1474
    def setUp(self):
1384
 
        if pycurl_present and self._transport == PyCurlTransport:
 
1475
        if (features.pycurl.available()
 
1476
            and self._transport == PyCurlTransport):
1385
1477
            raise tests.TestNotApplicable(
1386
 
                "pycurl doesn't redirect silently annymore")
 
1478
                "pycurl doesn't redirect silently anymore")
1387
1479
        super(TestHTTPSilentRedirections, self).setUp()
1388
 
        self.setup_redirected_request()
1389
 
        self.addCleanup(self.cleanup_redirected_request)
 
1480
        install_redirected_request(self)
 
1481
        cleanup_http_redirection_connections(self)
1390
1482
        self.build_tree_contents([('a','a'),
1391
1483
                                  ('1/',),
1392
1484
                                  ('1/a', 'redirected once'),
1400
1492
                                  ('5/a', 'redirected 5 times'),
1401
1493
                                  ],)
1402
1494
 
1403
 
        self.old_transport = self._transport(self.old_server.get_url())
1404
 
 
1405
 
    def setup_redirected_request(self):
1406
 
        self.original_class = _urllib2_wrappers.Request
1407
 
        _urllib2_wrappers.Request = RedirectedRequest
1408
 
 
1409
 
    def cleanup_redirected_request(self):
1410
 
        _urllib2_wrappers.Request = self.original_class
1411
 
 
1412
 
    def create_transport_secondary_server(self):
1413
 
        """Create the secondary server, redirections are defined in the tests"""
1414
 
        return http_utils.HTTPServerRedirecting(
1415
 
            protocol_version=self._protocol_version)
1416
 
 
1417
1495
    def test_one_redirection(self):
1418
 
        t = self.old_transport
1419
 
 
1420
 
        req = RedirectedRequest('GET', t.abspath('a'))
1421
 
        req.follow_redirections = True
 
1496
        t = self.get_old_transport()
 
1497
        req = RedirectedRequest('GET', t._remote_path('a'))
1422
1498
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1423
1499
                                       self.new_server.port)
1424
1500
        self.old_server.redirections = \
1425
1501
            [('(.*)', r'%s/1\1' % (new_prefix), 301),]
1426
 
        self.assertEquals('redirected once',t._perform(req).read())
 
1502
        self.assertEqual('redirected once', t._perform(req).read())
1427
1503
 
1428
1504
    def test_five_redirections(self):
1429
 
        t = self.old_transport
1430
 
 
1431
 
        req = RedirectedRequest('GET', t.abspath('a'))
1432
 
        req.follow_redirections = True
 
1505
        t = self.get_old_transport()
 
1506
        req = RedirectedRequest('GET', t._remote_path('a'))
1433
1507
        old_prefix = 'http://%s:%s' % (self.old_server.host,
1434
1508
                                       self.old_server.port)
1435
1509
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1441
1515
            ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1442
1516
            ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1443
1517
            ]
1444
 
        self.assertEquals('redirected 5 times',t._perform(req).read())
 
1518
        self.assertEqual('redirected 5 times', t._perform(req).read())
1445
1519
 
1446
1520
 
1447
1521
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1448
1522
    """Test transport.do_catching_redirections."""
1449
1523
 
 
1524
    scenarios = multiply_scenarios(
 
1525
        vary_by_http_client_implementation(),
 
1526
        vary_by_http_protocol_version(),
 
1527
        )
 
1528
 
1450
1529
    def setUp(self):
1451
1530
        super(TestDoCatchRedirections, self).setUp()
1452
1531
        self.build_tree_contents([('a', '0123456789'),],)
1453
 
 
1454
 
        self.old_transport = self._transport(self.old_server.get_url())
1455
 
 
1456
 
    def get_a(self, transport):
1457
 
        return transport.get('a')
 
1532
        cleanup_http_redirection_connections(self)
 
1533
 
 
1534
        self.old_transport = self.get_old_transport()
 
1535
 
 
1536
    def get_a(self, t):
 
1537
        return t.get('a')
1458
1538
 
1459
1539
    def test_no_redirection(self):
1460
 
        t = self._transport(self.new_server.get_url())
 
1540
        t = self.get_new_transport()
1461
1541
 
1462
1542
        # We use None for redirected so that we fail if redirected
1463
 
        self.assertEquals('0123456789',
1464
 
                          transport.do_catching_redirections(
 
1543
        self.assertEqual('0123456789',
 
1544
                         transport.do_catching_redirections(
1465
1545
                self.get_a, t, None).read())
1466
1546
 
1467
1547
    def test_one_redirection(self):
1468
1548
        self.redirections = 0
1469
1549
 
1470
 
        def redirected(transport, exception, redirection_notice):
 
1550
        def redirected(t, exception, redirection_notice):
1471
1551
            self.redirections += 1
1472
 
            dir, file = urlutils.split(exception.target)
1473
 
            return self._transport(dir)
 
1552
            redirected_t = t._redirected_to(exception.source, exception.target)
 
1553
            return redirected_t
1474
1554
 
1475
 
        self.assertEquals('0123456789',
1476
 
                          transport.do_catching_redirections(
 
1555
        self.assertEqual('0123456789',
 
1556
                         transport.do_catching_redirections(
1477
1557
                self.get_a, self.old_transport, redirected).read())
1478
 
        self.assertEquals(1, self.redirections)
 
1558
        self.assertEqual(1, self.redirections)
1479
1559
 
1480
1560
    def test_redirection_loop(self):
1481
1561
 
1490
1570
                          self.get_a, self.old_transport, redirected)
1491
1571
 
1492
1572
 
 
1573
def _setup_authentication_config(**kwargs):
 
1574
    conf = config.AuthenticationConfig()
 
1575
    conf._get_config().update({'httptest': kwargs})
 
1576
    conf._save()
 
1577
 
 
1578
 
 
1579
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
 
1580
    """Unit tests for glue by which urllib2 asks us for authentication"""
 
1581
 
 
1582
    def test_get_user_password_without_port(self):
 
1583
        """We cope if urllib2 doesn't tell us the port.
 
1584
 
 
1585
        See https://bugs.launchpad.net/bzr/+bug/654684
 
1586
        """
 
1587
        user = 'joe'
 
1588
        password = 'foo'
 
1589
        _setup_authentication_config(scheme='http', host='localhost',
 
1590
                                     user=user, password=password)
 
1591
        handler = _urllib2_wrappers.HTTPAuthHandler()
 
1592
        got_pass = handler.get_user_password(dict(
 
1593
            user='joe',
 
1594
            protocol='http',
 
1595
            host='localhost',
 
1596
            path='/',
 
1597
            realm='Realm',
 
1598
            ))
 
1599
        self.assertEquals((user, password), got_pass)
 
1600
 
 
1601
 
1493
1602
class TestAuth(http_utils.TestCaseWithWebserver):
1494
1603
    """Test authentication scheme"""
1495
1604
 
1496
 
    _auth_header = 'Authorization'
1497
 
    _password_prompt_prefix = ''
1498
 
    _username_prompt_prefix = ''
1499
 
    # Set by load_tests
1500
 
    _auth_server = None
 
1605
    scenarios = multiply_scenarios(
 
1606
        vary_by_http_client_implementation(),
 
1607
        vary_by_http_protocol_version(),
 
1608
        vary_by_http_auth_scheme(),
 
1609
        )
1501
1610
 
1502
1611
    def setUp(self):
1503
1612
        super(TestAuth, self).setUp()
1506
1615
                                  ('b', 'contents of b\n'),])
1507
1616
 
1508
1617
    def create_transport_readonly_server(self):
1509
 
        return self._auth_server(protocol_version=self._protocol_version)
 
1618
        server = self._auth_server(protocol_version=self._protocol_version)
 
1619
        server._url_protocol = self._url_protocol
 
1620
        return server
1510
1621
 
1511
1622
    def _testing_pycurl(self):
1512
 
        return pycurl_present and self._transport == PyCurlTransport
 
1623
        # TODO: This is duplicated for lots of the classes in this file
 
1624
        return (features.pycurl.available()
 
1625
                and self._transport == PyCurlTransport)
1513
1626
 
1514
1627
    def get_user_url(self, user, password):
1515
1628
        """Build an url embedding user and password"""
1523
1636
        return url
1524
1637
 
1525
1638
    def get_user_transport(self, user, password):
1526
 
        return self._transport(self.get_user_url(user, password))
 
1639
        t = transport.get_transport_from_url(
 
1640
            self.get_user_url(user, password))
 
1641
        return t
1527
1642
 
1528
1643
    def test_no_user(self):
1529
1644
        self.server.add_user('joe', 'foo')
1580
1695
        self.assertEqual('', ui.ui_factory.stdin.readline())
1581
1696
        stderr.seek(0)
1582
1697
        expected_prompt = self._expected_username_prompt(t._unqualified_scheme)
1583
 
        self.assertEquals(expected_prompt, stderr.read(len(expected_prompt)))
1584
 
        self.assertEquals('', stdout.getvalue())
 
1698
        self.assertEqual(expected_prompt, stderr.read(len(expected_prompt)))
 
1699
        self.assertEqual('', stdout.getvalue())
1585
1700
        self._check_password_prompt(t._unqualified_scheme, 'joe',
1586
1701
                                    stderr.readline())
1587
1702
 
1602
1717
        self.assertEqual('', ui.ui_factory.stdin.readline())
1603
1718
        self._check_password_prompt(t._unqualified_scheme, 'joe',
1604
1719
                                    stderr.getvalue())
1605
 
        self.assertEquals('', stdout.getvalue())
 
1720
        self.assertEqual('', stdout.getvalue())
1606
1721
        # And we shouldn't prompt again for a different request
1607
1722
        # against the same transport.
1608
1723
        self.assertEqual('contents of b\n',t.get('b').read())
1618
1733
                              % (scheme.upper(),
1619
1734
                                 user, self.server.host, self.server.port,
1620
1735
                                 self.server.auth_realm)))
1621
 
        self.assertEquals(expected_prompt, actual_prompt)
 
1736
        self.assertEqual(expected_prompt, actual_prompt)
1622
1737
 
1623
1738
    def _expected_username_prompt(self, scheme):
1624
1739
        return (self._username_prompt_prefix
1638
1753
        self.server.add_user(user, password)
1639
1754
        t = self.get_user_transport(user, None)
1640
1755
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1641
 
                                            stdout=tests.StringIOWrapper())
 
1756
                                            stderr=tests.StringIOWrapper())
1642
1757
        # Create a minimal config file with the right password
1643
 
        conf = config.AuthenticationConfig()
1644
 
        conf._get_config().update(
1645
 
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1646
 
                          'user': user, 'password': password}})
1647
 
        conf._save()
 
1758
        _setup_authentication_config(scheme='http', port=self.server.port,
 
1759
                                     user=user, password=password)
1648
1760
        # Issue a request to the server to connect
1649
1761
        self.assertEqual('contents of a\n',t.get('a').read())
1650
1762
        # stdin should have  been left untouched
1652
1764
        # Only one 'Authentication Required' error should occur
1653
1765
        self.assertEqual(1, self.server.auth_required_errors)
1654
1766
 
1655
 
    def test_user_from_auth_conf(self):
1656
 
        if self._testing_pycurl():
1657
 
            raise tests.TestNotApplicable(
1658
 
                'pycurl does not support authentication.conf')
1659
 
        user = 'joe'
1660
 
        password = 'foo'
1661
 
        self.server.add_user(user, password)
1662
 
        # Create a minimal config file with the right password
1663
 
        conf = config.AuthenticationConfig()
1664
 
        conf._get_config().update(
1665
 
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1666
 
                          'user': user, 'password': password}})
1667
 
        conf._save()
1668
 
        t = self.get_user_transport(None, None)
1669
 
        # Issue a request to the server to connect
1670
 
        self.assertEqual('contents of a\n', t.get('a').read())
1671
 
        # Only one 'Authentication Required' error should occur
1672
 
        self.assertEqual(1, self.server.auth_required_errors)
1673
 
 
1674
1767
    def test_changing_nonce(self):
1675
1768
        if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1676
1769
                                     http_utils.ProxyDigestAuthServer):
1677
1770
            raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1678
1771
        if self._testing_pycurl():
1679
 
            raise tests.KnownFailure(
 
1772
            self.knownFailure(
1680
1773
                'pycurl does not handle a nonce change')
1681
1774
        self.server.add_user('joe', 'foo')
1682
1775
        t = self.get_user_transport('joe', 'foo')
1692
1785
        # initial 'who are you' and a second 'who are you' with the new nonce)
1693
1786
        self.assertEqual(2, self.server.auth_required_errors)
1694
1787
 
 
1788
    def test_user_from_auth_conf(self):
 
1789
        if self._testing_pycurl():
 
1790
            raise tests.TestNotApplicable(
 
1791
                'pycurl does not support authentication.conf')
 
1792
        user = 'joe'
 
1793
        password = 'foo'
 
1794
        self.server.add_user(user, password)
 
1795
        _setup_authentication_config(scheme='http', port=self.server.port,
 
1796
                                     user=user, password=password)
 
1797
        t = self.get_user_transport(None, None)
 
1798
        # Issue a request to the server to connect
 
1799
        self.assertEqual('contents of a\n', t.get('a').read())
 
1800
        # Only one 'Authentication Required' error should occur
 
1801
        self.assertEqual(1, self.server.auth_required_errors)
 
1802
 
 
1803
    def test_no_credential_leaks_in_log(self):
 
1804
        self.overrideAttr(debug, 'debug_flags', set(['http']))
 
1805
        user = 'joe'
 
1806
        password = 'very-sensitive-password'
 
1807
        self.server.add_user(user, password)
 
1808
        t = self.get_user_transport(user, password)
 
1809
        # Capture the debug calls to mutter
 
1810
        self.mutters = []
 
1811
        def mutter(*args):
 
1812
            lines = args[0] % args[1:]
 
1813
            # Some calls output multiple lines, just split them now since we
 
1814
            # care about a single one later.
 
1815
            self.mutters.extend(lines.splitlines())
 
1816
        self.overrideAttr(trace, 'mutter', mutter)
 
1817
        # Issue a request to the server to connect
 
1818
        self.assertEqual(True, t.has('a'))
 
1819
        # Only one 'Authentication Required' error should occur
 
1820
        self.assertEqual(1, self.server.auth_required_errors)
 
1821
        # Since the authentification succeeded, there should be a corresponding
 
1822
        # debug line
 
1823
        sent_auth_headers = [line for line in self.mutters
 
1824
                             if line.startswith('> %s' % (self._auth_header,))]
 
1825
        self.assertLength(1, sent_auth_headers)
 
1826
        self.assertStartsWith(sent_auth_headers[0],
 
1827
                              '> %s: <masked>' % (self._auth_header,))
1695
1828
 
1696
1829
 
1697
1830
class TestProxyAuth(TestAuth):
1698
 
    """Test proxy authentication schemes."""
1699
 
 
1700
 
    _auth_header = 'Proxy-authorization'
1701
 
    _password_prompt_prefix = 'Proxy '
1702
 
    _username_prompt_prefix = 'Proxy '
 
1831
    """Test proxy authentication schemes.
 
1832
 
 
1833
    This inherits from TestAuth to tweak the setUp and filter some failing
 
1834
    tests.
 
1835
    """
 
1836
 
 
1837
    scenarios = multiply_scenarios(
 
1838
        vary_by_http_client_implementation(),
 
1839
        vary_by_http_protocol_version(),
 
1840
        vary_by_http_proxy_auth_scheme(),
 
1841
        )
1703
1842
 
1704
1843
    def setUp(self):
1705
1844
        super(TestProxyAuth, self).setUp()
1706
 
        self._old_env = {}
1707
 
        self.addCleanup(self._restore_env)
1708
1845
        # Override the contents to avoid false positives
1709
1846
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1710
1847
                                  ('b', 'not proxied contents of b\n'),
1713
1850
                                  ])
1714
1851
 
1715
1852
    def get_user_transport(self, user, password):
1716
 
        self._install_env({'all_proxy': self.get_user_url(user, password)})
1717
 
        return self._transport(self.server.get_url())
1718
 
 
1719
 
    def _install_env(self, env):
1720
 
        for name, value in env.iteritems():
1721
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1722
 
 
1723
 
    def _restore_env(self):
1724
 
        for name, value in self._old_env.iteritems():
1725
 
            osutils.set_or_unset_env(name, value)
 
1853
        self.overrideEnv('all_proxy', self.get_user_url(user, password))
 
1854
        return TestAuth.get_user_transport(self, user, password)
1726
1855
 
1727
1856
    def test_empty_pass(self):
1728
1857
        if self._testing_pycurl():
1729
1858
            import pycurl
1730
1859
            if pycurl.version_info()[1] < '7.16.0':
1731
 
                raise tests.KnownFailure(
 
1860
                self.knownFailure(
1732
1861
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
1733
1862
        super(TestProxyAuth, self).test_empty_pass()
1734
1863
 
1747
1876
        self.readfile = StringIO(socket_read_content)
1748
1877
        self.writefile = StringIO()
1749
1878
        self.writefile.close = lambda: None
 
1879
        self.close = lambda: None
1750
1880
 
1751
1881
    def makefile(self, mode='r', bufsize=None):
1752
1882
        if 'r' in mode:
1757
1887
 
1758
1888
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1759
1889
 
 
1890
    scenarios = multiply_scenarios(
 
1891
        vary_by_http_client_implementation(),
 
1892
        vary_by_http_protocol_version(),
 
1893
        )
 
1894
 
1760
1895
    def setUp(self):
1761
1896
        super(SmartHTTPTunnellingTest, self).setUp()
1762
1897
        # We use the VFS layer as part of HTTP tunnelling tests.
1763
 
        self._captureVar('BZR_NO_SMART_VFS', None)
 
1898
        self.overrideEnv('BZR_NO_SMART_VFS', None)
1764
1899
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
 
1900
        self.http_server = self.get_readonly_server()
1765
1901
 
1766
1902
    def create_transport_readonly_server(self):
1767
 
        return http_utils.HTTPServerWithSmarts(
 
1903
        server = http_utils.HTTPServerWithSmarts(
1768
1904
            protocol_version=self._protocol_version)
 
1905
        server._url_protocol = self._url_protocol
 
1906
        return server
1769
1907
 
1770
1908
    def test_open_bzrdir(self):
1771
1909
        branch = self.make_branch('relpath')
1772
 
        http_server = self.get_readonly_server()
1773
 
        url = http_server.get_url() + 'relpath'
 
1910
        url = self.http_server.get_url() + 'relpath'
1774
1911
        bd = bzrdir.BzrDir.open(url)
 
1912
        self.addCleanup(bd.transport.disconnect)
1775
1913
        self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1776
1914
 
1777
1915
    def test_bulk_data(self):
1779
1917
        # The 'readv' command in the smart protocol both sends and receives
1780
1918
        # bulk data, so we use that.
1781
1919
        self.build_tree(['data-file'])
1782
 
        http_server = self.get_readonly_server()
1783
 
        http_transport = self._transport(http_server.get_url())
 
1920
        http_transport = transport.get_transport_from_url(
 
1921
            self.http_server.get_url())
1784
1922
        medium = http_transport.get_smart_medium()
1785
1923
        # Since we provide the medium, the url below will be mostly ignored
1786
1924
        # during the test, as long as the path is '/'.
1794
1932
        post_body = 'hello\n'
1795
1933
        expected_reply_body = 'ok\x012\n'
1796
1934
 
1797
 
        http_server = self.get_readonly_server()
1798
 
        http_transport = self._transport(http_server.get_url())
 
1935
        http_transport = transport.get_transport_from_url(
 
1936
            self.http_server.get_url())
1799
1937
        medium = http_transport.get_smart_medium()
1800
1938
        response = medium.send_http_smart_request(post_body)
1801
1939
        reply_body = response.read()
1802
1940
        self.assertEqual(expected_reply_body, reply_body)
1803
1941
 
1804
1942
    def test_smart_http_server_post_request_handler(self):
1805
 
        httpd = self.get_readonly_server()._get_httpd()
 
1943
        httpd = self.http_server.server
1806
1944
 
1807
1945
        socket = SampleSocket(
1808
1946
            'POST /.bzr/smart %s \r\n' % self._protocol_version
1840
1978
 
1841
1979
    def test_probe_smart_server(self):
1842
1980
        """Test error handling against server refusing smart requests."""
1843
 
        server = self.get_readonly_server()
1844
 
        t = self._transport(server.get_url())
 
1981
        t = self.get_readonly_transport()
1845
1982
        # No need to build a valid smart request here, the server will not even
1846
1983
        # try to interpret it.
1847
1984
        self.assertRaises(errors.SmartProtocolError,
1848
1985
                          t.get_smart_medium().send_http_smart_request,
1849
1986
                          'whatever')
1850
1987
 
 
1988
 
1851
1989
class Test_redirected_to(tests.TestCase):
1852
1990
 
 
1991
    scenarios = vary_by_http_client_implementation()
 
1992
 
1853
1993
    def test_redirected_to_subdir(self):
1854
1994
        t = self._transport('http://www.example.com/foo')
1855
1995
        r = t._redirected_to('http://www.example.com/foo',
1856
1996
                             'http://www.example.com/foo/subdir')
1857
1997
        self.assertIsInstance(r, type(t))
1858
1998
        # Both transports share the some connection
1859
 
        self.assertEquals(t._get_connection(), r._get_connection())
 
1999
        self.assertEqual(t._get_connection(), r._get_connection())
 
2000
        self.assertEquals('http://www.example.com/foo/subdir/', r.base)
1860
2001
 
1861
2002
    def test_redirected_to_self_with_slash(self):
1862
2003
        t = self._transport('http://www.example.com/foo')
1866
2007
        # Both transports share the some connection (one can argue that we
1867
2008
        # should return the exact same transport here, but that seems
1868
2009
        # overkill).
1869
 
        self.assertEquals(t._get_connection(), r._get_connection())
 
2010
        self.assertEqual(t._get_connection(), r._get_connection())
1870
2011
 
1871
2012
    def test_redirected_to_host(self):
1872
2013
        t = self._transport('http://www.example.com/foo')
1873
2014
        r = t._redirected_to('http://www.example.com/foo',
1874
2015
                             'http://foo.example.com/foo/subdir')
1875
2016
        self.assertIsInstance(r, type(t))
 
2017
        self.assertEquals('http://foo.example.com/foo/subdir/',
 
2018
            r.external_url())
1876
2019
 
1877
2020
    def test_redirected_to_same_host_sibling_protocol(self):
1878
2021
        t = self._transport('http://www.example.com/foo')
1879
2022
        r = t._redirected_to('http://www.example.com/foo',
1880
2023
                             'https://www.example.com/foo')
1881
2024
        self.assertIsInstance(r, type(t))
 
2025
        self.assertEquals('https://www.example.com/foo/',
 
2026
            r.external_url())
1882
2027
 
1883
2028
    def test_redirected_to_same_host_different_protocol(self):
1884
2029
        t = self._transport('http://www.example.com/foo')
1885
2030
        r = t._redirected_to('http://www.example.com/foo',
1886
2031
                             'ftp://www.example.com/foo')
1887
2032
        self.assertNotEquals(type(r), type(t))
 
2033
        self.assertEquals('ftp://www.example.com/foo/', r.external_url())
 
2034
 
 
2035
    def test_redirected_to_same_host_specific_implementation(self):
 
2036
        t = self._transport('http://www.example.com/foo')
 
2037
        r = t._redirected_to('http://www.example.com/foo',
 
2038
                             'https+urllib://www.example.com/foo')
 
2039
        self.assertEquals('https://www.example.com/foo/', r.external_url())
1888
2040
 
1889
2041
    def test_redirected_to_different_host_same_user(self):
1890
2042
        t = self._transport('http://joe@www.example.com/foo')
1891
2043
        r = t._redirected_to('http://www.example.com/foo',
1892
2044
                             'https://foo.example.com/foo')
1893
2045
        self.assertIsInstance(r, type(t))
1894
 
        self.assertEquals(t._user, r._user)
 
2046
        self.assertEqual(t._parsed_url.user, r._parsed_url.user)
 
2047
        self.assertEquals('https://joe@foo.example.com/foo/', r.external_url())
1895
2048
 
1896
2049
 
1897
2050
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1906
2059
    line.
1907
2060
    """
1908
2061
 
1909
 
    def handle_one_request(self):
 
2062
    def _handle_one_request(self):
1910
2063
        tcs = self.server.test_case_server
1911
2064
        requestline = self.rfile.readline()
1912
2065
        headers = self.MessageClass(self.rfile, 0)
1950
2103
    pass
1951
2104
 
1952
2105
 
1953
 
if tests.HTTPSServerFeature.available():
 
2106
if features.HTTPSServerFeature.available():
1954
2107
    from bzrlib.tests import https_server
1955
2108
    class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1956
2109
        pass
1966
2119
    def setUp(self):
1967
2120
        tests.TestCase.setUp(self)
1968
2121
        self.server = self._activity_server(self._protocol_version)
1969
 
        self.server.setUp()
1970
 
        self.activities = {}
 
2122
        self.server.start_server()
 
2123
        _activities = {} # Don't close over self and create a cycle
1971
2124
        def report_activity(t, bytes, direction):
1972
 
            count = self.activities.get(direction, 0)
 
2125
            count = _activities.get(direction, 0)
1973
2126
            count += bytes
1974
 
            self.activities[direction] = count
 
2127
            _activities[direction] = count
 
2128
        self.activities = _activities
1975
2129
 
1976
2130
        # We override at class level because constructors may propagate the
1977
2131
        # bound method and render instance overriding ineffective (an
1978
2132
        # alternative would be to define a specific ui factory instead...)
1979
 
        self.orig_report_activity = self._transport._report_activity
1980
 
        self._transport._report_activity = report_activity
1981
 
 
1982
 
    def tearDown(self):
1983
 
        self._transport._report_activity = self.orig_report_activity
1984
 
        self.server.tearDown()
1985
 
        tests.TestCase.tearDown(self)
 
2133
        self.overrideAttr(self._transport, '_report_activity', report_activity)
 
2134
        self.addCleanup(self.server.stop_server)
1986
2135
 
1987
2136
    def get_transport(self):
1988
 
        return self._transport(self.server.get_url())
 
2137
        t = self._transport(self.server.get_url())
 
2138
        # FIXME: Needs cleanup -- vila 20100611
 
2139
        return t
1989
2140
 
1990
2141
    def assertActivitiesMatch(self):
1991
2142
        self.assertEqual(self.server.bytes_read,
2096
2247
'''
2097
2248
        t = self.get_transport()
2098
2249
        # We must send a single line of body bytes, see
2099
 
        # PredefinedRequestHandler.handle_one_request
 
2250
        # PredefinedRequestHandler._handle_one_request
2100
2251
        code, f = t._post('abc def end-of-body\n')
2101
2252
        self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2102
2253
        self.assertActivitiesMatch()
2104
2255
 
2105
2256
class TestActivity(tests.TestCase, TestActivityMixin):
2106
2257
 
 
2258
    scenarios = multiply_scenarios(
 
2259
        vary_by_http_activity(),
 
2260
        vary_by_http_protocol_version(),
 
2261
        )
 
2262
 
2107
2263
    def setUp(self):
2108
 
        tests.TestCase.setUp(self)
2109
 
        self.server = self._activity_server(self._protocol_version)
2110
 
        self.server.setUp()
2111
 
        self.activities = {}
2112
 
        def report_activity(t, bytes, direction):
2113
 
            count = self.activities.get(direction, 0)
2114
 
            count += bytes
2115
 
            self.activities[direction] = count
2116
 
 
2117
 
        # We override at class level because constructors may propagate the
2118
 
        # bound method and render instance overriding ineffective (an
2119
 
        # alternative would be to define a specific ui factory instead...)
2120
 
        self.orig_report_activity = self._transport._report_activity
2121
 
        self._transport._report_activity = report_activity
2122
 
 
2123
 
    def tearDown(self):
2124
 
        self._transport._report_activity = self.orig_report_activity
2125
 
        self.server.tearDown()
2126
 
        tests.TestCase.tearDown(self)
 
2264
        TestActivityMixin.setUp(self)
2127
2265
 
2128
2266
 
2129
2267
class TestNoReportActivity(tests.TestCase, TestActivityMixin):
2130
2268
 
 
2269
    # Unlike TestActivity, we are really testing ReportingFileSocket and
 
2270
    # ReportingSocket, so we don't need all the parametrization. Since
 
2271
    # ReportingFileSocket and ReportingSocket are wrappers, it's easier to
 
2272
    # test them through their use by the transport than directly (that's a
 
2273
    # bit less clean but far more simpler and effective).
 
2274
    _activity_server = ActivityHTTPServer
 
2275
    _protocol_version = 'HTTP/1.1'
 
2276
 
2131
2277
    def setUp(self):
2132
 
        tests.TestCase.setUp(self)
2133
 
        # Unlike TestActivity, we are really testing ReportingFileSocket and
2134
 
        # ReportingSocket, so we don't need all the parametrization. Since
2135
 
        # ReportingFileSocket and ReportingSocket are wrappers, it's easier to
2136
 
        # test them through their use by the transport than directly (that's a
2137
 
        # bit less clean but far more simpler and effective).
2138
 
        self.server = ActivityHTTPServer('HTTP/1.1')
2139
 
        self._transport=_urllib.HttpTransport_urllib
2140
 
 
2141
 
        self.server.setUp()
2142
 
 
2143
 
        # We override at class level because constructors may propagate the
2144
 
        # bound method and render instance overriding ineffective (an
2145
 
        # alternative would be to define a specific ui factory instead...)
2146
 
        self.orig_report_activity = self._transport._report_activity
2147
 
        self._transport._report_activity = None
2148
 
 
2149
 
    def tearDown(self):
2150
 
        self._transport._report_activity = self.orig_report_activity
2151
 
        self.server.tearDown()
2152
 
        tests.TestCase.tearDown(self)
 
2278
        self._transport =_urllib.HttpTransport_urllib
 
2279
        TestActivityMixin.setUp(self)
2153
2280
 
2154
2281
    def assertActivitiesMatch(self):
2155
2282
        # Nothing to check here
2156
2283
        pass
 
2284
 
 
2285
 
 
2286
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
 
2287
    """Test authentication on the redirected http server."""
 
2288
 
 
2289
    scenarios = vary_by_http_protocol_version()
 
2290
 
 
2291
    _auth_header = 'Authorization'
 
2292
    _password_prompt_prefix = ''
 
2293
    _username_prompt_prefix = ''
 
2294
    _auth_server = http_utils.HTTPBasicAuthServer
 
2295
    _transport = _urllib.HttpTransport_urllib
 
2296
 
 
2297
    def setUp(self):
 
2298
        super(TestAuthOnRedirected, self).setUp()
 
2299
        self.build_tree_contents([('a','a'),
 
2300
                                  ('1/',),
 
2301
                                  ('1/a', 'redirected once'),
 
2302
                                  ],)
 
2303
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
2304
                                       self.new_server.port)
 
2305
        self.old_server.redirections = [
 
2306
            ('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
2307
        self.old_transport = self.get_old_transport()
 
2308
        self.new_server.add_user('joe', 'foo')
 
2309
        cleanup_http_redirection_connections(self)
 
2310
 
 
2311
    def create_transport_readonly_server(self):
 
2312
        server = self._auth_server(protocol_version=self._protocol_version)
 
2313
        server._url_protocol = self._url_protocol
 
2314
        return server
 
2315
 
 
2316
    def get_a(self, t):
 
2317
        return t.get('a')
 
2318
 
 
2319
    def test_auth_on_redirected_via_do_catching_redirections(self):
 
2320
        self.redirections = 0
 
2321
 
 
2322
        def redirected(t, exception, redirection_notice):
 
2323
            self.redirections += 1
 
2324
            redirected_t = t._redirected_to(exception.source, exception.target)
 
2325
            self.addCleanup(redirected_t.disconnect)
 
2326
            return redirected_t
 
2327
 
 
2328
        stdout = tests.StringIOWrapper()
 
2329
        stderr = tests.StringIOWrapper()
 
2330
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
2331
                                            stdout=stdout, stderr=stderr)
 
2332
        self.assertEqual('redirected once',
 
2333
                         transport.do_catching_redirections(
 
2334
                self.get_a, self.old_transport, redirected).read())
 
2335
        self.assertEqual(1, self.redirections)
 
2336
        # stdin should be empty
 
2337
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
2338
        # stdout should be empty, stderr will contains the prompts
 
2339
        self.assertEqual('', stdout.getvalue())
 
2340
 
 
2341
    def test_auth_on_redirected_via_following_redirections(self):
 
2342
        self.new_server.add_user('joe', 'foo')
 
2343
        stdout = tests.StringIOWrapper()
 
2344
        stderr = tests.StringIOWrapper()
 
2345
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
2346
                                            stdout=stdout, stderr=stderr)
 
2347
        t = self.old_transport
 
2348
        req = RedirectedRequest('GET', t.abspath('a'))
 
2349
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
2350
                                       self.new_server.port)
 
2351
        self.old_server.redirections = [
 
2352
            ('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
2353
        self.assertEqual('redirected once', t._perform(req).read())
 
2354
        # stdin should be empty
 
2355
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
2356
        # stdout should be empty, stderr will contains the prompts
 
2357
        self.assertEqual('', stdout.getvalue())
 
2358