13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for HTTP implementations.
19
This module defines a load_tests() method that parametrize tests classes for
20
transport implementation, http protocol versions and authentication schemes.
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# FIXME: This test should be repeated for each available http client
18
# implementation; at the moment we have urllib and pycurl.
23
20
# TODO: Should be renamed to bzrlib.transport.http.tests?
24
21
# TODO: What about renaming to bzrlib.tests.transport.http ?
26
23
from cStringIO import StringIO
30
import SimpleHTTPServer
36
31
from bzrlib import (
41
remote as _mod_remote,
47
from bzrlib.symbol_versioning import (
50
37
from bzrlib.tests import (
43
from bzrlib.tests.HttpServer import (
48
from bzrlib.tests.HTTPTestUtil import (
49
BadProtocolRequestHandler,
50
BadStatusRequestHandler,
51
ForbiddenRequestHandler,
54
HTTPServerRedirecting,
55
InvalidStatusRequestHandler,
56
LimitedRangeHTTPServer,
57
NoRangeRequestHandler,
59
ProxyDigestAuthServer,
61
SingleRangeRequestHandler,
62
SingleOnlyRangeRequestHandler,
63
TestCaseWithRedirectedWebserver,
64
TestCaseWithTwoWebservers,
65
TestCaseWithWebserver,
54
68
from bzrlib.transport import (
70
do_catching_redirections,
58
74
from bzrlib.transport.http import (
65
from bzrlib.transport.http._pycurl import PyCurlTransport
67
except errors.DependencyNotPresent:
68
pycurl_present = False
71
def load_tests(standard_tests, module, loader):
72
"""Multiply tests for http clients and protocol versions."""
73
result = loader.suiteClass()
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,
82
transport_scenarios = [
83
('urllib', dict(_transport=_urllib.HttpTransport_urllib,
84
_server=http_server.HttpServer_urllib,
85
_qualified_prefix='http+urllib',)),
88
transport_scenarios.append(
89
('pycurl', dict(_transport=PyCurlTransport,
90
_server=http_server.HttpServer_PyCurl,
91
_qualified_prefix='http+pycurl',)))
92
tests.multiply_tests(t_tests, transport_scenarios, result)
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,
100
TestHTTPRedirections,
101
TestHTTPSilentRedirections,
102
TestLimitedRangeRequestServer,
106
TestSpecificRequestHandler,
108
protocol_scenarios = [
109
('HTTP/1.0', dict(_protocol_version='HTTP/1.0')),
110
('HTTP/1.1', dict(_protocol_version='HTTP/1.1')),
112
tp_scenarios = tests.multiply_scenarios(transport_scenarios,
114
tests.multiply_tests(tp_tests, tp_scenarios, result)
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((
121
proxy_auth_scheme_scenarios = [
122
('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
123
('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
125
dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
127
tppa_scenarios = tests.multiply_scenarios(tp_scenarios,
128
proxy_auth_scheme_scenarios)
129
tests.multiply_tests(tppa_tests, tppa_scenarios, result)
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((
136
auth_scheme_scenarios = [
137
('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
138
('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
140
dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
142
tpa_scenarios = tests.multiply_scenarios(tp_scenarios,
143
auth_scheme_scenarios)
144
tests.multiply_tests(tpa_tests, tpa_scenarios, result)
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((
151
activity_scenarios = [
152
('urllib,http', dict(_activity_server=ActivityHTTPServer,
153
_transport=_urllib.HttpTransport_urllib,)),
155
if tests.HTTPSServerFeature.available():
156
activity_scenarios.append(
157
('urllib,https', dict(_activity_server=ActivityHTTPSServer,
158
_transport=_urllib.HttpTransport_urllib,)),)
160
activity_scenarios.append(
161
('pycurl,http', dict(_activity_server=ActivityHTTPServer,
162
_transport=PyCurlTransport,)),)
163
if tests.HTTPSServerFeature.available():
164
from bzrlib.tests import (
167
# FIXME: Until we have a better way to handle self-signed
168
# certificates (like allowing them in a test specific
169
# authentication.conf for example), we need some specialized pycurl
170
# transport for tests.
171
class HTTPS_pycurl_transport(PyCurlTransport):
173
def __init__(self, base, _from_transport=None):
174
super(HTTPS_pycurl_transport, self).__init__(
175
base, _from_transport)
176
self.cabundle = str(ssl_certs.build_path('ca.crt'))
178
activity_scenarios.append(
179
('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
180
_transport=HTTPS_pycurl_transport,)),)
182
tpact_scenarios = tests.multiply_scenarios(activity_scenarios,
184
tests.multiply_tests(tpact_tests, tpact_scenarios, result)
186
# No parametrization for the remaining tests
187
result.addTests(remaining_tests)
79
from bzrlib.transport.http._urllib import HttpTransport_urllib
80
from bzrlib.transport.http._urllib2_wrappers import (
192
87
class FakeManager(object):
258
class TestAuthHeader(tests.TestCase):
260
def parse_header(self, header, auth_handler_class=None):
261
if auth_handler_class is None:
262
auth_handler_class = _urllib2_wrappers.AbstractAuthHandler
263
self.auth_handler = auth_handler_class()
264
return self.auth_handler._parse_auth_header(header)
266
def test_empty_header(self):
267
scheme, remainder = self.parse_header('')
268
self.assertEquals('', scheme)
269
self.assertIs(None, remainder)
271
def test_negotiate_header(self):
272
scheme, remainder = self.parse_header('Negotiate')
273
self.assertEquals('negotiate', scheme)
274
self.assertIs(None, remainder)
276
def test_basic_header(self):
277
scheme, remainder = self.parse_header(
278
'Basic realm="Thou should not pass"')
279
self.assertEquals('basic', scheme)
280
self.assertEquals('realm="Thou should not pass"', remainder)
282
def test_basic_extract_realm(self):
283
scheme, remainder = self.parse_header(
284
'Basic realm="Thou should not pass"',
285
_urllib2_wrappers.BasicAuthHandler)
286
match, realm = self.auth_handler.extract_realm(remainder)
287
self.assertTrue(match is not None)
288
self.assertEquals('Thou should not pass', realm)
290
def test_digest_header(self):
291
scheme, remainder = self.parse_header(
292
'Digest realm="Thou should not pass"')
293
self.assertEquals('digest', scheme)
294
self.assertEquals('realm="Thou should not pass"', remainder)
297
class TestHTTPServer(tests.TestCase):
298
"""Test the HTTP servers implementations."""
300
def test_invalid_protocol(self):
301
class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
303
protocol_version = 'HTTP/0.1'
305
server = http_server.HttpServer(BogusRequestHandler)
307
self.assertRaises(httplib.UnknownProtocol,server.setUp)
310
self.fail('HTTP Server creation did not raise UnknownProtocol')
312
def test_force_invalid_protocol(self):
313
server = http_server.HttpServer(protocol_version='HTTP/0.1')
315
self.assertRaises(httplib.UnknownProtocol,server.setUp)
318
self.fail('HTTP Server creation did not raise UnknownProtocol')
320
def test_server_start_and_stop(self):
321
server = http_server.HttpServer()
323
self.assertTrue(server._http_running)
325
self.assertFalse(server._http_running)
327
def test_create_http_server_one_zero(self):
328
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
330
protocol_version = 'HTTP/1.0'
332
server = http_server.HttpServer(RequestHandlerOneZero)
334
self.addCleanup(server.tearDown)
335
self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
337
def test_create_http_server_one_one(self):
338
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
340
protocol_version = 'HTTP/1.1'
342
server = http_server.HttpServer(RequestHandlerOneOne)
344
self.addCleanup(server.tearDown)
345
self.assertIsInstance(server._httpd,
346
http_server.TestingThreadingHTTPServer)
348
def test_create_http_server_force_one_one(self):
349
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
351
protocol_version = 'HTTP/1.0'
353
server = http_server.HttpServer(RequestHandlerOneZero,
354
protocol_version='HTTP/1.1')
356
self.addCleanup(server.tearDown)
357
self.assertIsInstance(server._httpd,
358
http_server.TestingThreadingHTTPServer)
360
def test_create_http_server_force_one_zero(self):
361
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
363
protocol_version = 'HTTP/1.1'
365
server = http_server.HttpServer(RequestHandlerOneOne,
366
protocol_version='HTTP/1.0')
368
self.addCleanup(server.tearDown)
369
self.assertIsInstance(server._httpd,
370
http_server.TestingHTTPServer)
373
153
class TestWithTransport_pycurl(object):
374
154
"""Test case to inherit from if pycurl is present"""
829
654
self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
830
655
t.readv, 'a', [(12,2)])
832
def test_readv_multiple_get_requests(self):
833
server = self.get_readonly_server()
834
t = self._transport(server.get_url())
835
# force transport to issue multiple requests
836
t._max_readv_combine = 1
837
t._max_get_ranges = 1
838
l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
839
self.assertEqual(l[0], (0, '0'))
840
self.assertEqual(l[1], (1, '1'))
841
self.assertEqual(l[2], (3, '34'))
842
self.assertEqual(l[3], (9, '9'))
843
# The server should have issued 4 requests
844
self.assertEqual(4, server.GET_request_nb)
846
def test_readv_get_max_size(self):
847
server = self.get_readonly_server()
848
t = self._transport(server.get_url())
849
# force transport to issue multiple requests by limiting the number of
850
# bytes by request. Note that this apply to coalesced offsets only, a
851
# single range will keep its size even if bigger than the limit.
853
l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
854
self.assertEqual(l[0], (0, '0'))
855
self.assertEqual(l[1], (1, '1'))
856
self.assertEqual(l[2], (2, '2345'))
857
self.assertEqual(l[3], (6, '6789'))
858
# The server should have issued 3 requests
859
self.assertEqual(3, server.GET_request_nb)
861
def test_complete_readv_leave_pipe_clean(self):
862
server = self.get_readonly_server()
863
t = self._transport(server.get_url())
864
# force transport to issue multiple requests
866
l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
867
# The server should have issued 3 requests
868
self.assertEqual(3, server.GET_request_nb)
869
self.assertEqual('0123456789', t.get_bytes('a'))
870
self.assertEqual(4, server.GET_request_nb)
872
def test_incomplete_readv_leave_pipe_clean(self):
873
server = self.get_readonly_server()
874
t = self._transport(server.get_url())
875
# force transport to issue multiple requests
877
# Don't collapse readv results into a list so that we leave unread
878
# bytes on the socket
879
ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
880
self.assertEqual((0, '0'), ireadv.next())
881
# The server should have issued one request so far
882
self.assertEqual(1, server.GET_request_nb)
883
self.assertEqual('0123456789', t.get_bytes('a'))
884
# get_bytes issued an additional request, the readv pending ones are
886
self.assertEqual(2, server.GET_request_nb)
889
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
890
"""Always reply to range request as if they were single.
892
Don't be explicit about it, just to annoy the clients.
895
def get_multiple_ranges(self, file, file_size, ranges):
896
"""Answer as if it was a single range request and ignores the rest"""
897
(start, end) = ranges[0]
898
return self.get_single_range(file, file_size, start, end)
901
658
class TestSingleRangeRequestServer(TestRangeRequestServer):
902
659
"""Test readv against a server which accept only single range requests"""
904
_req_handler_class = SingleRangeRequestHandler
907
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
908
"""Only reply to simple range requests, errors out on multiple"""
910
def get_multiple_ranges(self, file, file_size, ranges):
911
"""Refuses the multiple ranges request"""
914
self.send_error(416, "Requested range not satisfiable")
916
(start, end) = ranges[0]
917
return self.get_single_range(file, file_size, start, end)
661
def create_transport_readonly_server(self):
662
return HttpServer(SingleRangeRequestHandler)
665
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
666
TestCaseWithWebserver):
667
"""Tests single range requests accepting server for urllib implementation"""
669
_transport = HttpTransport_urllib
672
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
673
TestSingleRangeRequestServer,
674
TestCaseWithWebserver):
675
"""Tests single range requests accepting server for pycurl implementation"""
920
678
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
921
679
"""Test readv against a server which only accept single range requests"""
923
_req_handler_class = SingleOnlyRangeRequestHandler
926
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
927
"""Ignore range requests without notice"""
930
# Update the statistics
931
self.server.test_case_server.GET_request_nb += 1
932
# Just bypass the range handling done by TestingHTTPRequestHandler
933
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
681
def create_transport_readonly_server(self):
682
return HttpServer(SingleOnlyRangeRequestHandler)
685
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
686
TestCaseWithWebserver):
687
"""Tests single range requests accepting server for urllib implementation"""
689
_transport = HttpTransport_urllib
692
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
693
TestSingleOnlyRangeRequestServer,
694
TestCaseWithWebserver):
695
"""Tests single range requests accepting server for pycurl implementation"""
936
698
class TestNoRangeRequestServer(TestRangeRequestServer):
937
699
"""Test readv against a server which do not accept range requests"""
939
_req_handler_class = NoRangeRequestHandler
942
class MultipleRangeWithoutContentLengthRequestHandler(
943
http_server.TestingHTTPRequestHandler):
944
"""Reply to multiple range requests without content length header."""
946
def get_multiple_ranges(self, file, file_size, ranges):
947
self.send_response(206)
948
self.send_header('Accept-Ranges', 'bytes')
949
boundary = "%d" % random.randint(0,0x7FFFFFFF)
950
self.send_header("Content-Type",
951
"multipart/byteranges; boundary=%s" % boundary)
953
for (start, end) in ranges:
954
self.wfile.write("--%s\r\n" % boundary)
955
self.send_header("Content-type", 'application/octet-stream')
956
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
960
self.send_range_content(file, start, end - start + 1)
962
self.wfile.write("--%s\r\n" % boundary)
965
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
967
_req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
970
class TruncatedMultipleRangeRequestHandler(
971
http_server.TestingHTTPRequestHandler):
972
"""Reply to multiple range requests truncating the last ones.
974
This server generates responses whose Content-Length describes all the
975
ranges, but fail to include the last ones leading to client short reads.
976
This has been observed randomly with lighttpd (bug #179368).
701
def create_transport_readonly_server(self):
702
return HttpServer(NoRangeRequestHandler)
705
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
706
TestCaseWithWebserver):
707
"""Tests range requests refusing server for urllib implementation"""
709
_transport = HttpTransport_urllib
712
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
713
TestNoRangeRequestServer,
714
TestCaseWithWebserver):
715
"""Tests range requests refusing server for pycurl implementation"""
718
class TestLimitedRangeRequestServer(object):
719
"""Tests readv requests against server that errors out on too much ranges.
721
This MUST be used by daughter classes that also inherit from
722
TestCaseWithWebserver.
724
We can't inherit directly from TestCaseWithWebserver or the
725
test framework will try to create an instance which cannot
726
run, its implementation being incomplete.
979
_truncated_ranges = 2
981
def get_multiple_ranges(self, file, file_size, ranges):
982
self.send_response(206)
983
self.send_header('Accept-Ranges', 'bytes')
985
self.send_header('Content-Type',
986
'multipart/byteranges; boundary=%s' % boundary)
987
boundary_line = '--%s\r\n' % boundary
988
# Calculate the Content-Length
990
for (start, end) in ranges:
991
content_length += len(boundary_line)
992
content_length += self._header_line_length(
993
'Content-type', 'application/octet-stream')
994
content_length += self._header_line_length(
995
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
996
content_length += len('\r\n') # end headers
997
content_length += end - start # + 1
998
content_length += len(boundary_line)
999
self.send_header('Content-length', content_length)
1002
# Send the multipart body
1004
for (start, end) in ranges:
1005
self.wfile.write(boundary_line)
1006
self.send_header('Content-type', 'application/octet-stream')
1007
self.send_header('Content-Range', 'bytes %d-%d/%d'
1008
% (start, end, file_size))
1010
if cur + self._truncated_ranges >= len(ranges):
1011
# Abruptly ends the response and close the connection
1012
self.close_connection = 1
1014
self.send_range_content(file, start, end - start + 1)
1017
self.wfile.write(boundary_line)
1020
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
1022
_req_handler_class = TruncatedMultipleRangeRequestHandler
1025
super(TestTruncatedMultipleRangeServer, self).setUp()
1026
self.build_tree_contents([('a', '0123456789')],)
1028
def test_readv_with_short_reads(self):
1029
server = self.get_readonly_server()
1030
t = self._transport(server.get_url())
1031
# Force separate ranges for each offset
1032
t._bytes_to_read_before_seek = 0
1033
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1034
self.assertEqual((0, '0'), ireadv.next())
1035
self.assertEqual((2, '2'), ireadv.next())
1036
if not self._testing_pycurl():
1037
# Only one request have been issued so far (except for pycurl that
1038
# try to read the whole response at once)
1039
self.assertEqual(1, server.GET_request_nb)
1040
self.assertEqual((4, '45'), ireadv.next())
1041
self.assertEqual((9, '9'), ireadv.next())
1042
# Both implementations issue 3 requests but:
1043
# - urllib does two multiple (4 ranges, then 2 ranges) then a single
1045
# - pycurl does two multiple (4 ranges, 4 ranges) then a single range
1046
self.assertEqual(3, server.GET_request_nb)
1047
# Finally the client have tried a single range request and stays in
1049
self.assertEqual('single', t._range_hint)
1051
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1052
"""Errors out when range specifiers exceed the limit"""
1054
def get_multiple_ranges(self, file, file_size, ranges):
1055
"""Refuses the multiple ranges request"""
1056
tcs = self.server.test_case_server
1057
if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
1059
# Emulate apache behavior
1060
self.send_error(400, "Bad Request")
1062
return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1063
self, file, file_size, ranges)
1066
class LimitedRangeHTTPServer(http_server.HttpServer):
1067
"""An HttpServer erroring out on requests with too much range specifiers"""
1069
def __init__(self, request_handler=LimitedRangeRequestHandler,
1070
protocol_version=None,
1072
http_server.HttpServer.__init__(self, request_handler,
1073
protocol_version=protocol_version)
1074
self.range_limit = range_limit
1077
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1078
"""Tests readv requests against a server erroring out on too much ranges."""
1080
# Requests with more range specifiers will error out
1083
731
def create_transport_readonly_server(self):
1084
return LimitedRangeHTTPServer(range_limit=self.range_limit,
1085
protocol_version=self._protocol_version)
732
# Requests with more range specifiers will error out
733
return LimitedRangeHTTPServer(range_limit=self.range_limit)
1087
735
def get_transport(self):
1088
736
return self._transport(self.get_readonly_server().get_url())
1090
738
def setUp(self):
1091
http_utils.TestCaseWithWebserver.setUp(self)
739
TestCaseWithWebserver.setUp(self)
1092
740
# We need to manipulate ranges that correspond to real chunks in the
1093
741
# response, so we build a content appropriately.
1094
filler = ''.join(['abcdefghij' for x in range(102)])
742
filler = ''.join(['abcdefghij' for _ in range(102)])
1095
743
content = ''.join(['%04d' % v + filler for v in range(16)])
1096
744
self.build_tree_contents([('a', content)],)
1609
1298
# Only one 'Authentication Required' error should occur
1610
1299
self.assertEqual(1, self.server.auth_required_errors)
1612
def _check_password_prompt(self, scheme, user, actual_prompt):
1613
expected_prompt = (self._password_prompt_prefix
1614
+ ("%s %s@%s:%d, Realm: '%s' password: "
1616
user, self.server.host, self.server.port,
1617
self.server.auth_realm)))
1618
self.assertEquals(expected_prompt, actual_prompt)
1620
def _expected_username_prompt(self, scheme):
1621
return (self._username_prompt_prefix
1622
+ "%s %s:%d, Realm: '%s' username: " % (scheme.upper(),
1623
self.server.host, self.server.port,
1624
self.server.auth_realm))
1626
def test_no_prompt_for_password_when_using_auth_config(self):
1627
if self._testing_pycurl():
1628
raise tests.TestNotApplicable(
1629
'pycurl does not support authentication.conf'
1630
' since it cannot prompt')
1634
stdin_content = 'bar\n' # Not the right password
1635
self.server.add_user(user, password)
1636
t = self.get_user_transport(user, None)
1637
ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1638
stdout=tests.StringIOWrapper())
1639
# Create a minimal config file with the right password
1640
conf = config.AuthenticationConfig()
1641
conf._get_config().update(
1642
{'httptest': {'scheme': 'http', 'port': self.server.port,
1643
'user': user, 'password': password}})
1645
# Issue a request to the server to connect
1646
self.assertEqual('contents of a\n',t.get('a').read())
1647
# stdin should have been left untouched
1648
self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
1649
# Only one 'Authentication Required' error should occur
1650
self.assertEqual(1, self.server.auth_required_errors)
1652
def test_user_from_auth_conf(self):
1653
if self._testing_pycurl():
1654
raise tests.TestNotApplicable(
1655
'pycurl does not support authentication.conf')
1658
self.server.add_user(user, password)
1659
# Create a minimal config file with the right password
1660
conf = config.AuthenticationConfig()
1661
conf._get_config().update(
1662
{'httptest': {'scheme': 'http', 'port': self.server.port,
1663
'user': user, 'password': password}})
1665
t = self.get_user_transport(None, None)
1666
# Issue a request to the server to connect
1667
self.assertEqual('contents of a\n', t.get('a').read())
1668
# Only one 'Authentication Required' error should occur
1669
self.assertEqual(1, self.server.auth_required_errors)
1671
def test_changing_nonce(self):
1672
if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1673
http_utils.ProxyDigestAuthServer):
1674
raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1675
if self._testing_pycurl():
1676
raise tests.KnownFailure(
1677
'pycurl does not handle a nonce change')
1678
self.server.add_user('joe', 'foo')
1679
t = self.get_user_transport('joe', 'foo')
1680
self.assertEqual('contents of a\n', t.get('a').read())
1681
self.assertEqual('contents of b\n', t.get('b').read())
1682
# Only one 'Authentication Required' error should have
1684
self.assertEqual(1, self.server.auth_required_errors)
1685
# The server invalidates the current nonce
1686
self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1687
self.assertEqual('contents of a\n', t.get('a').read())
1688
# Two 'Authentication Required' errors should occur (the
1689
# initial 'who are you' and a second 'who are you' with the new nonce)
1690
self.assertEqual(2, self.server.auth_required_errors)
1302
class TestHTTPAuth(TestAuth):
1303
"""Test HTTP authentication schemes.
1305
Daughter classes MUST inherit from TestCaseWithWebserver too.
1308
_auth_header = 'Authorization'
1311
TestCaseWithWebserver.setUp(self)
1312
self.server = self.get_readonly_server()
1313
TestAuth.setUp(self)
1315
def get_user_transport(self, user=None, password=None):
1316
return self._transport(self.get_user_url(user, password))
1694
1319
class TestProxyAuth(TestAuth):
1695
"""Test proxy authentication schemes."""
1320
"""Test proxy authentication schemes.
1322
Daughter classes MUST also inherit from TestCaseWithWebserver.
1697
1324
_auth_header = 'Proxy-authorization'
1698
_password_prompt_prefix = 'Proxy '
1699
_username_prompt_prefix = 'Proxy '
1701
1326
def setUp(self):
1702
super(TestProxyAuth, self).setUp()
1327
TestCaseWithWebserver.setUp(self)
1328
self.server = self.get_readonly_server()
1703
1329
self._old_env = {}
1704
1330
self.addCleanup(self._restore_env)
1331
TestAuth.setUp(self)
1705
1332
# Override the contents to avoid false positives
1706
1333
self.build_tree_contents([('a', 'not proxied contents of a\n'),
1707
1334
('b', 'not proxied contents of b\n'),
1721
1348
for name, value in self._old_env.iteritems():
1722
1349
osutils.set_or_unset_env(name, value)
1724
def test_empty_pass(self):
1725
if self._testing_pycurl():
1727
if pycurl.version_info()[1] < '7.16.0':
1728
raise tests.KnownFailure(
1729
'pycurl < 7.16.0 does not handle empty proxy passwords')
1730
super(TestProxyAuth, self).test_empty_pass()
1733
class SampleSocket(object):
1734
"""A socket-like object for use in testing the HTTP request handler."""
1736
def __init__(self, socket_read_content):
1737
"""Constructs a sample socket.
1739
:param socket_read_content: a byte sequence
1741
# Use plain python StringIO so we can monkey-patch the close method to
1742
# not discard the contents.
1743
from StringIO import StringIO
1744
self.readfile = StringIO(socket_read_content)
1745
self.writefile = StringIO()
1746
self.writefile.close = lambda: None
1748
def makefile(self, mode='r', bufsize=None):
1750
return self.readfile
1752
return self.writefile
1755
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1758
super(SmartHTTPTunnellingTest, self).setUp()
1759
# We use the VFS layer as part of HTTP tunnelling tests.
1760
self._captureVar('BZR_NO_SMART_VFS', None)
1761
self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1763
def create_transport_readonly_server(self):
1764
return http_utils.HTTPServerWithSmarts(
1765
protocol_version=self._protocol_version)
1767
def test_open_bzrdir(self):
1768
branch = self.make_branch('relpath')
1769
http_server = self.get_readonly_server()
1770
url = http_server.get_url() + 'relpath'
1771
bd = bzrdir.BzrDir.open(url)
1772
self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1774
def test_bulk_data(self):
1775
# We should be able to send and receive bulk data in a single message.
1776
# The 'readv' command in the smart protocol both sends and receives
1777
# bulk data, so we use that.
1778
self.build_tree(['data-file'])
1779
http_server = self.get_readonly_server()
1780
http_transport = self._transport(http_server.get_url())
1781
medium = http_transport.get_smart_medium()
1782
# Since we provide the medium, the url below will be mostly ignored
1783
# during the test, as long as the path is '/'.
1784
remote_transport = remote.RemoteTransport('bzr://fake_host/',
1787
[(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1789
def test_http_send_smart_request(self):
1791
post_body = 'hello\n'
1792
expected_reply_body = 'ok\x012\n'
1794
http_server = self.get_readonly_server()
1795
http_transport = self._transport(http_server.get_url())
1796
medium = http_transport.get_smart_medium()
1797
response = medium.send_http_smart_request(post_body)
1798
reply_body = response.read()
1799
self.assertEqual(expected_reply_body, reply_body)
1801
def test_smart_http_server_post_request_handler(self):
1802
httpd = self.get_readonly_server()._get_httpd()
1804
socket = SampleSocket(
1805
'POST /.bzr/smart %s \r\n' % self._protocol_version
1806
# HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1808
+ 'Content-Length: 6\r\n'
1811
# Beware: the ('localhost', 80) below is the
1812
# client_address parameter, but we don't have one because
1813
# we have defined a socket which is not bound to an
1814
# address. The test framework never uses this client
1815
# address, so far...
1816
request_handler = http_utils.SmartRequestHandler(socket,
1819
response = socket.writefile.getvalue()
1820
self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1821
# This includes the end of the HTTP headers, and all the body.
1822
expected_end_of_response = '\r\n\r\nok\x012\n'
1823
self.assertEndsWith(response, expected_end_of_response)
1826
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1827
"""No smart server here request handler."""
1830
self.send_error(403, "Forbidden")
1833
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1834
"""Test smart client behaviour against an http server without smarts."""
1836
_req_handler_class = ForbiddenRequestHandler
1838
def test_probe_smart_server(self):
1839
"""Test error handling against server refusing smart requests."""
1840
server = self.get_readonly_server()
1841
t = self._transport(server.get_url())
1842
# No need to build a valid smart request here, the server will not even
1843
# try to interpret it.
1844
self.assertRaises(errors.SmartProtocolError,
1845
t.get_smart_medium().send_http_smart_request,
1848
class Test_redirected_to(tests.TestCase):
1850
def test_redirected_to_subdir(self):
1851
t = self._transport('http://www.example.com/foo')
1852
r = t._redirected_to('http://www.example.com/foo',
1853
'http://www.example.com/foo/subdir')
1854
self.assertIsInstance(r, type(t))
1855
# Both transports share the some connection
1856
self.assertEquals(t._get_connection(), r._get_connection())
1858
def test_redirected_to_self_with_slash(self):
1859
t = self._transport('http://www.example.com/foo')
1860
r = t._redirected_to('http://www.example.com/foo',
1861
'http://www.example.com/foo/')
1862
self.assertIsInstance(r, type(t))
1863
# Both transports share the some connection (one can argue that we
1864
# should return the exact same transport here, but that seems
1866
self.assertEquals(t._get_connection(), r._get_connection())
1868
def test_redirected_to_host(self):
1869
t = self._transport('http://www.example.com/foo')
1870
r = t._redirected_to('http://www.example.com/foo',
1871
'http://foo.example.com/foo/subdir')
1872
self.assertIsInstance(r, type(t))
1874
def test_redirected_to_same_host_sibling_protocol(self):
1875
t = self._transport('http://www.example.com/foo')
1876
r = t._redirected_to('http://www.example.com/foo',
1877
'https://www.example.com/foo')
1878
self.assertIsInstance(r, type(t))
1880
def test_redirected_to_same_host_different_protocol(self):
1881
t = self._transport('http://www.example.com/foo')
1882
r = t._redirected_to('http://www.example.com/foo',
1883
'ftp://www.example.com/foo')
1884
self.assertNotEquals(type(r), type(t))
1886
def test_redirected_to_different_host_same_user(self):
1887
t = self._transport('http://joe@www.example.com/foo')
1888
r = t._redirected_to('http://www.example.com/foo',
1889
'https://foo.example.com/foo')
1890
self.assertIsInstance(r, type(t))
1891
self.assertEquals(t._user, r._user)
1894
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1895
"""Request handler for a unique and pre-defined request.
1897
The only thing we care about here is how many bytes travel on the wire. But
1898
since we want to measure it for a real http client, we have to send it
1901
We expect to receive a *single* request nothing more (and we won't even
1902
check what request it is, we just measure the bytes read until an empty
1906
def handle_one_request(self):
1907
tcs = self.server.test_case_server
1908
requestline = self.rfile.readline()
1909
headers = self.MessageClass(self.rfile, 0)
1910
# We just read: the request, the headers, an empty line indicating the
1911
# end of the headers.
1912
bytes_read = len(requestline)
1913
for line in headers.headers:
1914
bytes_read += len(line)
1915
bytes_read += len('\r\n')
1916
if requestline.startswith('POST'):
1917
# The body should be a single line (or we don't know where it ends
1918
# and we don't want to issue a blocking read)
1919
body = self.rfile.readline()
1920
bytes_read += len(body)
1921
tcs.bytes_read = bytes_read
1923
# We set the bytes written *before* issuing the write, the client is
1924
# supposed to consume every produced byte *before* checking that value.
1926
# Doing the oppposite may lead to test failure: we may be interrupted
1927
# after the write but before updating the value. The client can then
1928
# continue and read the value *before* we can update it. And yes,
1929
# this has been observed -- vila 20090129
1930
tcs.bytes_written = len(tcs.canned_response)
1931
self.wfile.write(tcs.canned_response)
1934
class ActivityServerMixin(object):
1936
def __init__(self, protocol_version):
1937
super(ActivityServerMixin, self).__init__(
1938
request_handler=PredefinedRequestHandler,
1939
protocol_version=protocol_version)
1940
# Bytes read and written by the server
1942
self.bytes_written = 0
1943
self.canned_response = None
1946
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
1950
if tests.HTTPSServerFeature.available():
1951
from bzrlib.tests import https_server
1952
class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1956
class TestActivity(tests.TestCase):
1957
"""Test socket activity reporting.
1959
We use a special purpose server to control the bytes sent and received and
1960
be able to predict the activity on the client socket.
1964
tests.TestCase.setUp(self)
1965
self.server = self._activity_server(self._protocol_version)
1967
self.activities = {}
1968
def report_activity(t, bytes, direction):
1969
count = self.activities.get(direction, 0)
1971
self.activities[direction] = count
1973
# We override at class level because constructors may propagate the
1974
# bound method and render instance overriding ineffective (an
1975
# alternative would be to define a specific ui factory instead...)
1976
self.orig_report_activity = self._transport._report_activity
1977
self._transport._report_activity = report_activity
1980
self._transport._report_activity = self.orig_report_activity
1981
self.server.tearDown()
1982
tests.TestCase.tearDown(self)
1984
def get_transport(self):
1985
return self._transport(self.server.get_url())
1987
def assertActivitiesMatch(self):
1988
self.assertEqual(self.server.bytes_read,
1989
self.activities.get('write', 0), 'written bytes')
1990
self.assertEqual(self.server.bytes_written,
1991
self.activities.get('read', 0), 'read bytes')
1994
self.server.canned_response = '''HTTP/1.1 200 OK\r
1995
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
1996
Server: Apache/2.0.54 (Fedora)\r
1997
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
1998
ETag: "56691-23-38e9ae00"\r
1999
Accept-Ranges: bytes\r
2000
Content-Length: 35\r
2002
Content-Type: text/plain; charset=UTF-8\r
2004
Bazaar-NG meta directory, format 1
2006
t = self.get_transport()
2007
self.assertEqual('Bazaar-NG meta directory, format 1\n',
2008
t.get('foo/bar').read())
2009
self.assertActivitiesMatch()
2012
self.server.canned_response = '''HTTP/1.1 200 OK\r
2013
Server: SimpleHTTP/0.6 Python/2.5.2\r
2014
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2015
Content-type: application/octet-stream\r
2016
Content-Length: 20\r
2017
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2020
t = self.get_transport()
2021
self.assertTrue(t.has('foo/bar'))
2022
self.assertActivitiesMatch()
2024
def test_readv(self):
2025
self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
2026
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2027
Server: Apache/2.0.54 (Fedora)\r
2028
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2029
ETag: "238a3c-16ec2-805c5540"\r
2030
Accept-Ranges: bytes\r
2031
Content-Length: 1534\r
2033
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2036
--418470f848b63279b\r
2037
Content-type: text/plain; charset=UTF-8\r
2038
Content-range: bytes 0-254/93890\r
2040
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2041
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2042
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2043
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2044
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2046
--418470f848b63279b\r
2047
Content-type: text/plain; charset=UTF-8\r
2048
Content-range: bytes 1000-2049/93890\r
2051
mbp@sourcefrog.net-20050311063625-07858525021f270b
2052
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2053
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2054
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2055
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2056
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2057
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2058
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2059
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2060
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2061
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2062
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2063
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2064
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2065
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2066
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2067
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2068
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2069
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2070
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2072
--418470f848b63279b--\r
2074
t = self.get_transport()
2075
# Remember that the request is ignored and that the ranges below
2076
# doesn't have to match the canned response.
2077
l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2078
self.assertEqual(2, len(l))
2079
self.assertActivitiesMatch()
2081
def test_post(self):
2082
self.server.canned_response = '''HTTP/1.1 200 OK\r
2083
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2084
Server: Apache/2.0.54 (Fedora)\r
2085
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2086
ETag: "56691-23-38e9ae00"\r
2087
Accept-Ranges: bytes\r
2088
Content-Length: 35\r
2090
Content-Type: text/plain; charset=UTF-8\r
2092
lalala whatever as long as itsssss
2094
t = self.get_transport()
2095
# We must send a single line of body bytes, see
2096
# PredefinedRequestHandler.handle_one_request
2097
code, f = t._post('abc def end-of-body\n')
2098
self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2099
self.assertActivitiesMatch()
1352
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
1353
"""Test http basic authentication scheme"""
1355
_transport = HttpTransport_urllib
1357
def create_transport_readonly_server(self):
1358
return HTTPBasicAuthServer()
1361
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
1362
"""Test proxy basic authentication scheme"""
1364
_transport = HttpTransport_urllib
1366
def create_transport_readonly_server(self):
1367
return ProxyBasicAuthServer()
1370
class TestDigestAuth(object):
1371
"""Digest Authentication specific tests"""
1373
def test_changing_nonce(self):
1374
self.server.add_user('joe', 'foo')
1375
t = self.get_user_transport('joe', 'foo')
1376
self.assertEqual('contents of a\n', t.get('a').read())
1377
self.assertEqual('contents of b\n', t.get('b').read())
1378
# Only one 'Authentication Required' error should have
1380
self.assertEqual(1, self.server.auth_required_errors)
1381
# The server invalidates the current nonce
1382
self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1383
self.assertEqual('contents of a\n', t.get('a').read())
1384
# Two 'Authentication Required' errors should occur (the
1385
# initial 'who are you' and a second 'who are you' with the new nonce)
1386
self.assertEqual(2, self.server.auth_required_errors)
1389
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
1390
"""Test http digest authentication scheme"""
1392
_transport = HttpTransport_urllib
1394
def create_transport_readonly_server(self):
1395
return HTTPDigestAuthServer()
1398
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
1399
TestCaseWithWebserver):
1400
"""Test proxy digest authentication scheme"""
1402
_transport = HttpTransport_urllib
1404
def create_transport_readonly_server(self):
1405
return ProxyDigestAuthServer()