~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Vincent Ladeuil
  • Date: 2017-01-17 13:48:10 UTC
  • mfrom: (6615.3.6 merges)
  • mto: This revision was merged to the branch mainline in revision 6620.
  • Revision ID: v.ladeuil+lp@free.fr-20170117134810-j9p3lidfy6pfyfsc
Merge 2.7, resolving conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2012, 2015, 2016, 2017 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
34
31
 
35
32
import bzrlib
36
33
from bzrlib import (
37
 
    bzrdir,
38
34
    config,
 
35
    controldir,
 
36
    debug,
39
37
    errors,
40
38
    osutils,
41
39
    remote as _mod_remote,
42
40
    tests,
 
41
    trace,
43
42
    transport,
44
43
    ui,
45
 
    urlutils,
46
44
    )
47
45
from bzrlib.tests import (
48
46
    features,
50
48
    http_utils,
51
49
    test_server,
52
50
    )
 
51
from bzrlib.tests.scenarios import (
 
52
    load_tests_apply_scenarios,
 
53
    multiply_scenarios,
 
54
    )
53
55
from bzrlib.transport import (
54
56
    http,
55
57
    remote,
64
66
    from bzrlib.transport.http._pycurl import PyCurlTransport
65
67
 
66
68
 
67
 
def load_tests(standard_tests, module, loader):
68
 
    """Multiply tests for http clients and protocol versions."""
69
 
    result = loader.suiteClass()
70
 
 
71
 
    # one for each transport implementation
72
 
    t_tests, remaining_tests = tests.split_suite_by_condition(
73
 
        standard_tests, tests.condition_isinstance((
74
 
                TestHttpTransportRegistration,
75
 
                TestHttpTransportUrls,
76
 
                Test_redirected_to,
77
 
                )))
 
69
load_tests = load_tests_apply_scenarios
 
70
 
 
71
 
 
72
def vary_by_http_client_implementation():
 
73
    """Test the two libraries we can use, pycurl and urllib."""
78
74
    transport_scenarios = [
79
75
        ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
80
76
                        _server=http_server.HttpServer_urllib,
85
81
            ('pycurl', dict(_transport=PyCurlTransport,
86
82
                            _server=http_server.HttpServer_PyCurl,
87
83
                            _url_protocol='http+pycurl',)))
88
 
    tests.multiply_tests(t_tests, transport_scenarios, result)
89
 
 
90
 
    protocol_scenarios = [
91
 
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
92
 
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
93
 
            ]
94
 
 
95
 
    # some tests are parametrized by the protocol version only
96
 
    p_tests, remaining_tests = tests.split_suite_by_condition(
97
 
        remaining_tests, tests.condition_isinstance((
98
 
                TestAuthOnRedirected,
99
 
                )))
100
 
    tests.multiply_tests(p_tests, protocol_scenarios, result)
101
 
 
102
 
    # each implementation tested with each HTTP version
103
 
    tp_tests, remaining_tests = tests.split_suite_by_condition(
104
 
        remaining_tests, tests.condition_isinstance((
105
 
                SmartHTTPTunnellingTest,
106
 
                TestDoCatchRedirections,
107
 
                TestHTTPConnections,
108
 
                TestHTTPRedirections,
109
 
                TestHTTPSilentRedirections,
110
 
                TestLimitedRangeRequestServer,
111
 
                TestPost,
112
 
                TestProxyHttpServer,
113
 
                TestRanges,
114
 
                TestSpecificRequestHandler,
115
 
                )))
116
 
    tp_scenarios = tests.multiply_scenarios(transport_scenarios,
117
 
                                            protocol_scenarios)
118
 
    tests.multiply_tests(tp_tests, tp_scenarios, result)
119
 
 
120
 
    # proxy auth: each auth scheme on all http versions on all implementations.
121
 
    tppa_tests, remaining_tests = tests.split_suite_by_condition(
122
 
        remaining_tests, tests.condition_isinstance((
123
 
                TestProxyAuth,
124
 
                )))
125
 
    proxy_auth_scheme_scenarios = [
126
 
        ('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
127
 
        ('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
128
 
        ('basicdigest',
129
 
         dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
84
    return transport_scenarios
 
85
 
 
86
 
 
87
def vary_by_http_protocol_version():
 
88
    """Test on http/1.0 and 1.1"""
 
89
    return [
 
90
        ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
 
91
        ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
130
92
        ]
131
 
    tppa_scenarios = tests.multiply_scenarios(tp_scenarios,
132
 
                                              proxy_auth_scheme_scenarios)
133
 
    tests.multiply_tests(tppa_tests, tppa_scenarios, result)
134
 
 
135
 
    # auth: each auth scheme on all http versions on all implementations.
136
 
    tpa_tests, remaining_tests = tests.split_suite_by_condition(
137
 
        remaining_tests, tests.condition_isinstance((
138
 
                TestAuth,
139
 
                )))
140
 
    auth_scheme_scenarios = [
 
93
 
 
94
 
 
95
def vary_by_http_auth_scheme():
 
96
    scenarios = [
141
97
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
142
98
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
143
99
        ('basicdigest',
144
 
         dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
145
 
        ]
146
 
    tpa_scenarios = tests.multiply_scenarios(tp_scenarios,
147
 
                                             auth_scheme_scenarios)
148
 
    tests.multiply_tests(tpa_tests, tpa_scenarios, result)
149
 
 
150
 
    # activity: on all http[s] versions on all implementations
151
 
    tpact_tests, remaining_tests = tests.split_suite_by_condition(
152
 
        remaining_tests, tests.condition_isinstance((
153
 
                TestActivity,
154
 
                )))
 
100
            dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
 
101
        ]
 
102
    # Add some attributes common to all scenarios
 
103
    for scenario_id, scenario_dict in scenarios:
 
104
        scenario_dict.update(_auth_header='Authorization',
 
105
                             _username_prompt_prefix='',
 
106
                             _password_prompt_prefix='')
 
107
    return scenarios
 
108
 
 
109
 
 
110
def vary_by_http_proxy_auth_scheme():
 
111
    scenarios = [
 
112
        ('proxy-basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
 
113
        ('proxy-digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
 
114
        ('proxy-basicdigest',
 
115
            dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
116
        ]
 
117
    # Add some attributes common to all scenarios
 
118
    for scenario_id, scenario_dict in scenarios:
 
119
        scenario_dict.update(_auth_header='Proxy-Authorization',
 
120
                             _username_prompt_prefix='Proxy ',
 
121
                             _password_prompt_prefix='Proxy ')
 
122
    return scenarios
 
123
 
 
124
 
 
125
def vary_by_http_activity():
155
126
    activity_scenarios = [
156
127
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
157
 
                             _transport=_urllib.HttpTransport_urllib,)),
 
128
                            _transport=_urllib.HttpTransport_urllib,)),
158
129
        ]
159
 
    if tests.HTTPSServerFeature.available():
 
130
    if features.pycurl.available():
 
131
        activity_scenarios.append(
 
132
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
 
133
                                _transport=PyCurlTransport,)),)
 
134
    if features.HTTPSServerFeature.available():
 
135
        # FIXME: Until we have a better way to handle self-signed certificates
 
136
        # (like allowing them in a test specific authentication.conf for
 
137
        # example), we need some specialized pycurl/urllib transport for tests.
 
138
        # -- vila 2012-01-20
 
139
        from bzrlib.tests import (
 
140
            ssl_certs,
 
141
            )
 
142
        class HTTPS_urllib_transport(_urllib.HttpTransport_urllib):
 
143
 
 
144
            def __init__(self, base, _from_transport=None):
 
145
                super(HTTPS_urllib_transport, self).__init__(
 
146
                    base, _from_transport=_from_transport,
 
147
                    ca_certs=ssl_certs.build_path('ca.crt'))
 
148
 
160
149
        activity_scenarios.append(
161
150
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
162
 
                                  _transport=_urllib.HttpTransport_urllib,)),)
163
 
    if features.pycurl.available():
164
 
        activity_scenarios.append(
165
 
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
166
 
                                 _transport=PyCurlTransport,)),)
167
 
        if tests.HTTPSServerFeature.available():
168
 
            from bzrlib.tests import (
169
 
                ssl_certs,
170
 
                )
171
 
            # FIXME: Until we have a better way to handle self-signed
172
 
            # certificates (like allowing them in a test specific
173
 
            # authentication.conf for example), we need some specialized pycurl
174
 
            # transport for tests.
 
151
                                  _transport=HTTPS_urllib_transport,)),)
 
152
        if features.pycurl.available():
175
153
            class HTTPS_pycurl_transport(PyCurlTransport):
176
154
 
177
155
                def __init__(self, base, _from_transport=None):
181
159
 
182
160
            activity_scenarios.append(
183
161
                ('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
184
 
                                      _transport=HTTPS_pycurl_transport,)),)
185
 
 
186
 
    tpact_scenarios = tests.multiply_scenarios(activity_scenarios,
187
 
                                               protocol_scenarios)
188
 
    tests.multiply_tests(tpact_tests, tpact_scenarios, result)
189
 
 
190
 
    # No parametrization for the remaining tests
191
 
    result.addTests(remaining_tests)
192
 
 
193
 
    return result
 
162
                                    _transport=HTTPS_pycurl_transport,)),)
 
163
    return activity_scenarios
194
164
 
195
165
 
196
166
class FakeManager(object):
229
199
        self._sock.bind(('127.0.0.1', 0))
230
200
        self.host, self.port = self._sock.getsockname()
231
201
        self._ready = threading.Event()
232
 
        self._thread = test_server.ThreadWithException(
233
 
            event=self._ready, target=self._accept_read_and_reply)
 
202
        self._thread = test_server.TestThread(
 
203
            sync_event=self._ready, target=self._accept_read_and_reply)
234
204
        self._thread.start()
235
205
        if 'threads' in tests.selftest_debug_flags:
236
206
            sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
290
260
        self.assertEqual('basic', scheme)
291
261
        self.assertEqual('realm="Thou should not pass"', remainder)
292
262
 
 
263
    def test_build_basic_header_with_long_creds(self):
 
264
        handler = _urllib2_wrappers.BasicAuthHandler()
 
265
        user = 'user' * 10  # length 40
 
266
        password = 'password' * 5  # length 40
 
267
        header = handler.build_auth_header(
 
268
            dict(user=user, password=password), None)
 
269
        # https://bugs.launchpad.net/bzr/+bug/1606203 was caused by incorrectly
 
270
        # creating a header value with an embedded '\n'
 
271
        self.assertFalse('\n' in header)
 
272
 
293
273
    def test_basic_extract_realm(self):
294
274
        scheme, remainder = self.parse_header(
295
275
            'Basic realm="Thou should not pass"',
305
285
        self.assertEqual('realm="Thou should not pass"', remainder)
306
286
 
307
287
 
 
288
class TestHTTPRangeParsing(tests.TestCase):
 
289
 
 
290
    def setUp(self):
 
291
        super(TestHTTPRangeParsing, self).setUp()
 
292
        # We focus on range  parsing here and ignore everything else
 
293
        class RequestHandler(http_server.TestingHTTPRequestHandler):
 
294
            def setup(self): pass
 
295
            def handle(self): pass
 
296
            def finish(self): pass
 
297
 
 
298
        self.req_handler = RequestHandler(None, None, None)
 
299
 
 
300
    def assertRanges(self, ranges, header, file_size):
 
301
        self.assertEqual(ranges,
 
302
                          self.req_handler._parse_ranges(header, file_size))
 
303
 
 
304
    def test_simple_range(self):
 
305
        self.assertRanges([(0,2)], 'bytes=0-2', 12)
 
306
 
 
307
    def test_tail(self):
 
308
        self.assertRanges([(8, 11)], 'bytes=-4', 12)
 
309
 
 
310
    def test_tail_bigger_than_file(self):
 
311
        self.assertRanges([(0, 11)], 'bytes=-99', 12)
 
312
 
 
313
    def test_range_without_end(self):
 
314
        self.assertRanges([(4, 11)], 'bytes=4-', 12)
 
315
 
 
316
    def test_invalid_ranges(self):
 
317
        self.assertRanges(None, 'bytes=12-22', 12)
 
318
        self.assertRanges(None, 'bytes=1-3,12-22', 12)
 
319
        self.assertRanges(None, 'bytes=-', 12)
 
320
 
 
321
 
308
322
class TestHTTPServer(tests.TestCase):
309
323
    """Test the HTTP servers implementations."""
310
324
 
380
394
    _transport = property(_get_pycurl_maybe)
381
395
 
382
396
 
383
 
class TestHttpUrls(tests.TestCase):
384
 
 
385
 
    # TODO: This should be moved to authorization tests once they
386
 
    # are written.
387
 
 
388
 
    def test_url_parsing(self):
389
 
        f = FakeManager()
390
 
        url = http.extract_auth('http://example.com', f)
391
 
        self.assertEqual('http://example.com', url)
392
 
        self.assertEqual(0, len(f.credentials))
393
 
        url = http.extract_auth(
394
 
            'http://user:pass@example.com/bzr/bzr.dev', f)
395
 
        self.assertEqual('http://example.com/bzr/bzr.dev', url)
396
 
        self.assertEqual(1, len(f.credentials))
397
 
        self.assertEqual([None, 'example.com', 'user', 'pass'],
398
 
                         f.credentials[0])
399
 
 
400
 
 
401
397
class TestHttpTransportUrls(tests.TestCase):
402
398
    """Test the http urls."""
403
399
 
 
400
    scenarios = vary_by_http_client_implementation()
 
401
 
404
402
    def test_abs_url(self):
405
403
        """Construction of absolute http URLs"""
406
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
404
        t = self._transport('http://example.com/bzr/bzr.dev/')
407
405
        eq = self.assertEqualDiff
408
 
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
409
 
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
410
 
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
 
406
        eq(t.abspath('.'), 'http://example.com/bzr/bzr.dev')
 
407
        eq(t.abspath('foo/bar'), 'http://example.com/bzr/bzr.dev/foo/bar')
 
408
        eq(t.abspath('.bzr'), 'http://example.com/bzr/bzr.dev/.bzr')
411
409
        eq(t.abspath('.bzr/1//2/./3'),
412
 
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
 
410
           'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
413
411
 
414
412
    def test_invalid_http_urls(self):
415
413
        """Trap invalid construction of urls"""
416
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
414
        self._transport('http://example.com/bzr/bzr.dev/')
417
415
        self.assertRaises(errors.InvalidURL,
418
416
                          self._transport,
419
 
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
 
417
                          'http://http://example.com/bzr/bzr.dev/')
420
418
 
421
419
    def test_http_root_urls(self):
422
420
        """Construction of URLs from server root"""
423
 
        t = self._transport('http://bzr.ozlabs.org/')
 
421
        t = self._transport('http://example.com/')
424
422
        eq = self.assertEqualDiff
425
423
        eq(t.abspath('.bzr/tree-version'),
426
 
           'http://bzr.ozlabs.org/.bzr/tree-version')
 
424
           'http://example.com/.bzr/tree-version')
427
425
 
428
426
    def test_http_impl_urls(self):
429
427
        """There are servers which ask for particular clients to connect"""
475
473
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
476
474
    """Test the http connections."""
477
475
 
 
476
    scenarios = multiply_scenarios(
 
477
        vary_by_http_client_implementation(),
 
478
        vary_by_http_protocol_version(),
 
479
        )
 
480
 
478
481
    def setUp(self):
479
 
        http_utils.TestCaseWithWebserver.setUp(self)
 
482
        super(TestHTTPConnections, self).setUp()
480
483
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
481
484
                        transport=self.get_transport())
482
485
 
525
528
class TestHttpTransportRegistration(tests.TestCase):
526
529
    """Test registrations of various http implementations"""
527
530
 
 
531
    scenarios = vary_by_http_client_implementation()
 
532
 
528
533
    def test_http_registered(self):
529
 
        t = transport.get_transport('%s://foo.com/' % self._url_protocol)
 
534
        t = transport.get_transport_from_url(
 
535
            '%s://foo.com/' % self._url_protocol)
530
536
        self.assertIsInstance(t, transport.Transport)
531
537
        self.assertIsInstance(t, self._transport)
532
538
 
533
539
 
534
540
class TestPost(tests.TestCase):
535
541
 
 
542
    scenarios = multiply_scenarios(
 
543
        vary_by_http_client_implementation(),
 
544
        vary_by_http_protocol_version(),
 
545
        )
 
546
 
536
547
    def test_post_body_is_received(self):
537
548
        server = RecordingServer(expect_body_tail='end-of-body',
538
549
                                 scheme=self._url_protocol)
539
550
        self.start_server(server)
540
551
        url = server.get_url()
541
552
        # FIXME: needs a cleanup -- vila 20100611
542
 
        http_transport = transport.get_transport(url)
 
553
        http_transport = transport.get_transport_from_url(url)
543
554
        code, response = http_transport._post('abc def end-of-body')
544
555
        self.assertTrue(
545
556
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
546
557
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
 
558
        self.assertTrue('content-type: application/octet-stream\r'
 
559
                        in server.received_bytes.lower())
547
560
        # The transport should not be assuming that the server can accept
548
561
        # chunked encoding the first time it connects, because HTTP/1.1, so we
549
562
        # check for the literal string.
585
598
    Daughter classes are expected to override _req_handler_class
586
599
    """
587
600
 
 
601
    scenarios = multiply_scenarios(
 
602
        vary_by_http_client_implementation(),
 
603
        vary_by_http_protocol_version(),
 
604
        )
 
605
 
588
606
    # Provide a useful default
589
607
    _req_handler_class = http_server.TestingHTTPRequestHandler
590
608
 
648
666
 
649
667
    _req_handler_class = BadStatusRequestHandler
650
668
 
 
669
    def setUp(self):
 
670
        super(TestBadStatusServer, self).setUp()
 
671
        # See https://bugs.launchpad.net/bzr/+bug/1451448 for details.
 
672
        # TD;LR: Running both a TCP client and server in the same process and
 
673
        # thread uncovers a race in python. The fix is to run the server in a
 
674
        # different process. Trying to fix yet another race here is not worth
 
675
        # the effort. -- vila 2015-09-06
 
676
        if 'HTTP/1.0' in self.id():
 
677
            raise tests.TestSkipped(
 
678
                'Client/Server in the same process and thread can hang')
 
679
 
651
680
    def test_http_has(self):
652
681
        t = self.get_readonly_transport()
653
 
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
 
682
        self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
 
683
                           errors.InvalidHttpResponse),
 
684
                          t.has, 'foo/bar')
654
685
 
655
686
    def test_http_get(self):
656
687
        t = self.get_readonly_transport()
657
 
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
 
688
        self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
 
689
                           errors.InvalidHttpResponse),
 
690
                          t.get, 'foo/bar')
658
691
 
659
692
 
660
693
class InvalidStatusRequestHandler(http_server.TestingHTTPRequestHandler):
841
874
        t = self.get_readonly_transport()
842
875
        # force transport to issue multiple requests
843
876
        t._get_max_size = 2
844
 
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
877
        list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
845
878
        # The server should have issued 3 requests
846
879
        self.assertEqual(3, server.GET_request_nb)
847
880
        self.assertEqual('0123456789', t.get_bytes('a'))
924
957
    def get_multiple_ranges(self, file, file_size, ranges):
925
958
        self.send_response(206)
926
959
        self.send_header('Accept-Ranges', 'bytes')
 
960
        # XXX: this is strange; the 'random' name below seems undefined and
 
961
        # yet the tests pass -- mbp 2010-10-11 bug 658773
927
962
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
928
963
        self.send_header("Content-Type",
929
964
                         "multipart/byteranges; boundary=%s" % boundary)
991
1026
                return
992
1027
            self.send_range_content(file, start, end - start + 1)
993
1028
            cur += 1
994
 
        # No final boundary
 
1029
        # Final boundary
995
1030
        self.wfile.write(boundary_line)
996
1031
 
997
1032
 
1026
1061
        # that mode
1027
1062
        self.assertEqual('single', t._range_hint)
1028
1063
 
 
1064
 
 
1065
class TruncatedBeforeBoundaryRequestHandler(
 
1066
    http_server.TestingHTTPRequestHandler):
 
1067
    """Truncation before a boundary, like in bug 198646"""
 
1068
 
 
1069
    _truncated_ranges = 1
 
1070
 
 
1071
    def get_multiple_ranges(self, file, file_size, ranges):
 
1072
        self.send_response(206)
 
1073
        self.send_header('Accept-Ranges', 'bytes')
 
1074
        boundary = 'tagada'
 
1075
        self.send_header('Content-Type',
 
1076
                         'multipart/byteranges; boundary=%s' % boundary)
 
1077
        boundary_line = '--%s\r\n' % boundary
 
1078
        # Calculate the Content-Length
 
1079
        content_length = 0
 
1080
        for (start, end) in ranges:
 
1081
            content_length += len(boundary_line)
 
1082
            content_length += self._header_line_length(
 
1083
                'Content-type', 'application/octet-stream')
 
1084
            content_length += self._header_line_length(
 
1085
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
 
1086
            content_length += len('\r\n') # end headers
 
1087
            content_length += end - start # + 1
 
1088
        content_length += len(boundary_line)
 
1089
        self.send_header('Content-length', content_length)
 
1090
        self.end_headers()
 
1091
 
 
1092
        # Send the multipart body
 
1093
        cur = 0
 
1094
        for (start, end) in ranges:
 
1095
            if cur + self._truncated_ranges >= len(ranges):
 
1096
                # Abruptly ends the response and close the connection
 
1097
                self.close_connection = 1
 
1098
                return
 
1099
            self.wfile.write(boundary_line)
 
1100
            self.send_header('Content-type', 'application/octet-stream')
 
1101
            self.send_header('Content-Range', 'bytes %d-%d/%d'
 
1102
                             % (start, end, file_size))
 
1103
            self.end_headers()
 
1104
            self.send_range_content(file, start, end - start + 1)
 
1105
            cur += 1
 
1106
        # Final boundary
 
1107
        self.wfile.write(boundary_line)
 
1108
 
 
1109
 
 
1110
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
 
1111
    """Tests the case of bug 198646, disconnecting before a boundary."""
 
1112
 
 
1113
    _req_handler_class = TruncatedBeforeBoundaryRequestHandler
 
1114
 
 
1115
    def setUp(self):
 
1116
        super(TestTruncatedBeforeBoundary, self).setUp()
 
1117
        self.build_tree_contents([('a', '0123456789')],)
 
1118
 
 
1119
    def test_readv_with_short_reads(self):
 
1120
        server = self.get_readonly_server()
 
1121
        t = self.get_readonly_transport()
 
1122
        # Force separate ranges for each offset
 
1123
        t._bytes_to_read_before_seek = 0
 
1124
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
 
1125
        self.assertEqual((0, '0'), ireadv.next())
 
1126
        self.assertEqual((2, '2'), ireadv.next())
 
1127
        self.assertEqual((4, '45'), ireadv.next())
 
1128
        self.assertEqual((9, '9'), ireadv.next())
 
1129
 
 
1130
 
1029
1131
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1030
1132
    """Errors out when range specifiers exceed the limit"""
1031
1133
 
1055
1157
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1056
1158
    """Tests readv requests against a server erroring out on too much ranges."""
1057
1159
 
 
1160
    scenarios = multiply_scenarios(
 
1161
        vary_by_http_client_implementation(),
 
1162
        vary_by_http_protocol_version(),
 
1163
        )
 
1164
 
1058
1165
    # Requests with more range specifiers will error out
1059
1166
    range_limit = 3
1060
1167
 
1063
1170
                                      protocol_version=self._protocol_version)
1064
1171
 
1065
1172
    def setUp(self):
1066
 
        http_utils.TestCaseWithWebserver.setUp(self)
 
1173
        super(TestLimitedRangeRequestServer, self).setUp()
1067
1174
        # We need to manipulate ranges that correspond to real chunks in the
1068
1175
        # response, so we build a content appropriately.
1069
1176
        filler = ''.join(['abcdefghij' for x in range(102)])
1095
1202
    Only the urllib implementation is tested here.
1096
1203
    """
1097
1204
 
1098
 
    def setUp(self):
1099
 
        tests.TestCase.setUp(self)
1100
 
        self._old_env = {}
1101
 
        self.addCleanup(self._restore_env)
1102
 
 
1103
 
    def _install_env(self, env):
1104
 
        for name, value in env.iteritems():
1105
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1106
 
 
1107
 
    def _restore_env(self):
1108
 
        for name, value in self._old_env.iteritems():
1109
 
            osutils.set_or_unset_env(name, value)
1110
 
 
1111
1205
    def _proxied_request(self):
1112
1206
        handler = _urllib2_wrappers.ProxyHandler()
1113
 
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
 
1207
        request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
1114
1208
        handler.set_proxy(request, 'http')
1115
1209
        return request
1116
1210
 
 
1211
    def assertEvaluateProxyBypass(self, expected, host, no_proxy):
 
1212
        handler = _urllib2_wrappers.ProxyHandler()
 
1213
        self.assertEqual(expected,
 
1214
                          handler.evaluate_proxy_bypass(host, no_proxy))
 
1215
 
1117
1216
    def test_empty_user(self):
1118
 
        self._install_env({'http_proxy': 'http://bar.com'})
 
1217
        self.overrideEnv('http_proxy', 'http://bar.com')
 
1218
        request = self._proxied_request()
 
1219
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
 
1220
 
 
1221
    def test_user_with_at(self):
 
1222
        self.overrideEnv('http_proxy',
 
1223
                         'http://username@domain:password@proxy_host:1234')
1119
1224
        request = self._proxied_request()
1120
1225
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
1121
1226
 
1122
1227
    def test_invalid_proxy(self):
1123
1228
        """A proxy env variable without scheme"""
1124
 
        self._install_env({'http_proxy': 'host:1234'})
 
1229
        self.overrideEnv('http_proxy', 'host:1234')
1125
1230
        self.assertRaises(errors.InvalidURL, self._proxied_request)
1126
1231
 
 
1232
    def test_evaluate_proxy_bypass_true(self):
 
1233
        """The host is not proxied"""
 
1234
        self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
 
1235
        self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
 
1236
 
 
1237
    def test_evaluate_proxy_bypass_false(self):
 
1238
        """The host is proxied"""
 
1239
        self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
 
1240
 
 
1241
    def test_evaluate_proxy_bypass_unknown(self):
 
1242
        """The host is not explicitly proxied"""
 
1243
        self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
 
1244
        self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
 
1245
 
 
1246
    def test_evaluate_proxy_bypass_empty_entries(self):
 
1247
        """Ignore empty entries"""
 
1248
        self.assertEvaluateProxyBypass(None, 'example.com', '')
 
1249
        self.assertEvaluateProxyBypass(None, 'example.com', ',')
 
1250
        self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
 
1251
 
1127
1252
 
1128
1253
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
1129
1254
    """Tests proxy server.
1134
1259
    to the file names).
1135
1260
    """
1136
1261
 
 
1262
    scenarios = multiply_scenarios(
 
1263
        vary_by_http_client_implementation(),
 
1264
        vary_by_http_protocol_version(),
 
1265
        )
 
1266
 
1137
1267
    # FIXME: We don't have an https server available, so we don't
1138
1268
    # test https connections. --vila toolongago
1139
1269
 
1153
1283
            self.no_proxy_host = self.server_host_port
1154
1284
        # The secondary server is the proxy
1155
1285
        self.proxy_url = self.get_secondary_url()
1156
 
        self._old_env = {}
 
1286
        if self._testing_pycurl():
 
1287
            self.proxy_url = self.proxy_url.replace('+pycurl', '')
1157
1288
 
1158
1289
    def _testing_pycurl(self):
1159
1290
        # TODO: This is duplicated for lots of the classes in this file
1160
1291
        return (features.pycurl.available()
1161
1292
                and self._transport == PyCurlTransport)
1162
1293
 
1163
 
    def _install_env(self, env):
1164
 
        for name, value in env.iteritems():
1165
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1166
 
 
1167
 
    def _restore_env(self):
1168
 
        for name, value in self._old_env.iteritems():
1169
 
            osutils.set_or_unset_env(name, value)
1170
 
 
1171
 
    def proxied_in_env(self, env):
1172
 
        self._install_env(env)
1173
 
        t = self.get_readonly_transport()
1174
 
        try:
1175
 
            self.assertEqual('proxied contents of foo\n', t.get('foo').read())
1176
 
        finally:
1177
 
            self._restore_env()
1178
 
 
1179
 
    def not_proxied_in_env(self, env):
1180
 
        self._install_env(env)
1181
 
        t = self.get_readonly_transport()
1182
 
        try:
1183
 
            self.assertEqual('contents of foo\n', t.get('foo').read())
1184
 
        finally:
1185
 
            self._restore_env()
 
1294
    def assertProxied(self):
 
1295
        t = self.get_readonly_transport()
 
1296
        self.assertEqual('proxied contents of foo\n', t.get('foo').read())
 
1297
 
 
1298
    def assertNotProxied(self):
 
1299
        t = self.get_readonly_transport()
 
1300
        self.assertEqual('contents of foo\n', t.get('foo').read())
1186
1301
 
1187
1302
    def test_http_proxy(self):
1188
 
        self.proxied_in_env({'http_proxy': self.proxy_url})
 
1303
        self.overrideEnv('http_proxy', self.proxy_url)
 
1304
        self.assertProxied()
1189
1305
 
1190
1306
    def test_HTTP_PROXY(self):
1191
1307
        if self._testing_pycurl():
1194
1310
            # about. Should we ?)
1195
1311
            raise tests.TestNotApplicable(
1196
1312
                'pycurl does not check HTTP_PROXY for security reasons')
1197
 
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
 
1313
        self.overrideEnv('HTTP_PROXY', self.proxy_url)
 
1314
        self.assertProxied()
1198
1315
 
1199
1316
    def test_all_proxy(self):
1200
 
        self.proxied_in_env({'all_proxy': self.proxy_url})
 
1317
        self.overrideEnv('all_proxy', self.proxy_url)
 
1318
        self.assertProxied()
1201
1319
 
1202
1320
    def test_ALL_PROXY(self):
1203
 
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
 
1321
        self.overrideEnv('ALL_PROXY', self.proxy_url)
 
1322
        self.assertProxied()
1204
1323
 
1205
1324
    def test_http_proxy_with_no_proxy(self):
1206
 
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
1207
 
                                 'no_proxy': self.no_proxy_host})
 
1325
        self.overrideEnv('no_proxy', self.no_proxy_host)
 
1326
        self.overrideEnv('http_proxy', self.proxy_url)
 
1327
        self.assertNotProxied()
1208
1328
 
1209
1329
    def test_HTTP_PROXY_with_NO_PROXY(self):
1210
1330
        if self._testing_pycurl():
1211
1331
            raise tests.TestNotApplicable(
1212
1332
                'pycurl does not check HTTP_PROXY for security reasons')
1213
 
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
1214
 
                                 'NO_PROXY': self.no_proxy_host})
 
1333
        self.overrideEnv('NO_PROXY', self.no_proxy_host)
 
1334
        self.overrideEnv('HTTP_PROXY', self.proxy_url)
 
1335
        self.assertNotProxied()
1215
1336
 
1216
1337
    def test_all_proxy_with_no_proxy(self):
1217
 
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
1218
 
                                 'no_proxy': self.no_proxy_host})
 
1338
        self.overrideEnv('no_proxy', self.no_proxy_host)
 
1339
        self.overrideEnv('all_proxy', self.proxy_url)
 
1340
        self.assertNotProxied()
1219
1341
 
1220
1342
    def test_ALL_PROXY_with_NO_PROXY(self):
1221
 
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
1222
 
                                 'NO_PROXY': self.no_proxy_host})
 
1343
        self.overrideEnv('NO_PROXY', self.no_proxy_host)
 
1344
        self.overrideEnv('ALL_PROXY', self.proxy_url)
 
1345
        self.assertNotProxied()
1223
1346
 
1224
1347
    def test_http_proxy_without_scheme(self):
 
1348
        self.overrideEnv('http_proxy', self.server_host_port)
1225
1349
        if self._testing_pycurl():
1226
1350
            # pycurl *ignores* invalid proxy env variables. If that ever change
1227
1351
            # in the future, this test will fail indicating that pycurl do not
1228
1352
            # ignore anymore such variables.
1229
 
            self.not_proxied_in_env({'http_proxy': self.server_host_port})
 
1353
            self.assertNotProxied()
1230
1354
        else:
1231
 
            self.assertRaises(errors.InvalidURL,
1232
 
                              self.proxied_in_env,
1233
 
                              {'http_proxy': self.server_host_port})
 
1355
            self.assertRaises(errors.InvalidURL, self.assertProxied)
1234
1356
 
1235
1357
 
1236
1358
class TestRanges(http_utils.TestCaseWithWebserver):
1237
1359
    """Test the Range header in GET methods."""
1238
1360
 
 
1361
    scenarios = multiply_scenarios(
 
1362
        vary_by_http_client_implementation(),
 
1363
        vary_by_http_protocol_version(),
 
1364
        )
 
1365
 
1239
1366
    def setUp(self):
1240
 
        http_utils.TestCaseWithWebserver.setUp(self)
 
1367
        super(TestRanges, self).setUp()
1241
1368
        self.build_tree_contents([('a', '0123456789')],)
1242
1369
 
1243
1370
    def create_transport_readonly_server(self):
1281
1408
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1282
1409
    """Test redirection between http servers."""
1283
1410
 
 
1411
    scenarios = multiply_scenarios(
 
1412
        vary_by_http_client_implementation(),
 
1413
        vary_by_http_protocol_version(),
 
1414
        )
 
1415
 
1284
1416
    def setUp(self):
1285
1417
        super(TestHTTPRedirections, self).setUp()
1286
1418
        self.build_tree_contents([('a', '0123456789'),
1349
1481
    -- vila 20070212
1350
1482
    """
1351
1483
 
 
1484
    scenarios = multiply_scenarios(
 
1485
        vary_by_http_client_implementation(),
 
1486
        vary_by_http_protocol_version(),
 
1487
        )
 
1488
 
1352
1489
    def setUp(self):
1353
1490
        if (features.pycurl.available()
1354
1491
            and self._transport == PyCurlTransport):
1399
1536
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1400
1537
    """Test transport.do_catching_redirections."""
1401
1538
 
 
1539
    scenarios = multiply_scenarios(
 
1540
        vary_by_http_client_implementation(),
 
1541
        vary_by_http_protocol_version(),
 
1542
        )
 
1543
 
1402
1544
    def setUp(self):
1403
1545
        super(TestDoCatchRedirections, self).setUp()
1404
1546
        self.build_tree_contents([('a', '0123456789'),],)
1443
1585
                          self.get_a, self.old_transport, redirected)
1444
1586
 
1445
1587
 
 
1588
def _setup_authentication_config(**kwargs):
 
1589
    conf = config.AuthenticationConfig()
 
1590
    conf._get_config().update({'httptest': kwargs})
 
1591
    conf._save()
 
1592
 
 
1593
 
 
1594
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
 
1595
    """Unit tests for glue by which urllib2 asks us for authentication"""
 
1596
 
 
1597
    def test_get_user_password_without_port(self):
 
1598
        """We cope if urllib2 doesn't tell us the port.
 
1599
 
 
1600
        See https://bugs.launchpad.net/bzr/+bug/654684
 
1601
        """
 
1602
        user = 'joe'
 
1603
        password = 'foo'
 
1604
        _setup_authentication_config(scheme='http', host='localhost',
 
1605
                                     user=user, password=password)
 
1606
        handler = _urllib2_wrappers.HTTPAuthHandler()
 
1607
        got_pass = handler.get_user_password(dict(
 
1608
            user='joe',
 
1609
            protocol='http',
 
1610
            host='localhost',
 
1611
            path='/',
 
1612
            realm='Realm',
 
1613
            ))
 
1614
        self.assertEqual((user, password), got_pass)
 
1615
 
 
1616
 
1446
1617
class TestAuth(http_utils.TestCaseWithWebserver):
1447
1618
    """Test authentication scheme"""
1448
1619
 
1449
 
    _auth_header = 'Authorization'
1450
 
    _password_prompt_prefix = ''
1451
 
    _username_prompt_prefix = ''
1452
 
    # Set by load_tests
1453
 
    _auth_server = None
 
1620
    scenarios = multiply_scenarios(
 
1621
        vary_by_http_client_implementation(),
 
1622
        vary_by_http_protocol_version(),
 
1623
        vary_by_http_auth_scheme(),
 
1624
        )
1454
1625
 
1455
1626
    def setUp(self):
1456
1627
        super(TestAuth, self).setUp()
1480
1651
        return url
1481
1652
 
1482
1653
    def get_user_transport(self, user, password):
1483
 
        t = transport.get_transport(self.get_user_url(user, password))
 
1654
        t = transport.get_transport_from_url(
 
1655
            self.get_user_url(user, password))
1484
1656
        return t
1485
1657
 
1486
1658
    def test_no_user(self):
1598
1770
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1599
1771
                                            stderr=tests.StringIOWrapper())
1600
1772
        # Create a minimal config file with the right password
1601
 
        conf = config.AuthenticationConfig()
1602
 
        conf._get_config().update(
1603
 
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1604
 
                          'user': user, 'password': password}})
1605
 
        conf._save()
 
1773
        _setup_authentication_config(scheme='http', port=self.server.port,
 
1774
                                     user=user, password=password)
1606
1775
        # Issue a request to the server to connect
1607
1776
        self.assertEqual('contents of a\n',t.get('a').read())
1608
1777
        # stdin should have  been left untouched
1610
1779
        # Only one 'Authentication Required' error should occur
1611
1780
        self.assertEqual(1, self.server.auth_required_errors)
1612
1781
 
1613
 
    def test_user_from_auth_conf(self):
1614
 
        if self._testing_pycurl():
1615
 
            raise tests.TestNotApplicable(
1616
 
                'pycurl does not support authentication.conf')
1617
 
        user = 'joe'
1618
 
        password = 'foo'
1619
 
        self.server.add_user(user, password)
1620
 
        # Create a minimal config file with the right password
1621
 
        conf = config.AuthenticationConfig()
1622
 
        conf._get_config().update(
1623
 
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1624
 
                          'user': user, 'password': password}})
1625
 
        conf._save()
1626
 
        t = self.get_user_transport(None, None)
1627
 
        # Issue a request to the server to connect
1628
 
        self.assertEqual('contents of a\n', t.get('a').read())
1629
 
        # Only one 'Authentication Required' error should occur
1630
 
        self.assertEqual(1, self.server.auth_required_errors)
1631
 
 
1632
1782
    def test_changing_nonce(self):
1633
1783
        if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1634
1784
                                     http_utils.ProxyDigestAuthServer):
1635
1785
            raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1636
1786
        if self._testing_pycurl():
1637
 
            raise tests.KnownFailure(
 
1787
            self.knownFailure(
1638
1788
                'pycurl does not handle a nonce change')
1639
1789
        self.server.add_user('joe', 'foo')
1640
1790
        t = self.get_user_transport('joe', 'foo')
1650
1800
        # initial 'who are you' and a second 'who are you' with the new nonce)
1651
1801
        self.assertEqual(2, self.server.auth_required_errors)
1652
1802
 
 
1803
    def test_user_from_auth_conf(self):
 
1804
        if self._testing_pycurl():
 
1805
            raise tests.TestNotApplicable(
 
1806
                'pycurl does not support authentication.conf')
 
1807
        user = 'joe'
 
1808
        password = 'foo'
 
1809
        self.server.add_user(user, password)
 
1810
        _setup_authentication_config(scheme='http', port=self.server.port,
 
1811
                                     user=user, password=password)
 
1812
        t = self.get_user_transport(None, None)
 
1813
        # Issue a request to the server to connect
 
1814
        self.assertEqual('contents of a\n', t.get('a').read())
 
1815
        # Only one 'Authentication Required' error should occur
 
1816
        self.assertEqual(1, self.server.auth_required_errors)
 
1817
 
 
1818
    def test_no_credential_leaks_in_log(self):
 
1819
        self.overrideAttr(debug, 'debug_flags', set(['http']))
 
1820
        user = 'joe'
 
1821
        password = 'very-sensitive-password'
 
1822
        self.server.add_user(user, password)
 
1823
        t = self.get_user_transport(user, password)
 
1824
        # Capture the debug calls to mutter
 
1825
        self.mutters = []
 
1826
        def mutter(*args):
 
1827
            lines = args[0] % args[1:]
 
1828
            # Some calls output multiple lines, just split them now since we
 
1829
            # care about a single one later.
 
1830
            self.mutters.extend(lines.splitlines())
 
1831
        self.overrideAttr(trace, 'mutter', mutter)
 
1832
        # Issue a request to the server to connect
 
1833
        self.assertEqual(True, t.has('a'))
 
1834
        # Only one 'Authentication Required' error should occur
 
1835
        self.assertEqual(1, self.server.auth_required_errors)
 
1836
        # Since the authentification succeeded, there should be a corresponding
 
1837
        # debug line
 
1838
        sent_auth_headers = [line for line in self.mutters
 
1839
                             if line.startswith('> %s' % (self._auth_header,))]
 
1840
        self.assertLength(1, sent_auth_headers)
 
1841
        self.assertStartsWith(sent_auth_headers[0],
 
1842
                              '> %s: <masked>' % (self._auth_header,))
1653
1843
 
1654
1844
 
1655
1845
class TestProxyAuth(TestAuth):
1656
 
    """Test proxy authentication schemes."""
1657
 
 
1658
 
    _auth_header = 'Proxy-authorization'
1659
 
    _password_prompt_prefix = 'Proxy '
1660
 
    _username_prompt_prefix = 'Proxy '
 
1846
    """Test proxy authentication schemes.
 
1847
 
 
1848
    This inherits from TestAuth to tweak the setUp and filter some failing
 
1849
    tests.
 
1850
    """
 
1851
 
 
1852
    scenarios = multiply_scenarios(
 
1853
        vary_by_http_client_implementation(),
 
1854
        vary_by_http_protocol_version(),
 
1855
        vary_by_http_proxy_auth_scheme(),
 
1856
        )
1661
1857
 
1662
1858
    def setUp(self):
1663
1859
        super(TestProxyAuth, self).setUp()
1664
 
        self._old_env = {}
1665
 
        self.addCleanup(self._restore_env)
1666
1860
        # Override the contents to avoid false positives
1667
1861
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1668
1862
                                  ('b', 'not proxied contents of b\n'),
1671
1865
                                  ])
1672
1866
 
1673
1867
    def get_user_transport(self, user, password):
1674
 
        self._install_env({'all_proxy': self.get_user_url(user, password)})
 
1868
        proxy_url = self.get_user_url(user, password)
 
1869
        if self._testing_pycurl():
 
1870
            proxy_url = proxy_url.replace('+pycurl', '')
 
1871
        self.overrideEnv('all_proxy', proxy_url)
1675
1872
        return TestAuth.get_user_transport(self, user, password)
1676
1873
 
1677
 
    def _install_env(self, env):
1678
 
        for name, value in env.iteritems():
1679
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1680
 
 
1681
 
    def _restore_env(self):
1682
 
        for name, value in self._old_env.iteritems():
1683
 
            osutils.set_or_unset_env(name, value)
1684
 
 
1685
1874
    def test_empty_pass(self):
1686
1875
        if self._testing_pycurl():
1687
1876
            import pycurl
1688
1877
            if pycurl.version_info()[1] < '7.16.0':
1689
 
                raise tests.KnownFailure(
 
1878
                self.knownFailure(
1690
1879
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
1691
1880
        super(TestProxyAuth, self).test_empty_pass()
1692
1881
 
1716
1905
 
1717
1906
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1718
1907
 
 
1908
    scenarios = multiply_scenarios(
 
1909
        vary_by_http_client_implementation(),
 
1910
        vary_by_http_protocol_version(),
 
1911
        )
 
1912
 
1719
1913
    def setUp(self):
1720
1914
        super(SmartHTTPTunnellingTest, self).setUp()
1721
1915
        # We use the VFS layer as part of HTTP tunnelling tests.
1722
 
        self._captureVar('BZR_NO_SMART_VFS', None)
 
1916
        self.overrideEnv('BZR_NO_SMART_VFS', None)
1723
1917
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1724
1918
        self.http_server = self.get_readonly_server()
1725
1919
 
1729
1923
        server._url_protocol = self._url_protocol
1730
1924
        return server
1731
1925
 
1732
 
    def test_open_bzrdir(self):
 
1926
    def test_open_controldir(self):
1733
1927
        branch = self.make_branch('relpath')
1734
1928
        url = self.http_server.get_url() + 'relpath'
1735
 
        bd = bzrdir.BzrDir.open(url)
 
1929
        bd = controldir.ControlDir.open(url)
1736
1930
        self.addCleanup(bd.transport.disconnect)
1737
1931
        self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1738
1932
 
1741
1935
        # The 'readv' command in the smart protocol both sends and receives
1742
1936
        # bulk data, so we use that.
1743
1937
        self.build_tree(['data-file'])
1744
 
        http_transport = transport.get_transport(self.http_server.get_url())
 
1938
        http_transport = transport.get_transport_from_url(
 
1939
            self.http_server.get_url())
1745
1940
        medium = http_transport.get_smart_medium()
1746
1941
        # Since we provide the medium, the url below will be mostly ignored
1747
1942
        # during the test, as long as the path is '/'.
1755
1950
        post_body = 'hello\n'
1756
1951
        expected_reply_body = 'ok\x012\n'
1757
1952
 
1758
 
        http_transport = transport.get_transport(self.http_server.get_url())
 
1953
        http_transport = transport.get_transport_from_url(
 
1954
            self.http_server.get_url())
1759
1955
        medium = http_transport.get_smart_medium()
1760
1956
        response = medium.send_http_smart_request(post_body)
1761
1957
        reply_body = response.read()
1810
2006
 
1811
2007
class Test_redirected_to(tests.TestCase):
1812
2008
 
 
2009
    scenarios = vary_by_http_client_implementation()
 
2010
 
1813
2011
    def test_redirected_to_subdir(self):
1814
2012
        t = self._transport('http://www.example.com/foo')
1815
2013
        r = t._redirected_to('http://www.example.com/foo',
1817
2015
        self.assertIsInstance(r, type(t))
1818
2016
        # Both transports share the some connection
1819
2017
        self.assertEqual(t._get_connection(), r._get_connection())
 
2018
        self.assertEqual('http://www.example.com/foo/subdir/', r.base)
1820
2019
 
1821
2020
    def test_redirected_to_self_with_slash(self):
1822
2021
        t = self._transport('http://www.example.com/foo')
1833
2032
        r = t._redirected_to('http://www.example.com/foo',
1834
2033
                             'http://foo.example.com/foo/subdir')
1835
2034
        self.assertIsInstance(r, type(t))
 
2035
        self.assertEqual('http://foo.example.com/foo/subdir/',
 
2036
            r.external_url())
1836
2037
 
1837
2038
    def test_redirected_to_same_host_sibling_protocol(self):
1838
2039
        t = self._transport('http://www.example.com/foo')
1839
2040
        r = t._redirected_to('http://www.example.com/foo',
1840
2041
                             'https://www.example.com/foo')
1841
2042
        self.assertIsInstance(r, type(t))
 
2043
        self.assertEqual('https://www.example.com/foo/',
 
2044
            r.external_url())
1842
2045
 
1843
2046
    def test_redirected_to_same_host_different_protocol(self):
1844
2047
        t = self._transport('http://www.example.com/foo')
1845
2048
        r = t._redirected_to('http://www.example.com/foo',
1846
2049
                             'ftp://www.example.com/foo')
1847
 
        self.assertNotEquals(type(r), type(t))
 
2050
        self.assertNotEqual(type(r), type(t))
 
2051
        self.assertEqual('ftp://www.example.com/foo/', r.external_url())
 
2052
 
 
2053
    def test_redirected_to_same_host_specific_implementation(self):
 
2054
        t = self._transport('http://www.example.com/foo')
 
2055
        r = t._redirected_to('http://www.example.com/foo',
 
2056
                             'https+urllib://www.example.com/foo')
 
2057
        self.assertEqual('https://www.example.com/foo/', r.external_url())
1848
2058
 
1849
2059
    def test_redirected_to_different_host_same_user(self):
1850
2060
        t = self._transport('http://joe@www.example.com/foo')
1851
2061
        r = t._redirected_to('http://www.example.com/foo',
1852
2062
                             'https://foo.example.com/foo')
1853
2063
        self.assertIsInstance(r, type(t))
1854
 
        self.assertEqual(t._user, r._user)
 
2064
        self.assertEqual(t._parsed_url.user, r._parsed_url.user)
 
2065
        self.assertEqual('https://joe@foo.example.com/foo/', r.external_url())
1855
2066
 
1856
2067
 
1857
2068
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1910
2121
    pass
1911
2122
 
1912
2123
 
1913
 
if tests.HTTPSServerFeature.available():
 
2124
if features.HTTPSServerFeature.available():
1914
2125
    from bzrlib.tests import https_server
1915
2126
    class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1916
2127
        pass
1924
2135
    """
1925
2136
 
1926
2137
    def setUp(self):
1927
 
        tests.TestCase.setUp(self)
1928
2138
        self.server = self._activity_server(self._protocol_version)
1929
2139
        self.server.start_server()
1930
 
        self.activities = {}
 
2140
        self.addCleanup(self.server.stop_server)
 
2141
        _activities = {} # Don't close over self and create a cycle
1931
2142
        def report_activity(t, bytes, direction):
1932
 
            count = self.activities.get(direction, 0)
 
2143
            count = _activities.get(direction, 0)
1933
2144
            count += bytes
1934
 
            self.activities[direction] = count
1935
 
 
 
2145
            _activities[direction] = count
 
2146
        self.activities = _activities
1936
2147
        # We override at class level because constructors may propagate the
1937
2148
        # bound method and render instance overriding ineffective (an
1938
2149
        # alternative would be to define a specific ui factory instead...)
1939
2150
        self.overrideAttr(self._transport, '_report_activity', report_activity)
1940
 
        self.addCleanup(self.server.stop_server)
1941
2151
 
1942
2152
    def get_transport(self):
1943
2153
        t = self._transport(self.server.get_url())
2061
2271
 
2062
2272
class TestActivity(tests.TestCase, TestActivityMixin):
2063
2273
 
 
2274
    scenarios = multiply_scenarios(
 
2275
        vary_by_http_activity(),
 
2276
        vary_by_http_protocol_version(),
 
2277
        )
 
2278
 
2064
2279
    def setUp(self):
 
2280
        super(TestActivity, self).setUp()
2065
2281
        TestActivityMixin.setUp(self)
2066
2282
 
2067
2283
 
2076
2292
    _protocol_version = 'HTTP/1.1'
2077
2293
 
2078
2294
    def setUp(self):
 
2295
        super(TestNoReportActivity, self).setUp()
2079
2296
        self._transport =_urllib.HttpTransport_urllib
2080
2297
        TestActivityMixin.setUp(self)
2081
2298
 
2087
2304
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
2088
2305
    """Test authentication on the redirected http server."""
2089
2306
 
 
2307
    scenarios = vary_by_http_protocol_version()
 
2308
 
2090
2309
    _auth_header = 'Authorization'
2091
2310
    _password_prompt_prefix = ''
2092
2311
    _username_prompt_prefix = ''
2155
2374
        # stdout should be empty, stderr will contains the prompts
2156
2375
        self.assertEqual('', stdout.getvalue())
2157
2376
 
2158