~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Jelmer Vernooij
  • Date: 2011-04-09 19:25:42 UTC
  • mto: (5777.5.1 inventoryworkingtree)
  • mto: This revision was merged to the branch mainline in revision 5781.
  • Revision ID: jelmer@samba.org-20110409192542-8bbedp36s7nj928e
Split InventoryTree out of Tree.

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
    bzrdir,
35
35
    cethread,
36
36
    config,
37
 
    debug,
38
37
    errors,
39
38
    osutils,
40
39
    remote as _mod_remote,
41
40
    tests,
42
 
    trace,
43
41
    transport,
44
42
    ui,
45
43
    )
93
91
        ]
94
92
 
95
93
 
 
94
def vary_by_http_proxy_auth_scheme():
 
95
    return [
 
96
        ('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
 
97
        ('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
 
98
        ('basicdigest',
 
99
            dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
100
        ]
 
101
 
 
102
 
96
103
def vary_by_http_auth_scheme():
97
 
    scenarios = [
 
104
    return [
98
105
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
99
106
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
100
107
        ('basicdigest',
101
108
            dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
102
109
        ]
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
110
 
125
111
 
126
112
def vary_by_http_activity():
128
114
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
129
115
                            _transport=_urllib.HttpTransport_urllib,)),
130
116
        ]
131
 
    if features.HTTPSServerFeature.available():
 
117
    if tests.HTTPSServerFeature.available():
132
118
        activity_scenarios.append(
133
119
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
134
120
                                _transport=_urllib.HttpTransport_urllib,)),)
136
122
        activity_scenarios.append(
137
123
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
138
124
                                _transport=PyCurlTransport,)),)
139
 
        if features.HTTPSServerFeature.available():
 
125
        if tests.HTTPSServerFeature.available():
140
126
            from bzrlib.tests import (
141
127
                ssl_certs,
142
128
                )
533
519
    scenarios = vary_by_http_client_implementation()
534
520
 
535
521
    def test_http_registered(self):
536
 
        t = transport.get_transport_from_url(
537
 
            '%s://foo.com/' % self._url_protocol)
 
522
        t = transport.get_transport('%s://foo.com/' % self._url_protocol)
538
523
        self.assertIsInstance(t, transport.Transport)
539
524
        self.assertIsInstance(t, self._transport)
540
525
 
552
537
        self.start_server(server)
553
538
        url = server.get_url()
554
539
        # FIXME: needs a cleanup -- vila 20100611
555
 
        http_transport = transport.get_transport_from_url(url)
 
540
        http_transport = transport.get_transport(url)
556
541
        code, response = http_transport._post('abc def end-of-body')
557
542
        self.assertTrue(
558
543
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
1049
1034
        self.assertEqual('single', t._range_hint)
1050
1035
 
1051
1036
 
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
 
 
1118
1037
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1119
1038
    """Errors out when range specifiers exceed the limit"""
1120
1039
 
1570
1489
                          self.get_a, self.old_transport, redirected)
1571
1490
 
1572
1491
 
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
 
 
1602
1492
class TestAuth(http_utils.TestCaseWithWebserver):
1603
1493
    """Test authentication scheme"""
1604
1494
 
1608
1498
        vary_by_http_auth_scheme(),
1609
1499
        )
1610
1500
 
 
1501
    _auth_header = 'Authorization'
 
1502
    _password_prompt_prefix = ''
 
1503
    _username_prompt_prefix = ''
 
1504
    # Set by load_tests
 
1505
    _auth_server = None
 
1506
 
1611
1507
    def setUp(self):
1612
1508
        super(TestAuth, self).setUp()
1613
1509
        self.server = self.get_readonly_server()
1636
1532
        return url
1637
1533
 
1638
1534
    def get_user_transport(self, user, password):
1639
 
        t = transport.get_transport_from_url(
1640
 
            self.get_user_url(user, password))
 
1535
        t = transport.get_transport(self.get_user_url(user, password))
1641
1536
        return t
1642
1537
 
1643
1538
    def test_no_user(self):
1755
1650
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1756
1651
                                            stderr=tests.StringIOWrapper())
1757
1652
        # Create a minimal config file with the right password
1758
 
        _setup_authentication_config(scheme='http', port=self.server.port,
1759
 
                                     user=user, password=password)
 
1653
        _setup_authentication_config(
 
1654
            scheme='http', 
 
1655
            port=self.server.port,
 
1656
            user=user,
 
1657
            password=password)
1760
1658
        # Issue a request to the server to connect
1761
1659
        self.assertEqual('contents of a\n',t.get('a').read())
1762
1660
        # stdin should have  been left untouched
1769
1667
                                     http_utils.ProxyDigestAuthServer):
1770
1668
            raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1771
1669
        if self._testing_pycurl():
1772
 
            self.knownFailure(
 
1670
            raise tests.KnownFailure(
1773
1671
                'pycurl does not handle a nonce change')
1774
1672
        self.server.add_user('joe', 'foo')
1775
1673
        t = self.get_user_transport('joe', 'foo')
1792
1690
        user = 'joe'
1793
1691
        password = 'foo'
1794
1692
        self.server.add_user(user, password)
1795
 
        _setup_authentication_config(scheme='http', port=self.server.port,
1796
 
                                     user=user, password=password)
 
1693
        _setup_authentication_config(
 
1694
            scheme='http', 
 
1695
            port=self.server.port,
 
1696
            user=user,
 
1697
            password=password)
1797
1698
        t = self.get_user_transport(None, None)
1798
1699
        # Issue a request to the server to connect
1799
1700
        self.assertEqual('contents of a\n', t.get('a').read())
1800
1701
        # Only one 'Authentication Required' error should occur
1801
1702
        self.assertEqual(1, self.server.auth_required_errors)
1802
1703
 
1803
 
    def test_no_credential_leaks_in_log(self):
1804
 
        self.overrideAttr(debug, 'debug_flags', set(['http']))
 
1704
 
 
1705
def _setup_authentication_config(**kwargs):
 
1706
    conf = config.AuthenticationConfig()
 
1707
    conf._get_config().update({'httptest': kwargs})
 
1708
    conf._save()
 
1709
 
 
1710
 
 
1711
 
 
1712
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
 
1713
    """Unit tests for glue by which urllib2 asks us for authentication"""
 
1714
 
 
1715
    def test_get_user_password_without_port(self):
 
1716
        """We cope if urllib2 doesn't tell us the port.
 
1717
 
 
1718
        See https://bugs.launchpad.net/bzr/+bug/654684
 
1719
        """
1805
1720
        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,))
 
1721
        password = 'foo'
 
1722
        _setup_authentication_config(
 
1723
            scheme='http', 
 
1724
            host='localhost',
 
1725
            user=user,
 
1726
            password=password)
 
1727
        handler = _urllib2_wrappers.HTTPAuthHandler()
 
1728
        got_pass = handler.get_user_password(dict(
 
1729
            user='joe',
 
1730
            protocol='http',
 
1731
            host='localhost',
 
1732
            path='/',
 
1733
            realm='Realm',
 
1734
            ))
 
1735
        self.assertEquals((user, password), got_pass)
1828
1736
 
1829
1737
 
1830
1738
class TestProxyAuth(TestAuth):
1831
 
    """Test proxy authentication schemes.
1832
 
 
1833
 
    This inherits from TestAuth to tweak the setUp and filter some failing
1834
 
    tests.
1835
 
    """
 
1739
    """Test proxy authentication schemes."""
1836
1740
 
1837
1741
    scenarios = multiply_scenarios(
1838
1742
        vary_by_http_client_implementation(),
1840
1744
        vary_by_http_proxy_auth_scheme(),
1841
1745
        )
1842
1746
 
 
1747
    _auth_header = 'Proxy-authorization'
 
1748
    _password_prompt_prefix = 'Proxy '
 
1749
    _username_prompt_prefix = 'Proxy '
 
1750
 
1843
1751
    def setUp(self):
1844
1752
        super(TestProxyAuth, self).setUp()
1845
1753
        # Override the contents to avoid false positives
1857
1765
        if self._testing_pycurl():
1858
1766
            import pycurl
1859
1767
            if pycurl.version_info()[1] < '7.16.0':
1860
 
                self.knownFailure(
 
1768
                raise tests.KnownFailure(
1861
1769
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
1862
1770
        super(TestProxyAuth, self).test_empty_pass()
1863
1771
 
1917
1825
        # The 'readv' command in the smart protocol both sends and receives
1918
1826
        # bulk data, so we use that.
1919
1827
        self.build_tree(['data-file'])
1920
 
        http_transport = transport.get_transport_from_url(
1921
 
            self.http_server.get_url())
 
1828
        http_transport = transport.get_transport(self.http_server.get_url())
1922
1829
        medium = http_transport.get_smart_medium()
1923
1830
        # Since we provide the medium, the url below will be mostly ignored
1924
1831
        # during the test, as long as the path is '/'.
1932
1839
        post_body = 'hello\n'
1933
1840
        expected_reply_body = 'ok\x012\n'
1934
1841
 
1935
 
        http_transport = transport.get_transport_from_url(
1936
 
            self.http_server.get_url())
 
1842
        http_transport = transport.get_transport(self.http_server.get_url())
1937
1843
        medium = http_transport.get_smart_medium()
1938
1844
        response = medium.send_http_smart_request(post_body)
1939
1845
        reply_body = response.read()
1997
1903
        self.assertIsInstance(r, type(t))
1998
1904
        # Both transports share the some connection
1999
1905
        self.assertEqual(t._get_connection(), r._get_connection())
2000
 
        self.assertEquals('http://www.example.com/foo/subdir/', r.base)
2001
1906
 
2002
1907
    def test_redirected_to_self_with_slash(self):
2003
1908
        t = self._transport('http://www.example.com/foo')
2014
1919
        r = t._redirected_to('http://www.example.com/foo',
2015
1920
                             'http://foo.example.com/foo/subdir')
2016
1921
        self.assertIsInstance(r, type(t))
2017
 
        self.assertEquals('http://foo.example.com/foo/subdir/',
2018
 
            r.external_url())
2019
1922
 
2020
1923
    def test_redirected_to_same_host_sibling_protocol(self):
2021
1924
        t = self._transport('http://www.example.com/foo')
2022
1925
        r = t._redirected_to('http://www.example.com/foo',
2023
1926
                             'https://www.example.com/foo')
2024
1927
        self.assertIsInstance(r, type(t))
2025
 
        self.assertEquals('https://www.example.com/foo/',
2026
 
            r.external_url())
2027
1928
 
2028
1929
    def test_redirected_to_same_host_different_protocol(self):
2029
1930
        t = self._transport('http://www.example.com/foo')
2030
1931
        r = t._redirected_to('http://www.example.com/foo',
2031
1932
                             'ftp://www.example.com/foo')
2032
1933
        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())
2040
1934
 
2041
1935
    def test_redirected_to_different_host_same_user(self):
2042
1936
        t = self._transport('http://joe@www.example.com/foo')
2043
1937
        r = t._redirected_to('http://www.example.com/foo',
2044
1938
                             'https://foo.example.com/foo')
2045
1939
        self.assertIsInstance(r, type(t))
2046
 
        self.assertEqual(t._parsed_url.user, r._parsed_url.user)
2047
 
        self.assertEquals('https://joe@foo.example.com/foo/', r.external_url())
 
1940
        self.assertEqual(t._user, r._user)
2048
1941
 
2049
1942
 
2050
1943
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
2103
1996
    pass
2104
1997
 
2105
1998
 
2106
 
if features.HTTPSServerFeature.available():
 
1999
if tests.HTTPSServerFeature.available():
2107
2000
    from bzrlib.tests import https_server
2108
2001
    class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
2109
2002
        pass
2120
2013
        tests.TestCase.setUp(self)
2121
2014
        self.server = self._activity_server(self._protocol_version)
2122
2015
        self.server.start_server()
2123
 
        _activities = {} # Don't close over self and create a cycle
 
2016
        self.activities = {}
2124
2017
        def report_activity(t, bytes, direction):
2125
 
            count = _activities.get(direction, 0)
 
2018
            count = self.activities.get(direction, 0)
2126
2019
            count += bytes
2127
 
            _activities[direction] = count
2128
 
        self.activities = _activities
 
2020
            self.activities[direction] = count
2129
2021
 
2130
2022
        # We override at class level because constructors may propagate the
2131
2023
        # bound method and render instance overriding ineffective (an
2356
2248
        # stdout should be empty, stderr will contains the prompts
2357
2249
        self.assertEqual('', stdout.getvalue())
2358
2250
 
 
2251